Skip to content

Commit 9c5d087

Browse files
wormeymanclaude
andcommitted
test(cliffs): pin the SHAPE of the orientation residual (#84)
cliffOrientationOracle.spec.ts counts the residual and bounds it. This pins what it looks like, because the shape is the lead and a change in shape is a change in cause even when the count holds. Measured after #83 (multisample grid), #86 (lava rejection) and #90 (raw collision box): - 37 of 1531 matched cells, and all 37 differ in EXACTLY ONE edge. Not one two-edge difference survives; before #83 the dominant mode was two edges (125 of 175), a whole corner on the wrong side of a band. - Every one is an OVER-detection: the game reports a -to-none orientation and the port reports a crossing on that edge, never the reverse. - Spread over all four edges (L11/R6/T7/B13) and all three regions (7/26/4), so it is not a directional off-by-one. Two candidate causes are already eliminated, which is why the shape is worth pinning rather than re-deriving next session: - crossesCliff is EXACT. Disassembled at 0x10160c914 under 2.1.12 (the VA in cliffs-NOTES.md had moved); cliffPlacement.ts reproduces it line for line including the a<0||b<0 early-out, the boundary<e0 check, and the strict >0.5 gate and strict crossing comparisons. No >=-vs-> slip exists to find. - cliffiness_basic is EXONERATED. Substituting the game's own corner cliffiness leaves the count at exactly 37/1531. So the residual is in the grid-4 cliff-elevation field - the one input in the chain with no direct per-corner oracle. A single-edge, strictly one-directional over-detection is what a small positive field offset looks like. The direction assertion is the load-bearing one: if under-detections ever appear, the cause has changed and that reading is dead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt
1 parent 64b5b79 commit 9c5d087

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { describe, expect, it } from "vite-plus/test";
2+
3+
import vulcanus from "./fixtures/oracle-vulcanus-cliff-entities.seed123456.json";
4+
import { CLIFF_ORIENTATION_NAMES, cliffOrientationForCode } from "../src/noise/cliffs/cliffCatalog";
5+
import { makeCliffPlacementFromFields } from "../src/noise/cliffs/cliffPlacement";
6+
import {
7+
VULCANUS_CLIFF_ELEVATION_0,
8+
VULCANUS_CLIFF_ELEVATION_INTERVAL,
9+
VULCANUS_CLIFF_SMOOTHING,
10+
makeVulcanusCliffFields,
11+
} from "../src/noise/cliffs/vulcanusCliffFields";
12+
import { withCtxDefaults } from "../src/noise/eval/ctx";
13+
14+
const key = (p: { x: number; y: number }): string => `${String(p.x)},${String(p.y)}`;
15+
16+
/** code = (enc(L)<<6)|(enc(R)<<4)|(enc(T)<<2)|enc(B); enc: 0->0, +1->1, -1->3. */
17+
const edgesOf = (code: number): readonly number[] => [
18+
(code >> 6) & 3,
19+
(code >> 4) & 3,
20+
(code >> 2) & 3,
21+
code & 3,
22+
];
23+
24+
/**
25+
* **The SHAPE of the Vulcanus orientation residual** (issue #84).
26+
*
27+
* `cliffOrientationOracle.spec.ts` counts the residual and bounds it. This file
28+
* pins what it looks like, because the shape is the lead and a change in shape
29+
* is a change in cause even if the count holds.
30+
*
31+
* Measured 2026-08-02, after #83 (multisample grid), #86 (lava rejection) and
32+
* #90 (the raw collision box):
33+
*
34+
* - **37 of 1531 matched cells, and all 37 differ in EXACTLY ONE edge.** Not one
35+
* two-edge difference survives. Before #83 the dominant failure was two edges
36+
* (125 of 175), i.e. a whole corner on the wrong side of a band; that mode is
37+
* gone.
38+
* - **Every one is an OVER-detection.** In all 37 the game reports a `-to-none`
39+
* orientation and the port reports a crossing on that edge - never the
40+
* reverse. Sample transitions: `south-to-north -> none-to-north` (4x),
41+
* `north-to-south -> north-to-none` (4x), `west-to-east -> none-to-east` (3x).
42+
* - Spread evenly over the four edges (L11 / R6 / T7 / B13) and over regions
43+
* (7 / 26 / 4), so it is not a directional off-by-one.
44+
*
45+
* **Two candidate causes are already eliminated, which is why this is worth
46+
* pinning rather than re-deriving:**
47+
*
48+
* - `crossesCliff` is EXACT. Disassembled at `0x10160c914` under 2.1.12 (the VA
49+
* in `cliffs-NOTES.md` had moved); `cliffPlacement.ts` reproduces it line for
50+
* line, including the `a < 0 || b < 0` early-out, the `boundary < e0` check
51+
* and the strict `> 0.5` gate and strict crossing comparisons. There is no
52+
* `>=`-vs-`>` slip to find.
53+
* - `cliffiness_basic` is EXONERATED. Substituting the game's own corner
54+
* cliffiness leaves the count at exactly 37 / 1531.
55+
*
56+
* So the residual is in the **grid-4 cliff-elevation field**, the one input in
57+
* the chain with no direct per-corner oracle. A single-edge, strictly
58+
* one-directional over-detection is what a small positive field offset looks
59+
* like.
60+
*/
61+
describe("the shape of the Vulcanus orientation residual", () => {
62+
const ctx = withCtxDefaults({ seed0: vulcanus.seed, startingPositions: [{ x: 0, y: 0 }] });
63+
const fields = makeVulcanusCliffFields(ctx);
64+
const nameToId = new Map(CLIFF_ORIENTATION_NAMES.map((n, i) => [n, i]));
65+
const codeForOrientation = new Map<number, number>();
66+
for (let c = 0; c < 256; c++) {
67+
const id = cliffOrientationForCode(c);
68+
if (id !== undefined && !codeForOrientation.has(id)) codeForOrientation.set(id, c);
69+
}
70+
71+
const wrong: { ourCode: number; gameCode: number; ours: string; game: string }[] = [];
72+
let matched = 0;
73+
for (const c of vulcanus.cases) {
74+
const r = c.region;
75+
const placed = makeCliffPlacementFromFields(fields, {
76+
elevation0: VULCANUS_CLIFF_ELEVATION_0,
77+
interval: VULCANUS_CLIFF_ELEVATION_INTERVAL,
78+
smoothing: VULCANUS_CLIFF_SMOOTHING,
79+
}).placedCells(r.x0, r.y0, r.x1, r.y1);
80+
const ours = new Map(placed.map((p) => [key(p), p.code]));
81+
for (const p of c.cliffs.filter((q) => q.name === "cliff-vulcanus")) {
82+
const code = ours.get(key(p));
83+
if (code === undefined) continue;
84+
matched++;
85+
const id = cliffOrientationForCode(code);
86+
const got = id === undefined ? undefined : CLIFF_ORIENTATION_NAMES[id];
87+
if (got === p.orientation) continue;
88+
const gid = nameToId.get(p.orientation);
89+
const gameCode = gid === undefined ? undefined : codeForOrientation.get(gid);
90+
expect(gameCode).toBeDefined();
91+
wrong.push({
92+
ourCode: code,
93+
gameCode: gameCode as number,
94+
ours: String(got),
95+
game: p.orientation,
96+
});
97+
}
98+
}
99+
100+
it("compares a substantial set - the shape below is not read off a handful", () => {
101+
expect(matched).toBeGreaterThan(1500);
102+
expect(wrong.length).toBeGreaterThan(0);
103+
expect(wrong.length).toBeLessThanOrEqual(37);
104+
}, 120000);
105+
106+
it("differs in exactly ONE edge, every time", () => {
107+
for (const w of wrong) {
108+
const a = edgesOf(w.ourCode);
109+
const b = edgesOf(w.gameCode);
110+
let differing = 0;
111+
for (let i = 0; i < 4; i++) if (a[i] !== b[i]) differing++;
112+
expect(differing).toBe(1);
113+
}
114+
}, 120000);
115+
116+
/**
117+
* The direction is the actual lead. If this ever fails with under-detections
118+
* appearing, the cause has changed and the "small positive field offset"
119+
* reading above is dead.
120+
*/
121+
it("is always an OVER-detection - the game says none, we say a crossing", () => {
122+
let over = 0;
123+
for (const w of wrong) {
124+
const a = edgesOf(w.ourCode);
125+
const b = edgesOf(w.gameCode);
126+
for (let i = 0; i < 4; i++) {
127+
if (a[i] === b[i]) continue;
128+
// The game's edge carries no crossing (0) and ours does.
129+
expect(b[i]).toBe(0);
130+
expect(a[i]).not.toBe(0);
131+
over++;
132+
}
133+
}
134+
expect(over).toBe(wrong.length);
135+
}, 120000);
136+
137+
it("is not concentrated on one edge, which would be an off-by-one", () => {
138+
const perEdge = [0, 0, 0, 0];
139+
for (const w of wrong) {
140+
const a = edgesOf(w.ourCode);
141+
const b = edgesOf(w.gameCode);
142+
for (let i = 0; i < 4; i++) if (a[i] !== b[i]) perEdge[i]++;
143+
}
144+
// Measured L11 / R6 / T7 / B13. Every edge participates; no edge dominates.
145+
for (const n of perEdge) expect(n).toBeGreaterThan(0);
146+
expect(Math.max(...perEdge)).toBeLessThan(wrong.length * 0.6);
147+
}, 120000);
148+
});

0 commit comments

Comments
 (0)