Files
arah2/.planning/phases/08-alt-landmark-preprocessing-routing-backend-v2/08-02-SUMMARY.md
T

15 KiB
Raw Blame History

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
08-alt-landmark-preprocessing-routing-backend-v2 02 routing
alt
landmark
arg2
codec
dijkstra
typed-arrays
quantization
fixture
tdd
phase provides
08-alt-landmark-preprocessing-routing-backend-v2 (plan 08-01) the frozen ARG1 rejection vector (mini-graph-arg1.bin) this plan's G3 consumes, and the pre-ALT baseline record the codec change must not invalidate
phase provides
06-turn-by-turn-guidance-live-driving the ARG1 codec being versioned (graph.ts single authority), the A1 traversal law (bit0 never relaxed) the landmark Dijkstras mirror, and the committed mini-graph fixture basis
routing/src/landmark.ts — the ONE pure landmark compute core
farthestPointAnchors (deterministic degree>=2 farthest-point over the ĉ metric) + landmarkTables (exact integer Dijkstras, u16 tables, 0xFFFF sentinel, 65000-quantum loud overflow) + landmarkCoverage
ARG2 graph.ts — version-bumped single-authority codec
72-byte header (landmarkCount @60, quantum f32 @64), append-only u16 table suffix, exact-size law extended over the variable suffix BEFORE allocation, one decode path (ARG1 bytes → null)
the regenerated committed mini-graph.bin fixture (ARG2, L=2 anchors [0,18], quantum 0.1 s, coverage 1.0, byte-deterministic) through the SAME encode authority
extended G-suite (G1–G11) incl. the ARG1-fixture rejection pin, table round-trip, 9-case encode-validation matrix, landmarkCount=0 + monster-count decode rejections
RoadGraph.landmarkCount/landmarkQuantumS/landmarkTo/landmarkFrom/landmarkNodes — the typed-array fields Plan 08-03's ALT potentials consume
08-03 (ALT potentials read the tables)
08-04 (build script landmark stage via the same core)
08-05 (real-graph campaign)
bot routing suite (corridor graphs now ARG2)
added patterns
ĉ-metric surrogate: floor-quantize EDGE WEIGHTS once (Math.floor(seconds/quantumS)), exact integer Dijkstras over Int32 labels — quantization without an admissibility epsilon (research Pattern 2)
append-only layout versioning: new sections are a suffix, header grows into the old pad, ONE decode path, old-version bytes decode null (D-01 fail-honest)
tiny-real-landmark-stage fixture regeneration: the committed fixture flows through the SAME farthestPointAnchors/landmarkTables/encodeGraph the real build uses (byte-deterministic re-runs)
created modified
routing/src/landmark.ts
routing/test/landmark.test.mjs
routing/src/graph.ts (ARG2 version bump)
routing/test/graph.test.mjs (G-suite re-pin + G9–G11)
routing/test/fixtures/build-mini-graph.mjs (landmark stage)
routing/test/fixtures/mini-graph.bin (regenerated ARG2, 1668 → 1884 B)
routing/test/astar.test.mjs (A3 synthetic carries the landmark block)
routing/test/snap.test.mjs (S3 synthetic carries the landmark block)
bot/test/webhook.test.mjs (corridor/sparse builders compute landmark blocks)
bot/test/guidance-cron.test.mjs (unnamed-way builder computes the landmark block)
Fixture regeneration pulled forward into task 2's commit (Rule 3): task 2's own gate demands routing:test 0 failed, which is impossible while the committed fixture is ARG1 once decode is ARG2-only; task 3 then sealed byte-stability and re-ran the (idempotent) builder
Decode validates landmarkQuantumS (finite > 0) → null — hostile-quantum header defense in the T-08-01 family; nothing downstream can divide by it
Reserved u32 @68 is written 0 and accepted-untouched at decode (the plan's recommended reading — no assert on an unused field)
Anchor selection counts stored OUT-degree (blocked mirrors included) for the degree>=2 filter — a node on a oneway still sits on multi-edge road geometry
Graphs with no degree>=2 candidate (sparse/2-node synthetics) pin their anchor explicitly and still compute tables through landmarkTables — the selection filter is a quality law, not a table law
TDD with hand-computed integer vectors: RED expectations verified against an INDEPENDENT brute force before pinning (the L6 lesson — intuition about 'farthest' failed on a speed-skewed metric)
Tamper-vector tests construct CONSISTENT hostile buffers (byte size matches the tampered layout) so a specific decode law is isolated from the size law (G11)
SC3
SC4
~17min 2026-10-25

Phase 8 Plan 08-02: Landmark Core + ARG2 Codec Summary

The landmark compute core exists once as a pure typed-array module (deterministic farthest-point anchors + exact integer ĉ-metric Dijkstras with a 65000-quantum loud overflow), graph.ts is version-bumped to ARG2 with one fail-honest decode path whose exact-size law covers the u16 table suffix, and the committed fixture is regenerated as ARG2 through that same encode authority — routing 74/74 + bot 126/126 + both typechecks green, with astar.ts untouched (search behavior provably neutral at this commit).

Performance

  • Duration: ~17 min (10:34–10:51 UTC)
  • Started: 2026-10-25T10:34:07Z
  • Completed: 2026-10-25T10:51:00Z
  • Tasks: 3/3 (tasks 1 & 2 with full RED→GREEN gate commits)
  • Files modified: 10 (2 created, 8 modified)

Accomplishments

  • routing/src/landmark.ts — the ONE landmark compute core (zero imports, no I/O): farthestPointAnchors (first degree-≥2 candidate by node id, greedy farthest-point with per-pick forward-pass reuse, lower-id tie-breaks, documented coverageFraction early-stop knob), landmarkTables (forward + reversed integer Dijkstras over floor-quantized weights — the ĉ metric, no stored-distance rounding, no epsilon — binary-heap, lazy deletion, bit0 edges never relaxed in EITHER direction exactly mirroring astar.ts), landmarkCoverage (the build's per-anchor assert feed). 0xFFFF is DATA; any label past 65,000 quanta throws naming the distance and the fix.
  • ARG2 in graph.ts in place — no second codec: GRAPH_MAGIC = 0x41524732, GRAPH_VERSION = 2, HEADER_SIZE = 72 (f64 alignment kept), landmark header fields @60/@64/@68, append-only suffix (u16 toTable, u16 fromTable, u32 anchors) folded into layoutFor's total arithmetic so the exact-size check rejects monster counts BEFORE any view allocation. Decode refuses ARG1 bytes (the committed mini-graph-arg1.bin is the standing rejection vector), version ≠ 2, landmarkCount = 0 (no landmark-less ARG2 exists — D-01/A5), hostile quantum, and any size mismatch. GraphInput.landmarks is REQUIRED and loud-validated (9 named-throw classes).
  • The fixture is ARG2 through the one authority: the builder gained the tiny real landmark stage (L=2 → anchors [0, 18], quantum 0.1 s, coverage 1.00/1.00, fail-loud invariants before write) and is byte-deterministic on re-run; mini-graph-arg1.bin stayed byte-identical to its committed 08-01 bytes (cmp-verified).
  • The full battery is green at the final commit — routing:test 74/74 (L1–L6 + G1–G11 + untouched A/P/R/S suites), bot 126/126 (RT/H/GC + the updated corridor/cron builders), routing+bot typechecks clean. routing/src/astar.ts has ZERO changes — A1's 625-pair sweep green over the ARG2 fixture proves the codec change alone is behavior-neutral to search (routeAstar still runs haversine potentials; ALT lands in 08-03).

Task Commits

Each task was committed atomically (TDD tasks carry their RED gates):

  1. task 1: landmark.ts — the pure landmark compute core — c3a31ae (test, RED) → 1bf413b (test, L5 vector fix) → 5b7a130 (feat, GREEN)
  2. task 2: ARG2 codec in graph.ts — 34d96b0 (test, RED) → 32a00b5 (feat, GREEN; includes the fixture regen pull-forward)
  3. task 3: bot call sites + full battery seal — 9770867 (test)

Files Created/Modified

  • routing/src/landmark.ts — the pure landmark compute core (created)
  • routing/src/graph.ts — ARG2 codec: 72-byte header, u16 suffix, version-bumped decode laws, required-landmarks encode validation
  • routing/test/landmark.test.mjs — L1–L6: hand-computed vectors for anchors, quantum exactness, sentinels, asymmetry, overflow, the 5×5 grid (created)
  • routing/test/graph.test.mjs — G-suite re-pinned (G3/G5/G7/G8) + extended (G9 table round-trip, G10 encode validation, G11 decode rejection)
  • routing/test/fixtures/build-mini-graph.mjs — landmark stage + landmark stats in the summary line
  • routing/test/fixtures/mini-graph.bin — regenerated ARG2 (1884 B; +208-byte table suffix + 4 header bytes + 4-byte alignment)
  • routing/test/astar.test.mjs, routing/test/snap.test.mjs — synthetic encodeGraph call sites carry (inert until 08-03) landmark blocks
  • bot/test/webhook.test.mjs, bot/test/guidance-cron.test.mjs — corridor/sparse/cron builders compute tiny tables via farthestPointAnchors/landmarkTables and pass the landmarks block

Decisions Made

  • Pull the fixture regen forward (Rule 3): task 2's acceptance demands "runner output 0 failed" at its own commit — impossible with a committed ARG1 fixture under ARG2-only decode. The regen joined task 2's commit; task 3 verified the seal (idempotent re-run, byte-stable ARG1 vector) instead.
  • Decode-side quantum validation: a non-finite/non-positive landmarkQuantumS header decodes null — cheap hostile-header defense so 08-03's potentials can never divide by a corrupt quantum.
  • Degree filter counts stored out-edges (mirrors included): the blocked mirror still evidences multi-edge road geometry at a node; endpoints/dead-ends stay excluded.
  • Test-vector discipline: every pinned integer expectation was verified against an independent brute-force Dijkstra before pinning — which caught my own wrong intuition (below).

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Fixture regeneration pulled forward from task 3 into task 2's commit

  • Found during: task 2 (ARG2 codec)
  • Issue: task 2's own verification gate (npm run routing:test → 0 failed) cannot pass while the committed fixture is ARG1 bytes once decode becomes ARG2-only (G7 + A-suite + snap suite all decode the fixture)
  • Fix: extended build-mini-graph.mjs with the landmark stage and regenerated mini-graph.bin inside task 2's commit; task 3 then re-ran the builder (byte-identical output — determinism proven) and sealed ARG1-vector byte-stability
  • Files modified: routing/test/fixtures/build-mini-graph.mjs, routing/test/fixtures/mini-graph.bin
  • Verification: routing:test 74/74 at task-2 commit; cmp vs committed 08-01 ARG1 bytes passes; regen deterministic
  • Committed in: 32a00b5

2. [Rule 3 - Blocking] Two encodeGraph call sites missing from the plan's enumeration

  • Found during: task 2 (call-site census before implementing)
  • Issue: the required-landmarks law makes EVERY encodeGraph call throw — the plan's files list omitted routing/test/astar.test.mjs (A3's disconnected synthetic) and routing/test/snap.test.mjs (S3's bare 2-node synthetic)
  • Fix: both synthetics carry small hand-written landmark blocks (inert steering data until 08-03), keeping the cases' original focus
  • Files modified: routing/test/astar.test.mjs, routing/test/snap.test.mjs
  • Verification: A-suite and S-suite green in the full battery
  • Committed in: 32a00b5

3. [Rule 1 - Bug] Two hand-computed L-suite expectations were wrong (implementation was right)

  • Found during: task 1 GREEN
  • Issue: (a) L4 indexed table rows by anchor NODE ID instead of anchor POSITION (row-major is [ℓ·N+v], ℓ = position); (b) L6 assumed the ĉ-farthest node from corner 0 is the opposite corner 24 — with interior hops costing 2× perimeter quanta, interior node 18 (297 q) is farther than corner 24 (264 q)
  • Fix: re-pinned both vectors; node 18's 297-quantum max verified against an independent brute-force Dijkstra before pinning
  • Files modified: routing/test/landmark.test.mjs
  • Verification: L-suite green; the independent brute force output recorded in-session
  • Committed in: 5b7a130

4. [Rule 1 - Bug] Sparse/cron graphs have no degree-≥2 farthest-point candidate

  • Found during: task 3 (bot battery)
  • Issue: the plan assumed every call site can farthestPointAnchors(...) — but a single-segment graph's nodes all have out-degree 1, so selection legitimately returns empty and the first battery run failed at suite load
  • Fix: those call sites pin the anchor explicitly (Uint32Array.of(0)) and still compute tables through landmarkTables — the selection filter is a quality law, not a table law
  • Files modified: bot/test/webhook.test.mjs, bot/test/guidance-cron.test.mjs
  • Verification: bot suite 126/126
  • Committed in: 9770867

Total deviations: 4 auto-fixed (2 blocking-call-site/closure, 2 test-vector bugs) Impact on plan: All four were forced by the plan's own laws (required-landmarks encode, exact gate order) or by verified arithmetic; no scope creep — the 08-04/08-05 boundaries are untouched (build-road-graph.mjs still belongs to 08-04; no search code changed).

TDD Gate Compliance

Both tdd="true" tasks ran full RED→GREEN with separate commits: task 1 c3a31ae(+1bf413b) test gates BEFORE 5b7a130 feat; task 2 34d96b0 test gate BEFORE 32a00b5 feat. RED failures were verified to fail for the RIGHT reason each time (ERR_MODULE_NOT_FOUND for the new module; specific assertion deltas for the re-pinned codec laws). No gate skipped.

Issues Encountered

  • A first battery run piped bot tests through | tail -2 without pipefail, masking the suite's exit code behind tail's; caught by reading the output (npm error line), re-run with set -o pipefail — all subsequent battery commands use pipefail.
  • The plan's acceptance grep contains a typo ("landmarms"); the intent (both bot files pass a landmarks: { block) is satisfied literally.

User Setup Required

None - no external service configuration required.

Next Phase Readiness

  • 08-03 (ALT potentials): RoadGraph now exposes landmarkCount/landmarkQuantumS/landmarkTo/landmarkFrom/landmarkNodes as zero-copy views; only potential() in routeAstar changes. The table values are inert until then (documented coexistence — A-suite green at this commit is the neutrality proof).
  • 08-04 (build script): build-road-graph.mjs still encodes WITHOUT landmarks (line 336) — its planned preprocessing stage joins between edge assembly and the encode call, importing the SAME landmark.ts core the fixture builder now uses.
  • 08-05 (campaign): the real KL binary on disk is still ARG1 → decodes null until rebuilt+re-uploaded (the designed router-pending window, D-01); the committed baseline record (08-01) is untouched and its recorder's ARG1-version guard is exactly what makes that safe.

Phase: 08-alt-landmark-preprocessing-routing-backend-v2 Completed: 2026-10-25

Self-Check: PASSED

All 8 claimed files exist on disk; all 6 task commits (c3a31ae, 1bf413b, 5b7a130, 34d96b0, 32a00b5, 9770867) exist in git history. Final battery at 9770867: routing:test 74/74, bot 126/126, routing:typecheck + bot:typecheck clean.