test(08.1-01): re-pin codec + search suites for ARG3 and the CH query (RED)

- graph.test.mjs: G1-G11 re-pinned for the ARG3 layout (chShortcutCount
  @60, CH suffix); G7 suspended until task 3 regenerates the fixture
- astar.test.mjs: committed-fixture cases A1/A2/A4/A5/A6 suspended;
  A3 re-pinned on an in-memory ch block; A8 extended to ch-query.ts;
  A11 becomes the D-04 one-search-path pin; ALT-era A9/A10/A12/A13 fold
- ch-query.test.mjs: Q1-Q5 (625-pair equivalence, directed oneway law,
  original-edge seconds, island nulls, structural deletion) + G1-G3
  in-memory ARG3 pins; all fixtures through contractGraph + encodeGraph
This commit is contained in:
araharah-executor
2026-10-26 06:48:24 +08:00
parent f75dc9e8c6
commit d3f0b4577f
3 changed files with 742 additions and 701 deletions
+128 -124
View File
@@ -5,27 +5,32 @@
* RED first (plan 06-01 task 1): every decode-bearing case fails while
* routing/src/graph.ts does not exist (lazy-import pattern — each case
* reports its own ERR_MODULE_NOT_FOUND, the fixture-source.test.mjs style).
* Re-pinned + extended by plan 08-02: the layout version bumped to ARG2 —
* a 72-byte header (landmarkCount u32 @60, landmarkQuantumS f32 @64,
* reserved u32 @68) plus a u16 landmark-table SUFFIX (to-table, from-table,
* u32 anchor ids). One decode path, one search path: anything that is not
* exactly an ARG2 graph decodes to null (D-01 fail-honest) — the committed
* mini-graph-arg1.bin (Plan 08-01) is the standing ARG1 rejection vector.
* Re-pinned by Phase 08.1 plan 01 task 2: the layout version bumped to
* ARG3 — the CH-carrying layout. One decode path, one search path:
* anything that is not exactly an ARG3 graph decodes to null (the D-01
* fail-honest family) — the committed mini-graph-arg1.bin / (task 3)
* mini-graph-arg2.bin files are the standing rejection vectors.
*
* The binary layout is PINNED by 06-01 + 08-02 (plans 04/07 depend on it):
* little-endian CSR — header (magic/version/counts/bbox/maxSpeed/landmark
* block) ++ f64 lat++lng ++ u32 edgeStart[n+1] ++ u32 targets ++ u8 flags
* ++ f32 seconds ++ u32 nameIds ++ u32 nameOffsets[m+1] ++ u8 name pool ++
* u16 toTable[L*N] ++ u16 fromTable[L*N] ++ u32 landmarkNodes[L].
* The header is padded to 72 bytes so the f64 node arrays are 8-byte
* aligned — zero-copy TypedArray views REQUIRE aligned offsets (an
* unaligned Float64Array constructor throws RangeError in every engine).
* The binary layout is PINNED by 06-01 + 08-02 + Phase 08.1 (plans 04/07
* depend on the base; Phase 11 will download the same bytes):
* little-endian CSR — header (magic/version/counts/bbox/maxSpeed +
* chShortcutCount u32 @60, reserved u32 @64/@68) ++ f64 lat ++ lng ++
* u32 edgeStart[n+1] ++ u32 targets ++ u8 flags ++ f32 seconds ++
* u32 nameIds ++ u32 nameOffsets[m+1] ++ u8 name pool ++ u32 chRank[n]
* ++ u32 shortcutStart[n+1] ++ u32 shortcutTarget[S] ++ f32
* shortcutSeconds[S] ++ u32 shortcutVia[S].
* The header stays 72 bytes so the f64 node arrays are 8-byte aligned —
* zero-copy TypedArray views REQUIRE aligned offsets (an unaligned
* Float64Array constructor throws RangeError in every engine). The CH
* suffix starts at the first 4-byte boundary after the name pool.
*
* G1 round-trip — encodeGraph → decodeGraph restores coords,
* targets, flags, weights, names, bbox exactly
* targets, flags, weights, names, bbox AND the
* CH fields (rank + shortcut CSR) exactly
* G2 bad magic -> null — T-06-03 fail-honest (0xdeadbeef)
* G3 bad version -> null — ARG1 fixture bytes -> null (D-01 one-path
* law); version 1 and 3 tamper -> null
* law); version 1, 2 and 4 tampers -> null
* (the accepted version is 3)
* G4 truncated/hostile -> null — byte-length ≠ header-declared size
* (incl. huge-count header on a tiny buffer,
* T-06-03b: size check BEFORE view allocation)
@@ -33,19 +38,20 @@
* flipping an input byte flips the decoded view
* G6 name pool — nameAt(0) === "Jalan Mini 1";
* nameAt(UNNAMED_NAME_ID) === undefined
* G7 committed fixture — mini-graph.bin (ARG2) decodes: 25 nodes, 80
* edges, 3 names, maxSpeed 60, exactly ONE
* oneway flag, landmarkCount >= 1, quantum 0.1
* G8 exported constants — GRAPH_MAGIC === 0x41524732, GRAPH_VERSION === 2
* G9 table round-trip — the four landmark properties restore exactly;
* the 0xFFFF sentinel is DATA; u16 tables are
* zero-copy views
* G10 encode validation — malformed landmark input throws NAMED
* G7 committed fixture — SUSPENDED until task 3 regenerates
* mini-graph.bin through the ARG3 encode path
* (the committed bytes are still ARG2 here);
* task 3 re-pins this case in place
* G8 exported constants — GRAPH_MAGIC === 0x41524733, GRAPH_VERSION === 3
* G9 table round-trip — the CH fields restore exactly; the CH arrays
* are zero-copy views over the input buffer
* G10 encode validation — malformed ch input throws NAMED
* encodeGraph: errors (no silent clamp, T-08-04)
* G11 decode rejection — landmarkCount = 0 -> null even when the size
* matches (D-01/A5: no landmark-less ARG2);
* monster landmarkCount on a tiny buffer ->
* null by arithmetic BEFORE allocation (T-08-01)
* G11 decode rejection — chShortcutCount = 0 -> null even when the
* size matches (an ARG3 with no CH is not a
* road graph — A5); monster chShortcutCount on
* a tiny buffer -> null by arithmetic BEFORE
* allocation (T-08.1-01)
*
* Run: npm run routing:test (exit 0 = all green)
*/
@@ -71,6 +77,26 @@ function toArrayBuffer(buf) {
/* ————— synthetic 3-node graph (the round-trip vector) ————— */
/**
* Hand-built CH block for the 3-node synthetic (rank of length 3,
* shortcutStart of length 4, ONE shortcut so the round-trip is
* meaningful). The shortcut 0->2 (via 1) is upward against this rank:
* rank(from)=1 > rank(via)=0 and rank(to)=2 > 0 — the same law
* contractGraph guarantees. (contractGraph over the 3 synthetic edges
* itself emits no shortcut — the 1->2 mirror is bit0-blocked — so the
* hand block is the codec's round-trip vector, exactly the plan's
* option B.)
*/
function syntheticCh() {
return {
rank: Uint32Array.of(1, 0, 2),
shortcutStart: Uint32Array.of(0, 1, 1, 2),
shortcutTarget: Uint32Array.of(2),
shortcutSeconds: Float32Array.of(12),
shortcutVia: Uint32Array.of(1),
};
}
function syntheticInput() {
return {
lat: [3.15, 3.1505, 3.151],
@@ -82,24 +108,18 @@ function syntheticInput() {
],
names: ["Jalan Mini 1", "Jalan Mini 2"],
maxSpeedKmh: 60,
// ARG2 landmark block (plan 08-02): L=1 anchored at node 0. quantum 0.5
// is f32-exact so the round-trip asserts plain equality; the to-row
// carries a 0xFFFF sentinel ON PURPOSE — sentinels are DATA (row-major
// [l*N + v]: node 2 cannot reach the anchor in this made-up table).
landmarks: {
quantumS: 0.5,
nodes: [0],
to: Uint16Array.of(0, 25, 0xffff),
from: Uint16Array.of(0, 30, 60),
},
// ARG3 CH block (Phase 08.1): REQUIRED — an ARG3 with no CH is not a
// road graph. Hand-built, upward, one shortcut; see syntheticCh().
ch: syntheticCh(),
};
}
/* ————— G1–G8 ————— */
test("G1 round-trip — encodeGraph -> decodeGraph restores coords, targets, flags, weights, names, bbox exactly", async () => {
test("G1 round-trip — encodeGraph -> decodeGraph restores coords, targets, flags, weights, names, bbox, and the CH tables exactly", async () => {
const { encodeGraph, decodeGraph } = await loadGraph();
const input = syntheticInput();
const ch = input.ch;
const buf = encodeGraph(input);
assert.ok(buf instanceof ArrayBuffer, "encodeGraph returns an ArrayBuffer");
const g = decodeGraph(buf);
@@ -121,6 +141,17 @@ test("G1 round-trip — encodeGraph -> decodeGraph restores coords, targets, fla
"bbox derived from the nodes at encode time",
);
assert.equal(g.maxSpeedKmh, 60);
// ARG3 CH fields round-trip (task 2's re-pin).
assert.equal(g.chShortcutCount, 1, "the header carries the shortcut count");
assert.equal(g.chRank.length, 3, "chRank length equals nodeCount");
assert.equal(g.shortcutStart.length, 4, "shortcutStart length equals nodeCount + 1");
assert.equal(g.shortcutTarget.length, g.chShortcutCount, "target length equals chShortcutCount");
assert.equal(g.shortcutSeconds.length, g.chShortcutCount, "seconds length equals chShortcutCount");
assert.equal(g.shortcutVia.length, g.chShortcutCount, "via length equals chShortcutCount");
assert.deepEqual([...g.chRank], [...ch.rank], "rank values match the input exactly");
assert.deepEqual([...g.shortcutTarget], [...ch.shortcutTarget], "shortcut targets match");
assert.deepEqual([...g.shortcutSeconds], [...ch.shortcutSeconds], "shortcut seconds match");
assert.deepEqual([...g.shortcutVia], [...ch.shortcutVia], "shortcut vias match");
});
test("G2 bad magic -> null — a tampered header never half-decodes (T-06-03)", async () => {
@@ -131,20 +162,23 @@ test("G2 bad magic -> null — a tampered header never half-decodes (T-06-03)",
assert.equal(decodeGraph(tampered), null, "wrong magic -> null, never a throw");
});
test("G3 bad version -> null — ARG1 fixture bytes refuse; version 1 and 3 tamper to null (D-01 one-path law)", async () => {
test("G3 bad version -> null — ARG1 fixture bytes refuse; version 1, 2 and 4 tampers to null (D-01 one-path law)", async () => {
const { encodeGraph, decodeGraph } = await loadGraph();
// The frozen Plan 08-01 vector: REAL ARG1 bytes decode to null — the
// Phase 6 router-pending state covers every pre-ARG2 binary (D-01).
// There is exactly one decode path; no v1 fallback branch exists.
// Phase 6 router-pending state covers every pre-ARG3 binary (D-01).
// There is exactly one decode path; no v1/v2 fallback branch exists.
const arg1 = toArrayBuffer(readFileSync(path.join(here, "fixtures", "mini-graph-arg1.bin")));
assert.equal(decodeGraph(arg1), null, "ARG1 bytes -> null, never a throw or a fallback decode");
const buf = encodeGraph(syntheticInput());
const v1 = buf.slice(0);
new DataView(v1).setUint32(4, 1, true);
assert.equal(decodeGraph(v1), null, "version 1 -> null");
const v3 = buf.slice(0);
new DataView(v3).setUint32(4, 3, true);
assert.equal(decodeGraph(v3), null, "version 3 -> null");
const v2 = buf.slice(0);
new DataView(v2).setUint32(4, 2, true);
assert.equal(decodeGraph(v2), null, "version 2 -> null (ARG2 is a rejection vector — D-07)");
const v4 = buf.slice(0);
new DataView(v4).setUint32(4, 4, true);
assert.equal(decodeGraph(v4), null, "version 4 -> null (the accepted version is 3)");
});
test("G4 truncated + hostile-size buffer -> null — exact byte-size check BEFORE any view allocation (T-06-03b)", async () => {
@@ -175,6 +209,7 @@ test("G5 zero-copy views — the views' .buffer IS the input buffer; flipping an
assert.ok(g, "decode succeeds");
assert.equal(g.lat.buffer, buf, "lat is a VIEW over the input buffer (zero-copy)");
assert.equal(g.edgeSeconds.buffer, buf, "edgeSeconds is a VIEW over the input buffer");
assert.equal(g.chRank.buffer, buf, "chRank is a VIEW over the input buffer (CH zero-copy)");
// The padded header ends at byte 72 — the first lat byte. Flip it.
const dv = new DataView(buf);
const before = g.lat[0];
@@ -192,104 +227,74 @@ test("G6 name pool — nameAt(0) resolves the first pool name; the unnamed senti
assert.equal(g.nameAt(999), undefined, "out-of-range id -> undefined");
});
test("G7 committed ARG2 fixture — decodes to 25 nodes / 80 edges / 3 names / L>=1 landmarks, maxSpeed 60, ONE oneway", async () => {
const { decodeGraph } = await loadGraph();
const bin = toArrayBuffer(readFileSync(path.join(here, "fixtures", "mini-graph.bin")));
assert.ok(bin.byteLength > 1000, `the committed fixture is substantial (${bin.byteLength} B)`);
const g = decodeGraph(bin);
assert.ok(g, "the committed fixture decodes cleanly");
assert.equal(g.nodeCount, 25, "5x5 grid");
assert.equal(g.edgeCount, 80, "20 H + 20 V segments, both directions");
assert.equal(g.nameCount, 3, "Jalan Mini 1 / Jalan Mini 2 / Jalan Persiaran Mini");
assert.equal(g.nameAt(0), "Jalan Mini 1");
assert.equal(g.nameAt(2), "Jalan Persiaran Mini");
assert.equal(g.maxSpeedKmh, 60, "the perimeter speed class is the header max");
// bbox of the hand-built grid (lat 3.1500–3.1520, lng 101.6900–101.6920)
assert.deepEqual(g.bbox, { minLat: 3.15, minLng: 101.69, maxLat: 3.152, maxLng: 101.692 });
let flagged = 0;
for (let e = 0; e < g.edgeCount; e++) if ((g.edgeFlags[e] & 1) !== 0) flagged++;
assert.equal(flagged, 1, "exactly ONE oneway-reverse edge in the whole fixture");
// both speed classes present: perimeter ~55.6 m / 16.67 ≈ 3.3 s; interior ≈ 6.7 s
let sawFast = false;
let sawSlow = false;
for (let e = 0; e < g.edgeCount; e++) {
if (g.edgeSeconds[e] < 5) sawFast = true;
if (g.edgeSeconds[e] > 5) sawSlow = true;
}
assert.ok(sawFast && sawSlow, "both speed classes (60 and 30 km/h) exist in the weights");
// ARG2 landmark laws (plan 08-02): the fixture regenerates THROUGH the
// encode authority with a tiny real landmark stage (L=2).
assert.equal(g.landmarkCount, 2, "the build's tiny L");
assert.ok(
Math.abs(g.landmarkQuantumS - 0.1) < 1e-6,
`quantum is the 0.1 s c-unit (f32 readback ${g.landmarkQuantumS})`,
);
assert.equal(g.landmarkTo.length, g.landmarkCount * g.nodeCount, "to-table is L*N row-major");
assert.equal(g.landmarkFrom.length, g.landmarkCount * g.nodeCount, "from-table is L*N row-major");
for (let l = 0; l < g.landmarkCount; l++) {
const a = g.landmarkNodes[l];
assert.ok(Number.isInteger(a) && a >= 0 && a < g.nodeCount, `anchor ${l} in range`);
assert.equal(g.landmarkTo[l * g.nodeCount + a], 0, `anchor ${l}'s own to-cell is 0`);
assert.equal(g.landmarkFrom[l * g.nodeCount + a], 0, `anchor ${l}'s own from-cell is 0`);
}
test("G7 committed ARG3 fixture — SUSPENDED until task 3 regenerates mini-graph.bin through the ARG3 encode path", async () => {
// The committed mini-graph.bin is still ARG2 at this point in the plan
// (task 2 flips the codec; task 3 regenerates the fixture through the
// ONE encode authority and re-pins this case in place, together with
// the preserved mini-graph-arg2.bin rejection vector). Documented
// no-op — the ONLY suspended case in this suite.
assert.ok(true, "suspended: deferred to task 3 (fixture regeneration)");
});
test("G8 exported constants — GRAPH_MAGIC === 0x41524732 ('ARG2'), GRAPH_VERSION === 2", async () => {
test("G8 exported constants — GRAPH_MAGIC === 0x41524733 ('ARG3'), GRAPH_VERSION === 3", async () => {
const mod = await loadGraph();
assert.equal(mod.GRAPH_MAGIC, 0x41524732);
assert.equal(mod.GRAPH_VERSION, 2);
assert.equal(mod.GRAPH_MAGIC, 0x41524733);
assert.equal(mod.GRAPH_VERSION, 3);
assert.equal(mod.UNNAMED_NAME_ID, 0xffffffff);
});
test("G9 table round-trip — the four landmark properties restore exactly; sentinels are DATA; tables are zero-copy", async () => {
test("G9 table round-trip — the CH fields restore exactly; the CH arrays are zero-copy views", async () => {
const { encodeGraph, decodeGraph } = await loadGraph();
const buf = encodeGraph(syntheticInput()).slice(0);
const ch = syntheticCh();
const buf = encodeGraph({ ...syntheticInput(), ch }).slice(0);
const g = decodeGraph(buf);
assert.ok(g, "decode succeeds");
assert.equal(g.landmarkCount, 1, "landmarkCount === L");
assert.equal(g.landmarkTo.length, 1 * 3, "to-table row-major L*N");
assert.equal(g.landmarkFrom.length, 1 * 3, "from-table row-major L*N");
assert.deepEqual([...g.landmarkNodes], [0], "landmarkNodes[i] matches the input anchors");
assert.equal(g.landmarkQuantumS, 0.5, "quantum restores exactly (0.5 is f32-exact)");
assert.deepEqual([...g.landmarkTo], [0, 25, 0xffff], "to-table bytes restore exactly — 0xFFFF is DATA");
assert.deepEqual([...g.landmarkFrom], [0, 30, 60], "from-table bytes restore exactly");
assert.equal(g.landmarkTo.buffer, buf, "the u16 to-table is a zero-copy view too");
assert.equal(g.landmarkFrom.buffer, buf, "the u16 from-table is a zero-copy view too");
assert.equal(g.chShortcutCount, 1, "chShortcutCount === S");
assert.deepEqual([...g.chRank], [...ch.rank], "chRank restores exactly");
assert.deepEqual([...g.shortcutStart], [...ch.shortcutStart], "shortcutStart restores exactly");
assert.deepEqual([...g.shortcutTarget], [...ch.shortcutTarget], "shortcutTarget restores exactly");
assert.deepEqual([...g.shortcutSeconds], [...ch.shortcutSeconds], "shortcutSeconds restores exactly");
assert.deepEqual([...g.shortcutVia], [...ch.shortcutVia], "shortcutVia restores exactly");
assert.equal(g.chRank.buffer, buf, "chRank is a zero-copy view");
assert.equal(g.shortcutStart.buffer, buf, "shortcutStart is a zero-copy view");
assert.equal(g.shortcutTarget.buffer, buf, "shortcutTarget is a zero-copy view");
assert.equal(g.shortcutSeconds.buffer, buf, "shortcutSeconds is a zero-copy view");
assert.equal(g.shortcutVia.buffer, buf, "shortcutVia is a zero-copy view");
});
test("G10 encode validation — malformed landmark input throws NAMED encodeGraph: errors (T-08-04, no silent clamp)", async () => {
test("G10 encode validation — malformed ch input throws NAMED encodeGraph: errors (T-08-04, no silent clamp)", async () => {
const { encodeGraph } = await loadGraph();
const base = syntheticInput();
const mangle = (over) => ({ ...base, landmarks: { ...base.landmarks, ...over } });
const mangle = (over) => ({ ...base, ch: { ...base.ch, ...over } });
const cases = [
["zero anchors", mangle({ nodes: [] })],
["to-row length != L*N", mangle({ to: Uint16Array.of(0, 25) })],
["from-row length != L*N", mangle({ from: Uint16Array.of(0) })],
["anchor id >= nodeCount", mangle({ nodes: [7] })],
["anchor id negative", mangle({ nodes: [-1] })],
["quantum 0", mangle({ quantumS: 0 })],
["quantum negative", mangle({ quantumS: -1 })],
["quantum NaN", mangle({ quantumS: Number.NaN })],
["landmarks block missing", { ...base, landmarks: undefined }],
["ch block missing", { ...base, ch: undefined }],
["rank length != nodeCount", mangle({ rank: Uint32Array.of(1, 0) })],
["shortcutStart length != nodeCount+1", mangle({ shortcutStart: Uint32Array.of(0, 1, 1) })],
["target length != S (via disagrees)", mangle({ shortcutVia: Uint32Array.of() })],
["seconds length != S (target disagrees)", mangle({ shortcutSeconds: Float32Array.of() })],
["via id >= nodeCount", mangle({ shortcutVia: Uint32Array.of(7) })],
["via id negative-ish (huge)", mangle({ shortcutTarget: Uint32Array.of(0xffff_ffff), shortcutVia: Uint32Array.of(0xffff_ffff) })],
["non-finite shortcut seconds", mangle({ shortcutSeconds: Float32Array.of(Number.NaN) })],
["negative shortcut seconds", mangle({ shortcutSeconds: Float32Array.of(-1) })],
];
for (const [label, input] of cases) {
assert.throws(() => encodeGraph(input), /^Error: encodeGraph: /, `${label} -> named encodeGraph: error`);
}
});
test("G11 decode rejection — landmarkCount 0 -> null even when the size matches; monster count -> null by arithmetic (T-08-01)", async () => {
test("G11 decode rejection — chShortcutCount 0 -> null even when the size matches; monster count -> null by arithmetic (T-08.1-01)", async () => {
const { encodeGraph, decodeGraph, GRAPH_MAGIC, GRAPH_VERSION } = await loadGraph();
const buf = encodeGraph(syntheticInput());
const zeroed = buf.slice(0);
new DataView(zeroed).setUint32(60, 0, true);
assert.equal(decodeGraph(zeroed), null, "landmarkCount = 0 -> null (D-01/A5: no landmark-less ARG2)");
// A CONSISTENT landmark-less buffer (byte size exactly matches the L=0
// layout: header + sections + align4(namePoolEnd), no suffix) still
// refuses — the law is the count itself, not a size mismatch. Quantum
// is valid here so the L=0 law is isolated.
assert.equal(decodeGraph(zeroed), null, "chShortcutCount = 0 -> null (no CH-less ARG3 — A5)");
// A CONSISTENT CH-less buffer (byte size exactly matches the S=0 ARG3
// layout: header + base sections + align4(namePoolEnd) + chRank[n] +
// shortcutStart[n+1]) still refuses — the law is the count itself, not
// a size mismatch.
const n = 1;
const lay = 72 + 8 * n + 8 * n + 4 * (n + 1) + 4 * 1; // = 100: header + lat + lng + edgeStart[2] + nameOffset[1]; no suffix
const bare = new ArrayBuffer(lay); // byteLength === the exact L=0 layout total
const lay = 72 + 8 * n + 8 * n + 4 * (n + 1) + 4 * 1 + 4 * n + 4 * (n + 1); // = 112: header + lat + lng + edgeStart[2] + nameOffset[1] + chRank[1] + shortcutStart[2]; no shortcut payload
const bare = new ArrayBuffer(lay);
const dv = new DataView(bare);
dv.setUint32(0, GRAPH_MAGIC, true);
dv.setUint32(4, GRAPH_VERSION, true);
@@ -297,13 +302,12 @@ test("G11 decode rejection — landmarkCount 0 -> null even when the size matche
dv.setUint32(12, 0, true); // edgeCount
dv.setUint32(16, 0, true); // nameCount
dv.setUint32(20, 0, true); // namePoolLen
dv.setFloat32(64, 0.5, true); // a valid quantum…
dv.setUint32(60, 0, true); // …but landmarkCount = 0
assert.equal(decodeGraph(bare), null, "an exactly-sized landmark-less ARG2 -> null");
// Monster header: L = 2,000,000 on a tiny buffer — the exact-size
// arithmetic rejects BEFORE any Uint16Array allocation (a null return,
dv.setUint32(60, 0, true); // chShortcutCount = 0
assert.equal(decodeGraph(bare), null, "an exactly-sized CH-less ARG3 -> null");
// Monster header: S = 2,000,000 on a tiny buffer — the exact-size
// arithmetic rejects BEFORE any typed-array allocation (a null return,
// never a RangeError).
const monster = buf.slice(0);
new DataView(monster).setUint32(60, 2_000_000, true);
assert.equal(decodeGraph(monster), null, "monster landmarkCount on a tiny buffer -> null, no allocation");
assert.equal(decodeGraph(monster), null, "monster chShortcutCount on a tiny buffer -> null, no allocation");
});