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:
+18
-2
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user