Part of epic #624 (S1). Blocking — a real correctness bug, independent of the landing page.
Symptom
On the dev profile the embedded sim panics repeatedly with:
panicked at obc-sim/src/present.rs:154:
assertion `left == right` failed: self-diff missed 1 changed row(s) — a systematic hash-diff bug
left: 1
right: 0
--release compiles the debug_assert_eq! out, so the sim runs — but per the present.rs module doc the same defect then surfaces as a stale row on glass, not just a failed assert. So this is not a debug-only artifact; it is a visible glitch in the shipped demo.
Why it appears now (mechanism)
- The
feat/sim-guided-tours-carousel branch does not touch present.rs or obc_platform, and the sim's frame loop always calls self.present.present(None) (no overlay exclude). See gui.rs render_to_texture (~line 678).
- With
exclude = None, a miss can only mean: the FNV row-hash diff did not flag a row whose content differs from presented.
- Such a miss is normally self-healing: the next frame the row changes again and gets caught. The develop demo's GPS replay keeps the whole map moving, so any miss heals within a frame and is never observed. The tour deliberately parks the sim on static screens (Climb, Menu) for ~2.9 s dwells — a missed row on a static screen never heals, so the assert fires persistently.
Key insight: the repeating assert is a CASCADE — hunt the FIRST frame
The assert sits between step 1 (hash-store update — the "self-healing" write updates hashes for every row) and step 4 (the push into presented). So the first panic aborts the present after the hashes were updated but before any row was pushed: every row that changed that frame is now permanently desynced (hashes[y] matches fb but presented[y] doesn't), and every subsequent present re-asserts — usually with a small residual count like missed: 1 once the moving rows re-heal. Consequences:
- The steady-state
missed: 1 tells you nothing. Find the first failing frame and what happened on it (was a tour cmd — enter / ambient / seek — drained that frame? a screen transition? the very first present?).
- As a robustness fix (regardless of root cause): move the oracle check after the push, or push before asserting — so a single failure can never poison all subsequent frames. The assert still fires; it just stops self-cascading.
A genuine FNV-1a collision is ~2⁻³² per changed row — at 60 fps with a few hundred changed rows that's ~once per multi-hour session, far too rare for what we observed within seconds of load. Expect a structural cause.
Task
- Reproduce deterministically on the native sim (fast; debug-asserts on in a debug build). Drive
App + Present through the tour's exact sequence — including the mid-frame app rebuild + GpxPlayer::seek that enter/ambient perform — and dwell on static screens. A unit test in the present.rs test module is the cleanest harness (see app_scenarios_idle_is_free_tick_is_small_pan_is_most, which already drives the real App + renderer into the backend).
- Instrument before fixing: on a miss, log the row index
y, the stored vs freshly-computed hash, and the differing byte ranges of fb[y] vs presented[y]. Measure, don't theorize.
- Fix at the correct layer. Only touch
obc_platform if the evidence shows a true hash weakness; otherwise fix present.rs / the caller. Include the assert-after-push reorder from above.
- Regression test: transition into a static screen (and separately: a demo-style app rebuild + seek), then present repeatedly, asserting zero misses and that the texture matches the frame on every row.
Acceptance
- Native sim (debug, asserts on) dwells on each tour screen (Map, Statistics, Climb, Menu, PoiList, PoiDetail, RouteOverview) ≥300 presents with no panic, including immediately after a demo-style reset.
- One failed oracle check can no longer desync subsequent frames (assert reordered after the push).
- New regression tests pass;
cargo test from firmware/ green.
--release web demo shows no stale row on any parked screen.
Part of epic #624 (S1). Blocking — a real correctness bug, independent of the landing page.
Symptom
On the dev profile the embedded sim panics repeatedly with:
--releasecompiles thedebug_assert_eq!out, so the sim runs — but per thepresent.rsmodule doc the same defect then surfaces as a stale row on glass, not just a failed assert. So this is not a debug-only artifact; it is a visible glitch in the shipped demo.Why it appears now (mechanism)
feat/sim-guided-tours-carouselbranch does not touchpresent.rsorobc_platform, and the sim's frame loop always callsself.present.present(None)(no overlayexclude). Seegui.rsrender_to_texture(~line 678).exclude = None, a miss can only mean: the FNV row-hash diff did not flag a row whose content differs frompresented.Key insight: the repeating assert is a CASCADE — hunt the FIRST frame
The assert sits between step 1 (hash-store update — the "self-healing" write updates
hashesfor every row) and step 4 (the push intopresented). So the first panic aborts the present after the hashes were updated but before any row was pushed: every row that changed that frame is now permanently desynced (hashes[y]matchesfbbutpresented[y]doesn't), and every subsequent present re-asserts — usually with a small residual count likemissed: 1once the moving rows re-heal. Consequences:missed: 1tells you nothing. Find the first failing frame and what happened on it (was a tour cmd —enter/ambient/seek— drained that frame? a screen transition? the very first present?).A genuine FNV-1a collision is ~2⁻³² per changed row — at 60 fps with a few hundred changed rows that's ~once per multi-hour session, far too rare for what we observed within seconds of load. Expect a structural cause.
Task
App+Presentthrough the tour's exact sequence — including the mid-frame app rebuild +GpxPlayer::seekthatenter/ambientperform — and dwell on static screens. A unit test in thepresent.rstest module is the cleanest harness (seeapp_scenarios_idle_is_free_tick_is_small_pan_is_most, which already drives the realApp+ renderer into the backend).y, the stored vs freshly-computed hash, and the differing byte ranges offb[y]vspresented[y]. Measure, don't theorize.obc_platformif the evidence shows a true hash weakness; otherwise fixpresent.rs/ the caller. Include the assert-after-push reorder from above.Acceptance
cargo testfromfirmware/green.--releaseweb demo shows no stale row on any parked screen.