Post-Batch-1 cleanup: RS-5.8 size budget, F9 default flip, F10 stale-tile report - #88
Merged
Conversation
…yout; F9 diag default flipped to 0
RS-5.8: check_size_budget.py asserted the pre-unification schema —
MM_BOOT_BASE/MM_BOOT_SIZE, which no longer exist — so even after the C-comment
parse fix it could not run. Decision, taken from the header itself: under the
unified layout the real hazard is the app load image growing into the CFG
region, so the enforced budget is [MM_APP_BASE, MM_CFG_BASE) = 184 KB, not the
nominal MM_APP_SIZE (192 KB, which would silently admit a CFG collision). The
.cfg_reserved section is naturally excluded by ending the region at cfg_base.
RAM assertion unchanged. The Makefile banner ("BOOT = 4 KB / APP = 180 KB")
disagreed with the header and is reconciled to the unified truth. Local run:
PASS at 23,036 B / 184 KB. This was the last failure in the "L072
cross-compile" CI job, red for months — expected green after this commit.
F9 default flip: HOST_ALLOW_REG_WRITE_DIAG now defaults to 0. The soak the
2026-07-30 commit required has passed on air: a flag=0 build was flashed to
both boards, the wire probe confirmed production arming ACKs while 0x1D and
raw-TX 0x83 are FORBIDDEN, and a full 360 s FHSS run (1318 frames,
rx_decode_err=0) ran clean. Production builds now ship with the 13-register
diagnostic surface closed — including 0x1D/0x1E, the FCC airtime-invariant
bypass that was the reason the flag existed. Bench diagnostics (T2 TCXO,
SPI-isolation) build with EXTRA_CFLAGS="-DHOST_ALLOW_REG_WRITE_DIAG=1",
documented in config.h. NOTE: the currently-flashed bench boards run the
flag=1 build deliberately (they are the diagnostic bench); the flip changes
what a fresh production build ships with.
Firmware 38558 B; 27-target check green; size budget PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…me self-heal The reassembly-timeout keyframe request was measured net harmful (n=2/side: +1.88 pts fragment loss, -16% frames delivered, +31% timeouts — it amplifies the loss it fires on), but couldn't simply be deleted: the base canvas is persistent, so a lost tile update displays stale imagery until the encoder happens to resend it, and only the receiver knows what arrived. CMD_OP_TILE_STALE (0x6C), base -> tractor: u16le base_seq_ref + u8 bitmap[(n_tiles+7)/8]. 14 B body at the 12x8 grid = 15.4 ms on air vs 1298.8 ms for the keyframe it replaces (84x cheaper). Advisory dirty marks, level-triggered: a lost report is superseded by the next one. Path: web_ui scans Canvas.arrived_ms every LIFETRAC_TILE_STALE_PERIOD_S (3 s) for tiles older than LIFETRAC_TILE_STALE_AFTER_MS (20 s — must exceed the encoder's full sweep rotation, since a static-scene receiver cannot distinguish "static" from "lost" except via the sweep's periodic re-ship) -> MQTT -> image_rx_daemon radiates one copy -> image_tx_daemon -> MQTT -> camera_service back-dates tile_last_seq past the escalation horizon, so the existing age-escalation sort front-runs the marked tiles into the next scheduled frame at zero extra image airtime. Keyframe requests remain for cold start / base_seq mismatch / mode change only; the reassembly-timeout pokes are now gated behind LIFETRAC_KF_ON_REASM_TIMEOUT (default off). Tests: proto roundtrip + wire-cost pin + base-side scan semantics (10 new). Full suite 1089 passed / 2446 subtests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, F10 Batch 1 section updated from "awaiting flash" to merged + accepted with the five checklist items checked against bench-evidence/FW_BATCH1_acceptance_2026-07-30/RESULTS.md. New post-batch1-cleanup section records the RS-5.8 budget decision ([MM_APP_BASE, MM_CFG_BASE) = 184 KB), the F9 default flip, and the F10 stale-tile implementation with its open on-air verification item. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
web_ui connects to the MQTT broker at import time and raises after 30 s if none is listening, so the bare import passed on the bench (local mosquitto) but broke CI collection with ConnectionRefusedError. Use the same mock-patch + importlib.reload pattern as the other web_ui tests, plus their fastapi/paho skip guard for pure-firmware checkouts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Post–Batch-1 cleanup that (1) fixes the Murata L072 size-budget enforcement under the unified flash layout, (2) flips the diagnostic register-write gate default to production-safe, and (3) replaces the measured-harmful “keyframe on reassembly timeout” behavior with a low-airtime receiver-driven stale-tile report (0x6C) wired end-to-end.
Changes:
- Re-anchor RS-5.8 flash budget enforcement to
[MM_APP_BASE, MM_CFG_BASE)and align the Makefile “Region budgets” banner accordingly. - Flip
HOST_ALLOW_REG_WRITE_DIAGdefault to0and update design/status documentation to reflect Batch 1 on-air acceptance. - Add
CMD_OP_TILE_STALE (0x6C)protocol + base-side stale scanning and plumb the report through base → tractor →camera_service, with new unit tests.
Reviewed changes
Copilot reviewed 10 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| LifeTrac-v25/DESIGN-CONTROLLER/TODO.md | Updates Batch 1 status/acceptance notes and closes RS-5.8 item with decision context. |
| LifeTrac-v25/DESIGN-CONTROLLER/firmware/tractor_x8/image_tx_daemon.py | Handles CMD_OP_TILE_STALE and forwards raw args to camera_service via MQTT. |
| LifeTrac-v25/DESIGN-CONTROLLER/firmware/tractor_x8/camera_service.py | Applies stale-tile marks by back-dating tile_last_seq to front-run repairs. |
| LifeTrac-v25/DESIGN-CONTROLLER/firmware/murata_l072/tools/check_size_budget.py | Fixes size-budget enforcement to end at MM_CFG_BASE under unified layout. |
| LifeTrac-v25/DESIGN-CONTROLLER/firmware/murata_l072/Makefile | Updates printed region budget banner to match the unified-layout budget. |
| LifeTrac-v25/DESIGN-CONTROLLER/firmware/murata_l072/config.h | Flips HOST_ALLOW_REG_WRITE_DIAG default to 0 with updated rationale/commentary. |
| LifeTrac-v25/DESIGN-CONTROLLER/firmware/murata_l072/build/firmware.map | Updates the tracked map output to reflect the new build state. |
| LifeTrac-v25/DESIGN-CONTROLLER/base_station/web_ui.py | Adds periodic stale-tile scanning and publishes packed stale reports to MQTT. |
| LifeTrac-v25/DESIGN-CONTROLLER/base_station/tests/test_tile_stale.py | Adds protocol roundtrip + stale-scan semantic tests for F10. |
| LifeTrac-v25/DESIGN-CONTROLLER/base_station/lora_proto.py | Defines CMD_OP_TILE_STALE and adds pack_tile_stale / parse_tile_stale. |
| LifeTrac-v25/DESIGN-CONTROLLER/base_station/image_rx_daemon.py | Radiates CMD_OP_TILE_STALE one-shot commands and gates reasm-timeout keyframes behind env. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+24
to
+26
| from image_pipeline.canvas import Canvas # noqa: E402 | ||
| from web_ui import compute_stale_tiles # noqa: E402 | ||
|
|
Comment on lines
+1111
to
+1115
| body = pack_tile_stale(base_seq, stale, n_tiles) | ||
| mqtt_client.publish(TILE_STALE_TOPIC, body, qos=0) | ||
| except Exception: | ||
| # Advisory path: never let a transient error kill the worker. | ||
| pass |
Comment on lines
+1051
to
+1063
| def _on_tile_stale_msg(self, _client, _userdata, msg) -> None: | ||
| # F10: level-triggered advisory — radiate as a ONE-SHOT single | ||
| # copy. A lost report is superseded by the next periodic one, so | ||
| # no pending/retry machinery and no extra copies. Body arrives | ||
| # pre-packed from web_ui (u16le base_seq + bitmap). | ||
| body = bytes(msg.payload or b"") | ||
| if len(body) < 3 or len(body) > 200: | ||
| return | ||
| try: | ||
| self._ctrl_out.put_nowait( | ||
| pack_command_frame(CMD_OP_TILE_STALE, body)) | ||
| except queue.Full: | ||
| pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three items queued behind the Batch 1 merge (PR #87), now that the on-air acceptance passed.
RS-5.8 — L072 size budget actually enforced (4f23abd)
The "L072 cross-compile" CI job has been red for months:
check_size_budget.pystill asserted the pre-unification flash schema (MM_BOOT_BASE/MM_BOOT_SIZE, removed by the flash-layout remediation), on top of the C-comment parse crash fixed earlier. Decision taken: under the unified layout the enforceable hazard is the app load image growing into the CFG region, so the budget is [MM_APP_BASE, MM_CFG_BASE) = 184 KB. The Makefile banner now states the same layout, one source of truth. Passes locally at 23,036 B / 184 KB — this PR should turn that job green for the first time.F9 default flip (4f23abd)
HOST_ALLOW_REG_WRITE_DIAGnow defaults 0 inconfig.h, per the plan on PR #87: the flip waited for the on-air soak, which passed 2026-07-30 (flag=0 build: arming + profile switch clean, forbidden-register writes rejected with ERR_PROTO, 360 s soak —bench-evidence/FW_BATCH1_acceptance_2026-07-30/RESULTS.md). Bench builds that need the diag register surface usemake EXTRA_CFLAGS="-DHOST_ALLOW_REG_WRITE_DIAG=1".F10 — 0x6C stale-tile report (cb71ee3)
The reassembly-timeout keyframe self-heal was measured net harmful (n=2/side: +1.88 pts fragment loss, −16 % frames delivered, +31 % timeouts — it amplifies the loss it fires on), but couldn't just be deleted: the base canvas is persistent, so a lost tile update displays stale imagery until the encoder happens to resend it, and only the receiver knows what arrived.
New
CMD_OP_TILE_STALE(0x6C), base → tractor: u16le base_seq_ref + tile bitmap. 14 B body at the 12×8 grid ≈ 15.4 ms on air vs 1298.8 ms for the keyframe it replaces (84× cheaper). Advisory, level-triggered: a lost report is superseded by the next one.Path: web_ui scans
Canvas.arrived_msevery 3 s for tiles stale > 20 s (horizon must exceed the encoder's full sweep rotation, since a static-scene receiver cannot distinguish "static" from "lost" except via the sweep's periodic re-ship) → image_rx_daemon radiates one copy → image_tx_daemon → camera_service back-datestile_last_seqpast the escalation horizon, so the existing age-escalation sort front-runs marked tiles into the next scheduled frame at zero extra image airtime. The reassembly-timeout pokes are gated behindLIFETRAC_KF_ON_REASM_TIMEOUT(default off); keyframe requests remain for cold start / base_seq mismatch / mode change.Env knobs:
LIFETRAC_TILE_STALE_ENABLE(1),LIFETRAC_TILE_STALE_PERIOD_S(3.0),LIFETRAC_TILE_STALE_AFTER_MS(20000),LIFETRAC_KF_ON_REASM_TIMEOUT(0).Verification
🤖 Generated with Claude Code