fix(08): WR-04 — quantized weight validated at the i32 boundary before the Int32Array store

- new named constant LANDMARK_I32_WEIGHT_CEILING (0x7fffffff) in
  landmark.ts: a seconds/quantumS quotient at or past 2^31 would wrap
  NEGATIVE on the Int32Array store, silently bypassing the
  LANDMARK_MAX_QUANTA throw in dijkstraPass (a negative contribution
  never exceeds it) and — at a -1 wrap — becoming the 0xFFFF sentinel;
  prepare() now fails loud at the table-build entry, naming the edge,
  the ceiling and the fix (raise landmarkQuantumS)
- 'nothing is clamped, ever' now holds at the storage boundary;
  validation is per-edge at build entry — the search hot path stays
  allocation-free and validation-free
- L5b regression vector: 3e10 s edge (3e11 quanta) throws in both
  landmarkTables and farthestPointAnchors; just-under-ceiling
  (2147483646) still fails via the documented 65000 law; ordinary
  weights untouched
This commit is contained in:
araharah-executor
2026-10-25 22:35:47 +08:00
parent 95399aec8a
commit e77bbe9f72
2 changed files with 76 additions and 1 deletions
+52
View File
@@ -197,6 +197,58 @@ test("L5 overflow throws — any label > 65000 quanta fails loud, naming the fix
);
});
test("L5b i32 wrap throws — an edge quantizing to >= 2^31 fails at prepare, before the Int32Array store", async () => {
const { landmarkTables, farthestPointAnchors } = await loadLandmark();
// seconds 3e10 at the 0.1 s quantum = 3e11 quanta — an absurd but FINITE
// positive weight. Stored unchecked it would wrap NEGATIVE in the
// Int32Array (-64771072 for 3e10), silently bypassing the LANDMARK_MAX_QUANTA
// throw (a negative contribution never exceeds it) and poisoning the
// tables. The wrap must be caught at the table-build entry instead.
const input = {
nodeCount: 3,
edges: [
{ from: 0, to: 1, seconds: 3e10, flags: 0 }, // floor(3e10/0.1) = 3e11 quanta >= 2^31
{ from: 1, to: 0, seconds: 10, flags: 0 },
{ from: 1, to: 2, seconds: 10, flags: 0 },
{ from: 2, to: 1, seconds: 10, flags: 0 },
],
quantumS: 0.1,
};
assert.throws(
() => landmarkTables(input, Uint32Array.of(0)),
/i32 weight ceiling 2147483647 \(raise landmarkQuantumS\)/,
"landmarkTables throws at the storage boundary, naming the edge, the ceiling and the fix",
);
assert.throws(
() => farthestPointAnchors(input, 1),
/i32 weight ceiling 2147483647/,
"the anchor-selection entry shares the same prepare-time law",
);
// The ceiling itself stays inside the legal domain: 2147483646 quanta on
// one edge does NOT throw here (the dijkstraPass 65000 ceiling is the law
// that fires later — prepare only rejects the i32 WRAP class).
const justUnder = {
nodeCount: 2,
edges: [
{ from: 0, to: 1, seconds: 214748364.6, flags: 0 }, // floor(/0.1) = 2147483646 < 2^31
{ from: 1, to: 0, seconds: 10, flags: 0 },
],
quantumS: 0.1,
};
assert.throws(
() => landmarkTables(justUnder, Uint32Array.of(0)),
/exceeds 65000/,
"just-under-ceiling still fails — via the documented 65000 law, never silently",
);
assert.doesNotThrow(
() => landmarkTables(
{ nodeCount: 2, edges: [{ from: 0, to: 1, seconds: 25.5, flags: 0 }, { from: 1, to: 0, seconds: 10, flags: 0 }], quantumS: 0.1 },
Uint32Array.of(0),
),
"ordinary weights are untouched by the new check",
);
});
test("L6 5x5 grid shape — anchors [0,18]; farthest 297 quanta; coverage 1.0", async () => {
const { farthestPointAnchors, landmarkTables, landmarkCoverage } = await loadLandmark();
const input = await gridInput();