summaryrefslogtreecommitdiff
path: root/core/src/commonTest
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2026-11-12 21:41:08 -0800
committerElizabeth Alexander Hunt <me@liz.coffee>2026-11-12 21:41:08 -0800
commit2a9a3126a4955dffc40bc04ab88a5ccdb3da77c9 (patch)
treeb19e42dde8eccf6c653495bd103db75e78b19201 /core/src/commonTest
parent3afb04de43726d71580b626e856c68b6ed79984e (diff)
downloadrookery-2a9a3126a4955dffc40bc04ab88a5ccdb3da77c9.tar.gz
rookery-2a9a3126a4955dffc40bc04ab88a5ccdb3da77c9.zip
GAN: never stall on lost packets; fix packaged Bluetooth runtime
Live testing on a GANicE3 (Gen4) showed a move held for 32 s and a deliberate disconnect once 16 moves backed up, both waiting on a move-history reply that never came (inherited from gan-web-bluetooth). - Retry history requests every 300 ms while stalled; after 1.5 s release held moves in order, flag the first with Move.afterGap, and request facelets so consumers can resync. Never disconnect over a backlog; a driver tick runs every 150 ms under a shared lock. - Bundle jdk.security.auth (and the other modules Compose suggests) in the packaged runtime: dbus-java needs it to authenticate to BlueZ. - `--cube monitor`: print facelets only on change, mark gaps, and accept r (reset to solved) / f (refresh) on stdin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'core/src/commonTest')
-rw-r--r--core/src/commonTest/kotlin/coffee/liz/rookery/bt/GanProtocolTest.kt36
1 files changed, 36 insertions, 0 deletions
diff --git a/core/src/commonTest/kotlin/coffee/liz/rookery/bt/GanProtocolTest.kt b/core/src/commonTest/kotlin/coffee/liz/rookery/bt/GanProtocolTest.kt
index 5e45af3..3ba2417 100644
--- a/core/src/commonTest/kotlin/coffee/liz/rookery/bt/GanProtocolTest.kt
+++ b/core/src/commonTest/kotlin/coffee/liz/rookery/bt/GanProtocolTest.kt
@@ -197,6 +197,42 @@ class GanProtocolTest {
)
}
+ @Test fun gen4RetriesHistoryThenGivesUpInsteadOfHanging() = runTest {
+ // The failure seen live on a GANicE3: a move after a lost packet waited until the next turn.
+ val d = GanGen4ProtocolDriver(clock)
+ val c = RecordingRawConnection()
+ d.handleStateEvent(c, Packets.gen4Facelets(10, SOLVED_STATE))
+ d.handleStateEvent(c, Packets.gen4Move(11, 4, 1, 100))
+ assertEquals(emptyList(), d.handleStateEvent(c, Packets.gen4Move(13, 5, 0, 155))) // 12 lost
+ assertEquals(1, c.sent.size)
+
+ now += 100; assertEquals(emptyList(), d.tick(c)); assertEquals(1, c.sent.size) // too soon to retry
+ now += 250; assertEquals(emptyList(), d.tick(c)); assertEquals(2, c.sent.size) // retried
+ assertEquals(0xD1.toByte(), c.sent.last()[0])
+
+ // History never comes: after 1.5 s the held move is released, flagged, and facelets requested.
+ now += 1_200
+ val released = d.tick(c)
+ assertEquals(listOf(Move("B", 155, 1_000, 13, afterGap = true)), released)
+ assertContentEquals(byteArrayOf(0xDD.toByte(), 0x04, 0x00, 0xED.toByte(), 0x00, 0x00) + ByteArray(14), c.sent.last())
+ assertEquals(0, c.disconnects)
+
+ // Live moves flow again immediately afterwards.
+ assertEquals(listOf("U"), d.handleStateEvent(c, Packets.gen4Move(14, 0, 0, 900)).map { (it as Move).move })
+ assertEquals(emptyList(), d.tick(c))
+ }
+
+ @Test fun gen4BigBacklogIsReleasedNotDisconnected() = runTest {
+ val d = GanGen4ProtocolDriver(clock)
+ val c = RecordingRawConnection()
+ d.handleStateEvent(c, Packets.gen4Facelets(10, SOLVED_STATE))
+ d.handleStateEvent(c, Packets.gen4Move(11, 0, 0, 1))
+ val out = (13..30).flatMap { d.handleStateEvent(c, Packets.gen4Move(it, 0, 0, it.toLong())) }
+ assertEquals((13..30).toList(), out.map { (it as Move).serial }) // 13-29 released, then 30 flows live
+ assertTrue((out.first() as Move).afterGap)
+ assertEquals(0, c.disconnects)
+ }
+
@Test fun gen4FaceletsTriggerHistoryWhenIdle() = runTest {
val d = GanGen4ProtocolDriver(clock)
val c = RecordingRawConnection()