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
+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 { buildRouteCard, buildRoutePendingCard } from "./route/route-card.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 {
interface Window {
@@ -97,8 +97,37 @@ async function boot(): Promise<void> {
mainEl.appendChild(mapEl);
appRoot.appendChild(mainEl);
const map = bootMap(document.getElementById("map")!);
if (!map) document.body.dataset.map = "absent"; // WebGL2 absent — the shell still renders
// The route consult waits on this: the harness session stubs resolve the
// 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;
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
// consulted ONCE per boot, only when the location chain is live AND a
// 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) {
routeConsulted = true;
void routeController.consult(fix, destination);
void mapLayersReady.then(() => routeController.consult(fix, destination));
}
if (!tickStarted) startTick();
// emitted UNCONDITIONALLY (outside the map guard above): a WebGL2-absent