From b83a88e00db7f4a5c9ba536e1121c3d5083d9c4a Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Sat, 24 Oct 2026 21:37:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(06-08):=20the=20pump's=20accepted=20fixes?= =?UTF-8?q?=20ride=20the=20chain's=20surfaces=20=E2=80=94=20puck=20+=20fre?= =?UTF-8?q?shness=20(G7c)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- web/src/main.ts | 12 ++++++++++++ web/src/route/route.ts | 7 +++++++ web/test/route-surface.test.mjs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/web/src/main.ts b/web/src/main.ts index fbbacdb..5300a3b 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -450,6 +450,18 @@ async function boot(): Promise { onGuidanceChange: () => { 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), bearer: getSession, }); diff --git a/web/src/route/route.ts b/web/src/route/route.ts index 8ff590e..4389456 100644 --- a/web/src/route/route.ts +++ b/web/src/route/route.ts @@ -136,6 +136,12 @@ export interface RouteControllerDeps { findGantry?: (banner: HTMLElement) => HTMLElement | null; /** Fired on guidance phase transitions — main.ts re-syncs the BackButton. */ 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). */ fetchImpl?: typeof fetch; /** The memory-held session token getter (the Bearer header). */ @@ -284,6 +290,7 @@ export function createRouteController(deps: RouteControllerDeps): { * gate decides when the cockpit mounts. */ function onWatchFix(fix: Fix): void { 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) { const model = selectedModel(); if (model === null) return; diff --git a/web/test/route-surface.test.mjs b/web/test/route-surface.test.mjs index f975676..6b0afdf 100644 --- a/web/test/route-surface.test.mjs +++ b/web/test/route-surface.test.mjs @@ -968,6 +968,9 @@ function steppedDeps(opts = {}) { sourceCalls: [], followModes: [], fetches: [], + /** 06-08 G7c: the pump's per-fix puck/freshness dep recordings. */ + puckFixes: [], + puckStale: [], fetchMode: "ok", // "ok" | "404" — the idempotent-delete tolerance flip }; // every source.route call is recorded (the exactly-one-request gates) @@ -997,6 +1000,12 @@ function steppedDeps(opts = {}) { }; deps.nowMs = () => guide.now; 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 plan's acceptance) — every envelope call lands in guide.fetches 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 () => { await withDocumentAsync(async () => { const deps = steppedDeps();