Files
arah2/routing/src/types.ts
T
araharah-executor 1ce35907d5 feat(06-01): CSR graph codec + committed mini-graph fixture + RouteStep.kind (GREEN)
- graph.ts: decodeGraph/encodeGraph over the pinned little-endian CSR
  layout; header magic+version+exact byte-size validation BEFORE any view
  allocation (T-06-03/T-06-03b fail-honest null); zero-copy typed-array
  views; street-name pool with UNNAMED_NAME_ID sentinel
- header padded 60 -> 64 bytes so f64 views are 8-byte aligned — unaligned
  Float64Array throws RangeError, making plan-literal offsets incompatible
  with the plan's own zero-copy law (documented deviation; field
  order/content/endian unchanged; consumers inherit via encodeGraph)
- build-mini-graph.mjs + committed mini-graph.bin: hand-built 5x5 grid,
  25 nodes, 80 directed edges, 3 named streets, exactly ONE oneway pair,
  two speed classes (60/30 km/h) — the A* ground truth (G7 pins it)
- types.ts: RouteStep gains optional kind (8 turn kinds) — Engine A
  populates it (06-01); fixture-world steps stay empty arrays (D-10)
2026-10-24 12:07:17 +08:00

146 lines
5.0 KiB
TypeScript

/*
* types.ts — the routing contracts (D-01, D-03, D-05, D-07, D-11).
* ----------------------------------------------------------------
* RoutePoint / RouteRequest / RouteResult / RoutingSource are the seam
* every backend implements: the build-time fixture corpus (backend #1,
* "fixture", this phase), the client-side WASM router (backend #2,
* "wasm-router" — OSS track, post-v1; origin+destination never touch a
* server), and Valhalla-on-VPS (backend #3, documented provision path,
* not shipped). The ladder is DOCUMENTED, not switchable: no env read,
* no config read, no dynamic backend registry (T-05-04) — the import
* site constructs the backend in code (plan 05-04).
*
* THE NULL CONTRACT IS LOAD-BEARING (D-03): route() resolving to NULL is
* the honest "router pending" state — an unknown destination, or an
* origin outside the fixture corpus's tolerance. Never a blank map,
* never a fake route, never a thrown error for a normal miss.
*
* No logging, no storage, no network (D-05/D-12/D-15 analog): request
* coordinates never leave the caller's frame.
*/
/** A WGS84 point. */
export interface RoutePoint {
lat: number;
lng: number;
}
/** A routing request. The origin is the live fix (memory only — GEO-03/D-07). */
export interface RouteRequest {
origin: RoutePoint;
/** The gazetteer id resolved by the Phase 4 destination resolver. */
destinationId: string;
}
/** Distance/duration summary. Distances in meters, durations in seconds. */
export interface RouteSummary {
distanceM: number;
durationS: number;
}
/**
* A maneuver step — DAY-ONE FIELD (D-07, GUID-05/06 upstream obligation).
* Fixture-world steps are ALWAYS empty arrays (D-10 defers instructions to
* Phase 6): an empty array is honest "not wired"; a fabricated step list
* would not be. Real step geometry arrives with backends #2/#3.
*/
export interface RouteStep {
distanceM?: number;
durationS?: number;
name?: string;
/** polyline6-encoded step geometry (backend #2/#3 populate this). */
geometry?: string;
/**
* Maneuver kind — Engine A populates this (06-01); fixture-world steps
* stay empty arrays (D-10).
*/
kind?:
| "depart"
| "straight"
| "left"
| "right"
| "slight-left"
| "slight-right"
| "uturn"
| "arrive";
}
/**
* Decoded geometry + the snapped-progress support field — DAY-ONE FIELD
* (D-07, GUID-05/06 upstream obligation): Phase 6's guidance walks a live
* fix along cumulativeDistanceM instead of re-deriving it per fix.
*/
export interface RouteGeometry {
points: RoutePoint[];
/** Same length as points; [0] === 0; non-decreasing. Meters along the line. */
cumulativeDistanceM: number[];
}
/** One routable alternative. options[0] of a result is the BEST route. */
export interface RouteOption {
geometry: RouteGeometry;
summary: RouteSummary;
steps: RouteStep[];
}
export interface RouteResult {
destinationId: string;
origin: RoutePoint;
/** 1..3 options, index 0 = best (order preserved from the backend). */
options: RouteOption[];
/** === the RoutingSource name that produced it. */
backend: string;
}
/**
* The pluggable routing seam (D-01). Backends: "fixture" (this phase) →
* "wasm-router" (OSS track, backend #2 — client-side, origin+destination
* never touch a server) → "valhalla" (VPS, backend #3). Backends #2/#3 do
* NOT ship in this phase — the interface exists so they slot in without a
* client rewrite, exactly parallel to SearchSource's ladder (D-03/D-04).
*
* THE NULL CONTRACT IS LOAD-BEARING (D-03): route() resolving to NULL is
* the honest "router pending" state — an unknown destination, or an origin
* outside the fixture corpus's tolerance. Never a blank map, never a fake
* route, never a thrown error for a normal miss.
*
* No logging, no storage, no network (D-05/D-12/D-15 analog): request
* coordinates never leave the caller's frame.
*/
export interface RoutingSource {
readonly name: string;
route(req: RouteRequest): Promise<RouteResult | null>;
}
/** One committed fixture route — the polyline6 string stored VERBATIM
* from the build-time OSRM demo response (RESEARCH Finding 1). */
export interface FixtureRoute {
/** polyline6, stored VERBATIM from the OSRM demo response. */
geometry: string;
distanceM: number;
durationS: number;
}
/** One golden origin→destination pair of the fixture corpus (D-02). */
export interface FixturePair {
/** "<origin-id>-to-<dest-id>", slug charset — also the ?fixpair= knob value. */
pairId: string;
origin: { lat: number; lng: number; label: string };
destinationId: string;
/** Best-first, 1..3 entries (OSRM demo returns best-first; preserved). */
routes: FixtureRoute[];
}
/** The committed dataset envelope (route-fixtures.json) — the ODbL
* attribution travels INSIDE the file, the gazetteer's three-surfaces
* pattern (T-05-06). Tests inject synthetic values of this shape. */
export interface RouteFixturesFile {
_license: string;
_attribution: string;
_notice: string;
_source: string;
version: number;
originToleranceM: number;
pairs: FixturePair[];
}