fix(06-08): the pump's accepted fixes ride the chain's surfaces — puck + freshness (G7c)

- during guidance the pump consumed fixes without refreshing the chain's
  surfaces: the puck froze at the boot fix while the camera rode the
  snapped line (the UI-SPEC's recorded call: 'the puck ... carr[ies]
  progress'), and lastFix stayed at boot so the strip's 45s staleness
  law falsely flipped SIGNAL LOST mid-drive and re-ran the chain
- one injected dep (updatePuck) composes both in main.ts: lastFix = fix,
  marker.update (the RAW fix — the camera rides the SNAPPED foot, the
  two-authorities-one-line law), marker.setStale(false); the camera is
  NOT touched (the guidance camera owns the viewport — the guard
  handleLive already keeps)
- G7c pins the dep call per accepted fix + the main.ts source-scan for
  the marker composition (the RS2 boot-wiring shape)
- found while building the real-drive gate: any drive past 45s would
  have hit both defects on the road
- web 148/148, typecheck clean; captures regenerated 20/20
This commit is contained in:
araharah-executor
2026-10-24 21:37:22 +08:00
parent ed86196f60
commit b83a88e00d
3 changed files with 49 additions and 0 deletions
+12
View File
@@ -450,6 +450,18 @@ async function boot(): Promise<void> {
onGuidanceChange: () => { onGuidanceChange: () => {
if (backSync !== null) backSync(); // confirm open/close + activation/teardown re-apply the graph if (backSync !== null) backSync(); // confirm open/close + activation/teardown re-apply the graph
}, },
// 06-08 G7c: the pump's fixes ride the chain's surfaces — the puck moves
// (the UI-SPEC's recorded call: the puck carries progress) and the
// chain's freshness axis stays live (a drive past 45s never flips
// SIGNAL LOST while fixes flow). The camera is NOT touched here: the
// guidance camera owns the viewport (the same guard handleLive keeps).
updatePuck: (fix) => {
lastFix = fix;
if (map !== null) {
marker.update(map, fix);
marker.setStale(false);
}
},
fetchImpl: fetch.bind(globalThis), fetchImpl: fetch.bind(globalThis),
bearer: getSession, bearer: getSession,
}); });
+7
View File
@@ -136,6 +136,12 @@ export interface RouteControllerDeps {
findGantry?: (banner: HTMLElement) => HTMLElement | null; findGantry?: (banner: HTMLElement) => HTMLElement | null;
/** Fired on guidance phase transitions — main.ts re-syncs the BackButton. */ /** Fired on guidance phase transitions — main.ts re-syncs the BackButton. */
onGuidanceChange?: () => void; onGuidanceChange?: () => void;
/** The pump's per-fix chain-surface dep (06-08 G7c): every ACCEPTED fix
* moves the puck (the raw fix — the camera rides the snapped foot) and
* refreshes the chain's freshness axis (the strip's staleness law reads
* the LAST fix; a drive past 45s must never flip SIGNAL LOST while
* fixes flow). Composed in main.ts: marker.update + setStale(false). */
updatePuck?: (fix: Fix) => void;
/** Injectable fetch (the envelope POST/DELETE — tests pass a stub). */ /** Injectable fetch (the envelope POST/DELETE — tests pass a stub). */
fetchImpl?: typeof fetch; fetchImpl?: typeof fetch;
/** The memory-held session token getter (the Bearer header). */ /** The memory-held session token getter (the Bearer header). */
@@ -284,6 +290,7 @@ export function createRouteController(deps: RouteControllerDeps): {
* gate decides when the cockpit mounts. */ * gate decides when the cockpit mounts. */
function onWatchFix(fix: Fix): void { function onWatchFix(fix: Fix): void {
if (state !== "route") return; // PX-03's precondition: never without a route if (state !== "route") return; // PX-03's precondition: never without a route
if (deps.updatePuck !== undefined) deps.updatePuck(fix); // the puck + the chain's freshness ride every accepted fix (G7c)
if (guidance === null) { if (guidance === null) {
const model = selectedModel(); const model = selectedModel();
if (model === null) return; if (model === null) return;
+30
View File
@@ -968,6 +968,9 @@ function steppedDeps(opts = {}) {
sourceCalls: [], sourceCalls: [],
followModes: [], followModes: [],
fetches: [], fetches: [],
/** 06-08 G7c: the pump's per-fix puck/freshness dep recordings. */
puckFixes: [],
puckStale: [],
fetchMode: "ok", // "ok" | "404" — the idempotent-delete tolerance flip fetchMode: "ok", // "ok" | "404" — the idempotent-delete tolerance flip
}; };
// every source.route call is recorded (the exactly-one-request gates) // every source.route call is recorded (the exactly-one-request gates)
@@ -997,6 +1000,12 @@ function steppedDeps(opts = {}) {
}; };
deps.nowMs = () => guide.now; deps.nowMs = () => guide.now;
deps.findGantry = (banner) => findByClass(banner, "guide-gantry"); deps.findGantry = (banner) => findByClass(banner, "guide-gantry");
// 06-08 G7c: the puck/freshness dep — main.ts composes marker.update +
// setStale(false); the fake records both surfaces for the gate.
deps.updatePuck = (fix) => {
guide.puckFixes.push(fix);
guide.puckStale.push(false);
};
// the envelope endpoint + an injectable recording fetch (the stubFetch of // the envelope endpoint + an injectable recording fetch (the stubFetch of
// the plan's acceptance) — every envelope call lands in guide.fetches // the plan's acceptance) — every envelope call lands in guide.fetches
deps.endpoint = "/api/guidance"; deps.endpoint = "/api/guidance";
@@ -1330,6 +1339,27 @@ test("G7b the confirm card survives a live fix — the D-09 surface never evicts
}); });
}); });
test("G7c the pump's fixes ride the chain's surfaces — puck + freshness per accepted fix", async () => {
await withDocumentAsync(async () => {
const deps = steppedDeps();
const controller = createRouteController(deps);
await driveTo(deps, controller);
const puckBefore = deps.__guide.puckFixes.length;
feed(900, { deps, advanceMs: 6000 });
await drain();
assert.equal(deps.__guide.puckFixes.length, puckBefore + 1, "every accepted fix reaches the puck");
assert.equal(deps.__guide.puckFixes.at(-1).lat, onLineFix(900, deps.__guide.now).lat, "the puck rides the RAW fix (the camera rides the snapped foot)");
assert.equal(deps.__guide.puckStale.at(-1), false, "the marker's stale flag clears while fixes flow");
// main.ts's wiring composes the REAL surfaces (the RS2 source-scan shape):
// marker.update + setStale(false) — the freshness axis rides the same dep
const mainCode = codeText(path.join(SRC, "main.ts"));
assert.ok(
/updatePuck:\s*\(fix\)\s*=>\s*\{[\s\S]{0,300}?marker\.update\(map, fix\)[\s\S]{0,200}?marker\.setStale\(false\)/.test(mainCode),
"main.ts's updatePuck must compose marker.update + setStale(false) — the puck carries progress (the UI-SPEC recorded call) and the chain's freshness axis stays live while driving",
);
});
});
test("G8 arrival — the machine's arrival effect runs the same teardown", async () => { test("G8 arrival — the machine's arrival effect runs the same teardown", async () => {
await withDocumentAsync(async () => { await withDocumentAsync(async () => {
const deps = steppedDeps(); const deps = steppedDeps();