Releases: terraboops/lfg
Release list
v0.2.6 — faster animation + faster recovery
Changed
- GIF animation is faster. Frame delay was 125cs (1.25s/frame, ~7.5s loop) normal and 50cs (0.5s/frame, ~3s loop) when any agent is in a requesting state. Now 60cs (0.6s/frame, 3.6s loop) and 25cs (0.25s/frame, 1.5s loop) respectively — about 2x snappier across the board, and the requesting "pulse the fire icon" mode actually feels urgent now.
Fixed
-
Faster recovery when the iDotMatrix firmware wedges. v0.2.5's slower inter-packet pacing was meant to prevent the buffer-overflow class of freeze, but the next freeze showed the first write of a fresh send hanging — meaning the device had already entered a wedged state that no amount of client-side pacing prevents. Tuned for fast recovery instead:
- `BLE_WRITE_TIMEOUT_SECS` 5 → 2
- `BLE_MAX_CONSECUTIVE_TIMEOUTS` 3 → 2
- Reconnect-path `disconnect()` timeout 5s → 1s (it hangs anyway when the device is wedged — better to drop the handle and start a fresh scan)
- Post-disconnect backoff 3s → 1s
- `BLE_INTER_PACKET_MS` 1000 → 250 (walked back from v0.2.5; the 1s value didn't help and may have stressed the BLE link more)
Total recovery time from wedge: ~12s → ~5s.
v0.2.5 — slower inter-packet pacing
Fixed
-
Periodic ~15s freezes from BLE buffer overflow. v0.2.4 switched GIF data to `WriteWithoutResponse` to avoid hangs waiting for ATT acknowledgements, but freezes kept happening every ~30-60 minutes. Root cause turned out to be inter-packet pacing: ~45% of our sends are 2-packet GIFs, and the 100ms inter-block sleep was overflowing either the iDotMatrix firmware buffer or CoreBluetooth's transmit queue. On macOS that flips the `canSendWriteWithoutResponse` gate, which blocks the next write until the stack drains — manifesting as 5s write timeouts.
Per the 8none1 reverse-engineering doc, the protocol expects either listening for the `0500010001` confirmation notification between blocks, or sleeping ~1 second. Bumped `BLE_INTER_PACKET_MS` from 100 to 1000.
Multi-packet GIFs now take ~1s longer to transmit, but that's comfortably within the existing 2s debounce so overall display update latency is unchanged. Single-packet GIFs (~55% of sends) are entirely unaffected. Notification-based flow control is left for a future change.
v0.2.4 — WriteWithoutResponse for GIF data
Fixed
-
Periodic ~15s display freeze while writes hung waiting for ACKs. With v0.2.3's false-positive `is_connected()` probe removed, a second failure mode became visible: roughly every hour, three consecutive BLE writes would hang for the full 5s timeout each before the `consecutive_timeouts >= 3` threshold triggered a reconnect. All GIF packets were sent as `WriteWithResponse`, so the call blocked waiting for a device ACK that — due to what appears to be a firmware quirk that builds up over time — stopped coming.
Switched GIF data packets to `WriteWithoutResponse`, matching what reference Python iDotMatrix libraries do for bulk data. The existing 100ms inter-packet sleep provides the flow control the ACK wait used to provide, and the 30s heartbeat (still `WriteWithResponse`) remains the canonical liveness check — if the device actually stops responding, the heartbeat catches it and the v0.2.0 supervisor/watchdog handles the reconnect.
v0.2.3 — drop spurious is_connected probe
Fixed
-
Eliminated a class of false-positive disconnects. The per-tick `peripheral.is_connected()` probe introduced earlier as a cheap keepalive turned out to stall for >5s under heavy write activity on CoreBluetooth — not because the device was actually stuck, but because the call serialized behind other in-flight btleplug operations. Each stall tripped the timeout and forced a full reconnect cycle (~15s of lost display time).
Observed in the wild: ~2 out of every 3 disconnects were the probe misfiring rather than the device actually going away. Removed entirely. Real disconnects are still caught by:
- Write path — explicit `Device disconnected` errors break out of the inner loop immediately.
- 30s heartbeat — the brightness ping in the `else` branch detects silent disconnects during idle periods.
- v0.2.0 watchdog — final safety net if the loop stops making progress.
v0.2.2 — fix drifting GIF background colour
Fixed
-
Panel background no longer drifts to random colours. NeuQuant's palette ordering is input-dependent, and whatever colour happened to land at palette index 0 became the GIF Logical Screen Descriptor's `background_color_index` — which the iDotMatrix firmware renders as the "off" colour between pixels. Over successive frame sets the background could drift to dim purple, orange, etc. instead of black.
Pin pure black at palette index 0 after NeuQuant finishes training, and mirror the swap when encoding frame pixels so no non-black pixels are forced onto the pinned slot. Verified via a captured debug GIF that `background_color_index` is 0 and `palette[0] = (0, 0, 0)`.
v0.2.1 — watchdog false positive fix
Fixed
-
BLE watchdog no longer trips on hash-neutral hook events. v0.2.0's watchdog watched `last_hook_event_at` vs `last_gif_sent_at`, but `last_hook_event_at` was bumped for every webhook event — including events that don't affect display state (e.g. `Notification`, which falls through `event::handle_event`'s match to `_ => {}`). The BLE render loop correctly stayed in the heartbeat branch waiting for its 30s tick, and the watchdog mistook that for a stuck loop and forced a spurious reconnect.
The fix: in the loop's hash-unchanged `else` branch, bump `last_gif_sent_at` to now. Semantically that field is now "last point in time the loop is known to be caught up", which is true whenever the rendered hash matches what's on the device. Genuine wedges still never reach that branch, so the original v0.2.0 protection is preserved.
v0.2.0 — BLE supervisor and watchdog
Fixed
-
BLE render loop no longer silently wedges. Under certain conditions the inner render loop could stop making progress (panic in the child task, non-cancellable btleplug call, or some other stall) and no outer observer would notice — the display would freeze for many minutes with no warning in the logs even though hook events kept flowing.
A new `ble_supervisor` now wraps the render loop. It tracks `last_hook_event_at` (bumped on every webhook event) and `last_gif_sent_at` (bumped per successful send, reset per reconnection cycle). Every 5s it checks: if events have arrived but no GIF was sent in the last 20s, it first sets `force_ble_reconnect` to let the loop break cooperatively; if the loop is still wedged 5s later, it calls `JoinHandle::abort()` and respawns. The supervisor also respawns on normal exit and panic.
Worst-case recovery from a silent wedge is now ~30–45s instead of never.
Added
- Tamagotchi sprite theme (theme 11).