- SUMMARY: CSR codec + snapping + typed-array A* (57/57 routing tests, 625/625 Dijkstra equivalence), 2 documented deviations (header alignment padding, compass turn convention), TDD gate sequence intact
16 KiB
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 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-turn-by-turn-guidance-live-driving | 01 | routing |
|
|
|
|
|
|
|
|
|
11min | 2026-10-24 |
Phase 6 Plan 1: Engine A Data Plane — CSR Graph Codec + Snapping + Typed-Array A* Summary
Engine A's pure data plane is REAL and proven: a pinned little-endian CSR binary codec that round-trips and fails honest (null) on every malformed-header class, cos(lat)-correct GPS-fix snapping over a WeakMap-memoized grid, and a typed-array A* proven equal to brute-force Dijkstra on all 625 mini-graph pairs — steps carry turn kinds, street names, and polyline6 geometry, 57/57 routing tests green.
Performance
- Duration: 11 min
- Started: 2026-10-24T04:03:16Z
- Completed: 2026-10-24T04:15:09Z
- Tasks: 3/3 (each RED→GREEN)
- Files: 10 (8 created, 2 modified)
Accomplishments
- The CSR codec exists and is pinned (D-01):
decodeGraph/encodeGraphover the little-endian layout (header magic/version/counts/bbox/maxSpeed ++ f64 coords ++ CSR offsets ++ edges/flags/weights ++ name table). Round-trip exact (G1); wrong magic/version/truncated/hostile-huge-count headers ALL resolve null — validated before any view allocation (T-06-03/T-06-03b, G2–G4); decoded views are zero-copy over the input buffer (G5 identity + mutation proof); the name pool resolvesnameAt(0) === "Jalan Mini 1"and the unnamed sentinel → undefined (G6). - The committed ground truth (Wave 0 gap closed):
mini-graph.bin(1,668 B) + its builder script — a hand-designed 5×5 grid near KL center (3.1500–3.1520 N, 101.6900–101.6920 E), 25 nodes, 80 directed edges, three named streets, exactly ONE oneway pair, two speed classes (60/30 km/h). G7 pins its invariants; snap and astar suites consume it as the shared fixture. - Snapping is the server authority (D-05):
snapToGraphreturns the closer endpoint of the best projected segment withdistanceM(haversine) and that segment'snameId(null for unnamed) — the additive field plan 07's cron street-hop consumes. Bbox+margin misses resolve null (D-03 router-pending); oneway direction ignored; the ~0.002° grid memoizes per RoadGraph in a WeakMap (S6 build-counter + structural greps prove WeakMap +Math.cos). - A* is provably optimal (GUID-02's engine): the exhaustive 625-pair sweep matches brute-force Dijkstra cost exactly (A1); the oneway pair routes the long way against the flagged edge and direct with it (A2); unreachable → null (A3). The hot loop is allocation-free — binary heap of indices over preallocated typed arrays, typed-array g/closed/came-from, on-demand cached heuristic,
new Map(grep = 0 (A8). - Turn steps speak the banner's language (GUID-01 upstream):
deriveStepssplits at non-straight bearings or street-name changes, emitsdepart-first/arrive-last steps with kind/name/distanceM/polyline6 (A4/A5); step distances sum to the route distance.classifyTurnis exported as THE turn authority with pinned thresholds (25/60/150) — plan 07's cron imports this exact classifier. - The ladder record moved (D-01/D-02): index.ts now documents backend #2 as "engine-a" (supersedes the phase-5 "wasm-router" note), records the Valhalla-port-infeasible + geojson-path-finder-rejected decisions, and names engine B the post-v1 evolution.
createHttpRoutingSourcedeliberately absent (A7 pins it for plan 04). - Suite state: routing 57 passed / 0 failed (was 35 — 22 new cases, all existing untouched-green);
routing:typecheck,web:typecheck,bot:typecheckclean; web (76) and bot (74) suites unaffected by the additiveRouteStep.kind.
Task Commits
Each task followed RED→GREEN with atomic commits:
- task 1: mini-graph fixture + CSR graph codec
5374094test(06-01): add failing CSR graph codec suite G1-G8 (RED)1ce3590feat(06-01): CSR graph codec + committed mini-graph fixture + RouteStep.kind (GREEN)
- task 2: GPS-fix snapping
72703d2test(06-01): add failing GPS-fix snap suite S1-S6 (RED)2e73068feat(06-01): point-to-edge GPS-fix snapping with memoized grid index (GREEN)
- task 3: typed-array A* + step derivation
6adfd86test(06-01): add failing A* equivalence + step-derivation suite A1-A8 (RED)e34c229feat(06-01): typed-array A* + turn classifier + step derivation + public exports (GREEN)
Files Created/Modified
routing/src/graph.ts— CSR codec; named layout constants; fail-honest decode; encode with input validation + stable CSR grouping; nameAt over the UTF-8 poolrouting/src/snap.ts— snapToGraph; SnapResult {node, distanceM, nameId}; SNAP_BBOX_MARGIN_DEG = 0.01; WeakMap grid; gridBuildCount instrumentationrouting/src/astar.ts— routeAstar (NodeHeap, lazy deletion); classifyTurn + TURN_* constants; GuidanceTurnKind = NonNullable<RouteStep["kind"]>; deriveStepsrouting/src/types.ts— RouteStep.kind (additive optional, 8 kinds, D-10 note) — no other line touchedrouting/src/index.ts— engine-A named exports + D-01/D-02 ladder note in the headerrouting/test/graph.test.mjs— G1–G8;routing/test/snap.test.mjs— S1–S6;routing/test/astar.test.mjs— A1–A8 (incl. the 625-pair equivalence sweep + test-local Dijkstra)routing/test/fixtures/build-mini-graph.mjs+mini-graph.bin— the committed ground truth + its regeneration script
Decisions Made
See key-decisions: header alignment padding, compass turn convention, oneway flag semantics, deriveSteps throw-on-malformed-path, internal grid instrumentation export.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking spec conflict] CSR header padded to 64 bytes; f32 section 4-byte-aligned
- Found during: task 1 GREEN implementation
- Issue: the pinned layout's node arrays start at byte 60 (header is 6×u32 + 4×f64 + f32 = 60 B).
new Float64Array(buffer, 60, n)throws RangeError in every engine — typed-array views require element-size-aligned offsets. The plan simultaneously mandates "zero-copy TypedArray views" (must_have truth + the G5-style mutation acceptance + a RoadGraph interface typed in Float64Array/Float32Array), which is UNSATISFIABLE on the literal offsets; the f32 edgeSeconds array is likewise unaligned whenever edgeCount ≢ 0 (mod 4) because of the u8 flags array. - Fix: 4 pad bytes after maxSpeedKmh (header 60 → 64 B, f64 arrays 8-byte aligned) + align4 padding before edgeSeconds. Field order, content, and endianness are unchanged; the layout is encapsulated behind encodeGraph, so plan 04's build script and every other consumer inherit it automatically. Both test files pin the layout the implementation ships (G5 mutates byte 64).
- Files modified: routing/src/graph.ts (documented in the module header)
- Commit:
1ce3590
2. [Rule 1 - Spec inconsistency] classifyTurn(0, 40) resolves "slight-right", not the plan's "slight-left"
- Found during: task 3 RED authoring
- Issue: the plan's behavior list pins classifyTurn(0, -90) → "left" and classifyTurn(0, 90) → "right" (compass convention: negative delta = left), but also classifyTurn(0, 40) → "slight-left" — contradictory under ANY sign convention.
- Fix: implemented the convention 3 of the plan's own 4 cases establish (compass bearings clockwise; negative = left) and pinned (0, 40) → "slight-right" + (0, -40) → "slight-left" in A6 with a comment naming the conflict.
- Files modified: routing/src/astar.ts, routing/test/astar.test.mjs
- Commit:
e34c229
Test-side fixes during GREEN (not deviations — executor test bugs caught by verification)
- S4 asserted "row-4 node" over ids 10..14 (that's row 2); the implementation had snapped correctly to node 21 (r4c1). Fixed the assertion range to 20..24 (commit
2e73068). - A4 expected ["depart","right","left","arrive"] from a mislabeled geography (increasing row = increasing lat = NORTH, so east→north is a LEFT). The implemented bearings were correct; fixed the expectation to ["depart","left","right","arrive"] (commit
e34c229). - build-mini-graph.mjs initially accumulated
3.15 + r*0.0005(float error vs the 3.152 literal); switched to the 1e-6 integer grid ((3150000 + r*500)/1e6), the codec's own grid law (commit1ce3590).
Judgment calls within plan laws (not deviations)
- Oneway flag semantics (see key-decisions) — the coherent reading of "respected by A*, ignored by snapping".
- Float64 heap keys instead of the plan's "f32 key[]" hint — numerical safety at zero cost; no gate keys on it.
- snap.ts documents its 3×3-neighborhood candidacy limit honestly (a segment whose endpoints both sit >1 cell away is not a candidate; miss → null, never a wrong snap) — plan 04's KL build densifies at intersections, well inside the window.
Known Stubs
None. All exports are fully wired pure implementations; the mini-graph fixture is intentional committed test data (the plan's declared ground truth), not a stub.
Threat Surface Scan
No new surface beyond the plan's <threat_model>: no network endpoints, no auth paths, no storage, no logging — the C4 banned-token gate (which now scans the three new modules automatically) stays green, and the task-level grep gates (zero expirationTtl|console.|fetch( in graph.ts) pass. T-06-03 (tampering → fail-honest null) is implemented and gated by G2–G4; T-06-03b (size check before allocation) is implemented in decodeGraph and gated by G4's hostile-huge-count case; T-06-05 (pure modules) holds by construction. No threat flags.
Issues Encountered
- Node lives under nvm; every shell command sourced
~/.nvm/nvm.shfirst (all prior plans' precedent). No project impact.
User Setup Required
None — pure modules, committed fixture, zero new dependencies.
Next Phase Readiness
- 06-02 (replays): route snapshots can embed
deriveStepsoutput directly — banner/list consume kind/name/distanceM/geometry verbatim. - 06-04 (graph build): imports
encodeGraph(the layout's single authority — the alignment padding travels with it); the mini-graph builder doubles as its shape reference. - 06-05 (http-source + worker-router):
decodeGraph+snapToGraph+routeAstarare the entire server route path; R2 ArrayBuffer → decodeGraph → (null = router-pending per D-03) → snap origin → A* → JSON steps. - 06-07 (cron): imports
classifyTurn+ TURN_* constants — one turn authority across foreground and cron surfaces.
Phase: 06-turn-by-turn-guidance-live-driving Completed: 2026-10-24
Self-Check: PASSED
- All 8 created files verified present on disk (graph/snap/astar.ts, 3 test suites, builder + binary).
- All 6 plan commits verified in git log:
5374094,1ce3590,72703d2,2e73068,6adfd86,e34c229(RED→GREEN per task; TDD gate sequence intact). npm run routing:test: 57 passed, 0 failed;npm run routing:typecheckexit 0; web + bot typechecks and suites unaffected.- A* ≡ Dijkstra verified exhaustively (625/625 pairs, A1).
- STATE.md and ROADMAP.md untouched (orchestrator-owned writes); only this SUMMARY force-added.