feat(05-05): ?fixpair harness knob + pinned-corpus origin lookup (GREEN)

- route-profiles.ts: HARNESS-ONLY module — sentinel ROUTE_PROFILE_HARNESS_ONLY
  + getRouteProfileOrigin wrapping getPairOrigin; D-12 header (pinned PUBLIC
  origin only, no coordinate params) + BA5 exclusion note
- index.html: ?fixpair=<pair-id> knob resolves the pair's pinned public
  origin as the mock's telegramFix (accuracy 12, heading null); unknown
  pairId keeps the default fix; knob documented in the profile comment
- [Rule 1 - Bug] main.ts never passed bootMap's opts.onLoad — route sources
  never existed and setRouteDrawn's setData silently no-op'd (cards rendered,
  the line never painted; invisible to the RS fakes). Wiring added + RS2
  boot-wiring gate pins it
- [Rule 1 - Bug] consult raced the map load: harness session stubs resolve
  the location chain before WebGL/style init, so draw landed on missing
  sources and the post-load empty install discarded it (probe: ROUTE token
  fired before RAMS-BASEMAP-READY). Consult now awaits mapLayersReady,
  resolved in the load handler after installRouteLayers (never wedges —
  finally-resolved; cards-only boots resolve immediately)
- Capture run: CAPTURE OK — 13/13; route line verified painted, alt swap
  renders ROUTE 2/2 +1 MIN VS ROUTE 1, pending card honest, dark PX-02
- npm test -w web: 76 passed (new RS2 boot-wiring gate included)
This commit is contained in:
araharah-executor
2026-10-24 06:01:57 +08:00
parent c9b1128a0f
commit af744e49c7
4 changed files with 92 additions and 7 deletions
+18 -2
View File
@@ -9,7 +9,7 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module"> <script type="module">
/* D-11 Mock-Telegram profile: ?mock=android|ios|tdesktop|macos|web + ?scheme=light|dark + ?pin=1 + ?locale=en|ms + ?fixdelay + ?sessiondelay + ?dest=<id> /* D-11 Mock-Telegram profile: ?mock=android|ios|tdesktop|macos|web + ?scheme=light|dark + ?pin=1 + ?locale=en|ms + ?fixdelay + ?sessiondelay + ?dest=<id> + ?fixpair=<pair-id>
Harness-only: served from web/dist-harness, never deployed. Harness-only: served from web/dist-harness, never deployed.
The WHOLE block below is inside `if (__ARAHARAH_HARNESS__)` — Vite's `define` The WHOLE block below is inside `if (__ARAHARAH_HARNESS__)` — Vite's `define`
replaces that identifier with `false` in the production build, so the branch is replaces that identifier with `false` in the production build, so the branch is
@@ -38,6 +38,22 @@
const sessionDelayMs = Number.parseInt(q.get("sessiondelay") || "0", 10); const sessionDelayMs = Number.parseInt(q.get("sessiondelay") || "0", 10);
const fixDelayMs = Number.parseInt(q.get("fixdelay") || "0", 10); const fixDelayMs = Number.parseInt(q.get("fixdelay") || "0", 10);
/* The 05-05 route profile (?fixpair=<pair-id>): the mock's Telegram fix
* becomes the PAIR'S PINNED PUBLIC ORIGIN, resolved from the committed
* fixture corpus through the harness-only route-profiles module (D-12 —
* the harness introduces no user coordinate; a ?lat/?lng knob is
* deliberately absent). An unknown pairId keeps the default fix — the
* boot must never wedge. The dynamic import lives inside this
* __ARAHARAH_HARNESS__ branch, so production dead-code-eliminates the
* module (BA5 greps web/dist for its sentinel). */
const fixpair = q.get("fixpair");
let telegramFix = { lat: 3.1513, lng: 101.6958, accuracyM: 12, headingDeg: null };
if (fixpair) {
const { getRouteProfileOrigin } = await import("/src/mock/route-profiles.ts");
const origin = getRouteProfileOrigin(fixpair);
if (origin) telegramFix = { lat: origin.lat, lng: origin.lng, accuracyM: 12, headingDeg: null };
}
// Set BEFORE main.ts is imported — this is the marker main.ts's install guard checks. // Set BEFORE main.ts is imported — this is the marker main.ts's install guard checks.
window.__ARAHARAH_MOCK__ = { profile, scheme, locale, dest }; window.__ARAHARAH_MOCK__ = { profile, scheme, locale, dest };
@@ -73,7 +89,7 @@
languageCode: locale, languageCode: locale,
grantLocation: profile !== "tdesktop", grantLocation: profile !== "tdesktop",
deliveryDelayMs: fixDelayMs > 0 ? fixDelayMs : undefined, deliveryDelayMs: fixDelayMs > 0 ? fixDelayMs : undefined,
telegramFix: { lat: 3.1513, lng: 101.6958, accuracyM: 12, headingDeg: null }, telegramFix,
startParam: dest ?? undefined, startParam: dest ?? undefined,
}); });
} }
+35 -5
View File
@@ -48,7 +48,7 @@ import { createFixtureRoutingSource } from "@araharah/routing/src/index.ts";
import { createRouteController, type RouteState } from "./route/route.ts"; import { createRouteController, type RouteState } from "./route/route.ts";
import { buildRouteCard, buildRoutePendingCard } from "./route/route-card.ts"; import { buildRouteCard, buildRoutePendingCard } from "./route/route-card.ts";
import { attachRouteEndpoints, removeRouteEndpoints } from "./route/route-markers.ts"; import { attachRouteEndpoints, removeRouteEndpoints } from "./route/route-markers.ts";
import type { MapLike } from "./route/route-layers.ts"; import { installRouteLayers, type MapLike } from "./route/route-layers.ts";
declare global { declare global {
interface Window { interface Window {
@@ -97,8 +97,37 @@ async function boot(): Promise<void> {
mainEl.appendChild(mapEl); mainEl.appendChild(mapEl);
appRoot.appendChild(mainEl); appRoot.appendChild(mainEl);
const map = bootMap(document.getElementById("map")!); // The route consult waits on this: the harness session stubs resolve the
if (!map) document.body.dataset.map = "absent"; // WebGL2 absent — the shell still renders // location chain faster than WebGL/style init, so consult+setData could
// land BEFORE the map's load event — the draw would hit sources that do
// not exist yet and the empty post-load install would silently discard it
// (found by the 05-05 capture legs: card rendered, line never painted).
// Resolves in map.ts's ONE load handler, right after installRouteLayers;
// a cards-only boot (map === null) resolves immediately. The Promise
// executor runs synchronously, so the resolver is assigned before any
// caller below reaches it.
let resolveMapLayersReady!: () => void;
const mapLayersReady: Promise<void> = new Promise((res) => {
resolveMapLayersReady = res;
});
const map = bootMap(document.getElementById("map")!, {
// The route layers install at map load, inside the ONE load handler
// (after building-3d, before the ready log — map.ts hosts the hook).
// 05-05 capture-leg fix: without this callback the sources never exist
// and setRouteDrawn's setData silently no-ops — cards rendered, the
// line never painted (the fakes in the RS gates could not see it).
onLoad: (m) => {
try {
installRouteLayers(m as unknown as MapLike);
} finally {
resolveMapLayersReady(); // the consult never wedges, even on install failure
}
},
});
if (!map) {
document.body.dataset.map = "absent"; // WebGL2 absent — the shell still renders
resolveMapLayersReady(); // cards-only degradation: nothing to wait for
}
let follow: ReturnType<typeof createFollowController> | null = null; let follow: ReturnType<typeof createFollowController> | null = null;
const marker = createPositionMarker(); // created here, attached in the guarded block below const marker = createPositionMarker(); // created here, attached in the guarded block below
@@ -426,10 +455,11 @@ async function boot(): Promise<void> {
// Auto-consult (the state-coexistence law): the fixture RoutingSource is // Auto-consult (the state-coexistence law): the fixture RoutingSource is
// consulted ONCE per boot, only when the location chain is live AND a // consulted ONCE per boot, only when the location chain is live AND a
// destination resolved — destination-unavailable and no-destination // destination resolved — destination-unavailable and no-destination
// boots never make a route request. // boots never make a route request. The consult itself waits for the
// map's load (route sources installed) — see mapLayersReady above.
if (destination !== null && !routeConsulted) { if (destination !== null && !routeConsulted) {
routeConsulted = true; routeConsulted = true;
void routeController.consult(fix, destination); void mapLayersReady.then(() => routeController.consult(fix, destination));
} }
if (!tickStarted) startTick(); if (!tickStarted) startTick();
// emitted UNCONDITIONALLY (outside the map guard above): a WebGL2-absent // emitted UNCONDITIONALLY (outside the map guard above): a WebGL2-absent
+31
View File
@@ -0,0 +1,31 @@
/*
* route-profiles.ts — HARNESS-ONLY ?fixpair= origin lookup (05-05).
* ----------------------------------------------------------------------------
* D-12: the pinned origin is COMMITTED PUBLIC FIXTURE DATA. The pair corpus
* is public-data routes between public places (RT2b proves every origin
* equals a gazetteer landmark within 1e-4 deg), so the harness delivers no
* user coordinate — it re-delivers a coordinate that is already committed
* to the repo. The ?fixpair knob accepts a pairId ONLY; arbitrary lat/lng
* query params are never read (T-05-14: a coordinate-parameter knob would
* reintroduce the coordinate-in-URL surface the fixture world exists to
* avoid).
*
* BA-EXCLUSION: this module is imported ONLY from the index.html harness
* block, inside `if (__ARAHARAH_HARNESS__)` — Vite's define replaces that
* identifier with `false` in the production build, so the dynamic import is
* dead-code-eliminated and the module never ships. The
* ROUTE_PROFILE_HARNESS_ONLY sentinel below joins BA5's token scan: if this
* module ever leaks into web/dist, the build fails.
*/
import { getPairOrigin } from "@araharah/routing/src/index.ts";
/** BA5 sentinel — must NEVER appear in any web/dist artifact. */
export const ROUTE_PROFILE_HARNESS_ONLY = true;
/** pairId -> the pair's pinned public origin, or null when the pairId is
* unknown (the harness keeps its default fix — an unknown pairId must not
* wedge the boot). Pure lookup over the committed corpus; the ONLY origin
* source the harness can deliver. */
export function getRouteProfileOrigin(pairId: string): { lat: number; lng: number } | null {
return getPairOrigin(pairId);
}
+8
View File
@@ -142,6 +142,14 @@ test("RS2 map-law — zero setStyle in web/src/route; opts.onLoad sits after bui
assert.ok(onLoad < ready, "the route install is BEFORE the RAMS-BASEMAP-READY log — sources exist when captures wait"); assert.ok(onLoad < ready, "the route install is BEFORE the RAMS-BASEMAP-READY log — sources exist when captures wait");
}); });
test("RS2 boot wiring — main.ts passes opts.onLoad to bootMap installing the route layers (05-05 capture fix)", () => {
const mainCode = codeText(path.join(SRC, "main.ts"));
assert.ok(
/bootMap\([\s\S]{0,400}?onLoad:\s*\(m\)\s*=>[\s\S]{0,200}?installRouteLayers\(/.test(mainCode),
"main.ts must wire bootMap's opts.onLoad to installRouteLayers — without the callback the sources never exist and setRouteDrawn's setData silently no-ops (cards render, the line never paints; found by the 05-05 capture legs)",
);
});
/* ————— RS3 — fake-map layer law ————— */ /* ————— RS3 — fake-map layer law ————— */
test("RS3 fake-map layers — 2 sources + 4 layers in order, idempotent install, setData swaps, empty clears", () => { test("RS3 fake-map layers — 2 sources + 4 layers in order, idempotent install, setData swaps, empty clears", () => {