diff options
| author | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-11-12 21:41:08 -0800 |
|---|---|---|
| committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-11-12 21:41:08 -0800 |
| commit | 2a9a3126a4955dffc40bc04ab88a5ccdb3da77c9 (patch) | |
| tree | b19e42dde8eccf6c653495bd103db75e78b19201 /core/src/commonTest/kotlin | |
| parent | 3afb04de43726d71580b626e856c68b6ed79984e (diff) | |
| download | rookery-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/kotlin')
| -rw-r--r-- | core/src/commonTest/kotlin/coffee/liz/rookery/bt/GanProtocolTest.kt | 36 |
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() |
