Skip to content

Commit b843eaf

Browse files
wormeymanclaude
andauthored
test(noise): channel audit - cliffs ARE the only coarse-grid consumer (#84) (#87)
#83 left an assumption behind: that cliffs are the only consumer reading a multisample-bearing field on a grid other than 1 tile. That was never measured, which is exactly the status the cliff case itself had before #83. Closes #84 item 4. The surface is much smaller than "audit every consumer", and that is the first finding. multisample appears in factorio-data @ 2.1.12 in exactly ONE place - vulcanus_basalt_lakes_multisample, used only by vulcanus_elev. No other planet uses the primitive. So the audit reduces to "who consumes Vulcanus elevation": | consumer | reads elevation? | grid | how | | ------------------------- | ---------------- | ---- | ---------------- | | cliff generator | yes | 4 | measured, #83 | | tile generator (19 range) | yes, via elev | 1 | measured, here | | vulcanus_temperature | yes | 1 | same program | | tungsten/coal/calcite/SA | no | n/a | read the Lua | | rocks, geyser | no | n/a | read the Lua | The resource row had to be checked rather than assumed, because the generic path DOES couple to elevation: starting_resources_lake_mask = clamp((elevation-1)/10, 0, 1) feeds starting_patches' spot_favorability_expression, and CLAUDE.md flags that coupling as exactly what changed at 2.1.9. Vulcanus does not take that path - each of its four resources uses its own vulcanus_*_region expression, and those four definitions contain 0 references to elevation. The tile generator reads grid 1, measured against the game. Substituting the cliff generator's 4-tile elevation into the tile resolver, over the 381 oracle positions: | | grid 1 (ships) | grid 4 | | ------------------------ | -------------- | ------- | | tile-name agreement | 0.9816 | 0.8609 | | lava misclassifications | 0 / 381 | 27 / 381 | 46 tiles named wrongly, and the binary lava call - the only thing the cliff collision rejection reads - goes from exact to 27 wrong. The metric is demonstrably sensitive to the swap, which is the part worth insisting on: PR #57's substitution failed precisely because "nothing changed" could not be distinguished from "the substitution never ran". Both arms carry that guard explicitly. Side result: this clears the lava perimeter that costs 13 real cliffs their placement (#86). Reading the other elevation channel makes lava dramatically worse, not better, so the perimeter error is not a channel mistake - it is somewhere else in vulcanusCatalog. Also asserts the max(-500, ...) clamp never bites at these positions, so the swap is exactly "the same field at grid 4" rather than something subtly else. Claude-Session: https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent e027566 commit b843eaf

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

docs/noise/vulcanus-cliffs-NOTES.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,3 +996,58 @@ sides of a comparison ran different **rule sets** - and in both cases the
996996
mismatch had a plausible mechanism ready to absorb it, so the wrong explanation
997997
was the comfortable one. Before attributing a residual to a mechanism, check
998998
that both sides of the comparison are running the same rules.
999+
1000+
## Channel audit: cliffs really are the only coarse-grid consumer (#84 item 4)
1001+
1002+
#83 left an assumption behind: cliffs are the only consumer reading a
1003+
`multisample`-bearing field on a grid other than 1 tile. That was never
1004+
measured - which is precisely the status the cliff case itself had before #83 -
1005+
so it is measured now. `test/multisampleChannelAudit.spec.ts`.
1006+
1007+
**The surface is much smaller than "audit every consumer", and that is the first
1008+
finding.** `multisample` appears in `~/GitHub/factorio-data` @ 2.1.12 in exactly
1009+
**one** place, `vulcanus_basalt_lakes_multisample`
1010+
(`planet-vulcanus-map-gen.lua:547`), used only by `vulcanus_elev`. No other
1011+
planet uses the primitive at all. So the audit reduces to "who consumes Vulcanus
1012+
`elevation`":
1013+
1014+
| consumer | reads `elevation`? | grid | how established |
1015+
| --- | --- | --- | --- |
1016+
| cliff generator | yes, via `cliff_elevation_from_elevation` | **4** | measured, #83 |
1017+
| tile generator (19 `*_range` expressions) | yes, via `vulcanus_elev` | **1** | measured, below |
1018+
| `vulcanus_temperature` | yes, `min(elev, elev/100)` | 1 | same program as tiles |
1019+
| resources: tungsten, coal, calcite, sulfuric acid | **no** | n/a | read below |
1020+
| rocks, geyser | **no** | n/a | read below |
1021+
1022+
**The resource row is the one that had to be checked rather than assumed**,
1023+
because the generic path *does* couple to elevation:
1024+
`starting_resources_lake_mask = clamp((elevation - 1) / 10, 0, 1)` feeds
1025+
`starting_patches`' `spot_favorability_expression`
1026+
(`core/prototypes/noise-programs.lua:270`), and CLAUDE.md flags that this
1027+
coupling is exactly what changed at 2.1.9. Vulcanus does not take that path -
1028+
each of its four resources is placed by its own `vulcanus_*_region` expression,
1029+
and those four definitions contain **0** references to `elevation`. So the lake
1030+
mask is a Nauvis-only coupling and no Vulcanus resource can see the multisample.
1031+
1032+
### The tile generator reads grid 1, measured against the game
1033+
1034+
Substituting the cliff generator's 4-tile elevation into the tile resolver and
1035+
comparing against the game's own `get_tile` output over the 381 oracle
1036+
positions:
1037+
1038+
| | grid 1 (ships) | grid 4 |
1039+
| --- | --- | --- |
1040+
| tile-name agreement | **0.9816** | 0.8609 |
1041+
| lava misclassifications | **0 / 381** | 27 / 381 |
1042+
1043+
46 tiles would be named wrongly, and the binary lava call - the only thing the
1044+
cliff collision rejection reads - goes from exact to 27 wrong. The 1-tile field
1045+
is not merely adequate here; it is the one that matches, and the metric is
1046+
demonstrably sensitive to the swap. That sensitivity is the part worth insisting
1047+
on: PR #57's substitution failed precisely because "nothing changed" was
1048+
indistinguishable from "the substitution never ran".
1049+
1050+
**A side result.** This also clears the lava perimeter that costs 13 real cliffs
1051+
their placement: reading the other elevation channel makes the lava
1052+
classification dramatically worse, not better, so the perimeter error is not a
1053+
channel mistake. It is somewhere else in `vulcanusCatalog`.
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, expect, it } from "vite-plus/test";
2+
3+
import fixture from "./fixtures/oracle-vulcanus-tile-names.seed123456.json";
4+
import { VULCANUS_CLIFF_BLOCKING_TILES } from "../src/noise/preview/renderVulcanusCliffs";
5+
import {
6+
makeVulcanusStack,
7+
makeVulcanusTileResolverFrom,
8+
type VulcanusStack,
9+
} from "../src/noise/tiles/vulcanusCatalog";
10+
11+
/**
12+
* **The channel audit for `multisample` (issue #84 item 4).**
13+
*
14+
* #83 found that `multisample`'s offsets are in the CALLING noise program's grid
15+
* units, so `vulcanus_basalt_lakes_multisample` is a 4-tile min-filter for the
16+
* cliff generator and a 1-tile one for `calculate_tile_properties`. The port had
17+
* used the 1-tile field for both. The obvious follow-up question is whether any
18+
* OTHER consumer reads a `multisample`-bearing field on a grid that is not 1
19+
* tile - and cliffs being the only coarse-grid consumer was an assumption, not a
20+
* measurement, which is exactly the status the cliff case had before #83.
21+
*
22+
* **The audit surface is far smaller than it looks, and that is a finding.**
23+
* `multisample` appears in `~/GitHub/factorio-data` @ 2.1.12 in exactly ONE
24+
* place - `vulcanus_basalt_lakes_multisample`
25+
* (`space-age/prototypes/planet/planet-vulcanus-map-gen.lua:547`), used only by
26+
* `vulcanus_elev`. Nothing on Nauvis, Gleba, Fulgora or Aquilo uses the
27+
* primitive at all. So "audit every consumer of a multisample-bearing field"
28+
* reduces to "audit every consumer of Vulcanus `elevation`", and that list is
29+
* short enough to close by reading the planet definition
30+
* (`space-age/prototypes/planet/planet-map-gen.lua:4-30`):
31+
*
32+
* | consumer | reads `elevation`? | grid |
33+
* | --- | --- | --- |
34+
* | cliff generator (`cliff_elevation_from_elevation`) | yes | **4** - #83 |
35+
* | tile generator (the 19 `*_range` expressions) | yes, via `vulcanus_elev` | 1 - measured below |
36+
* | `vulcanus_temperature` | yes, `min(elev, elev/100)` | 1, same program as tiles |
37+
* | resources (tungsten / coal / calcite / sulfuric acid) | **no** | n/a |
38+
* | rocks, geyser | **no** | n/a |
39+
*
40+
* The resource line is the one worth stating explicitly, because the generic
41+
* `resource_autoplace_all_patches` path DOES couple to elevation -
42+
* `starting_resources_lake_mask = clamp((elevation - 1) / 10, 0, 1)` feeds
43+
* `starting_patches`' `spot_favorability_expression`
44+
* (`core/prototypes/noise-programs.lua:270`). Vulcanus does not use that path:
45+
* each of its four resources is placed by its own `vulcanus_*_region`
46+
* expression, and grepping those four definitions for `elevation` returns **0**.
47+
* So the lake mask is a Nauvis-only coupling and no Vulcanus resource can see
48+
* the multisample. (`src/noise/resources/startingPatches.ts` hardcodes
49+
* `makeElevationNauvis` for the same reason.)
50+
*
51+
* That leaves the tile generator as the only consumer whose grid had to be
52+
* measured rather than reasoned about, which is what this file does.
53+
*/
54+
describe("multisample channel audit - which consumers read which grid", () => {
55+
const positions = fixture.positions;
56+
const want = fixture.tileNames;
57+
58+
/** A stack whose tile-facing `elev` is the CLIFF generator's 4-tile field. */
59+
const swappedStack = (base: VulcanusStack): VulcanusStack => ({
60+
...base,
61+
elevation: {
62+
...base.elevation,
63+
elev: (x, y) => base.elevation.cliffElevation(x, y),
64+
elevation: (x, y) => base.elevation.cliffElevation(x, y),
65+
},
66+
});
67+
68+
const agreement = (resolve: (x: number, y: number) => { name: string }): number => {
69+
let agree = 0;
70+
for (let i = 0; i < positions.length; i++)
71+
if (resolve(positions[i].x, positions[i].y).name === want[i]) agree++;
72+
return agree / positions.length;
73+
};
74+
75+
/**
76+
* The substitution is only exact if the `max(-500, ...)` clamp never bites,
77+
* because `cliffElevation` is clamped and `elev` is not. Asserted rather than
78+
* assumed: if a probe position ever sits below -500 this arm is comparing
79+
* something subtly different from "the same field at grid 4".
80+
*/
81+
it("the clamp never bites at these positions, so the swap is exact", () => {
82+
const stack = makeVulcanusStack({ seed0: fixture.seed0 });
83+
let min = Infinity;
84+
for (const p of positions) min = Math.min(min, stack.elevation.elev(p.x, p.y));
85+
expect(min).toBeGreaterThan(-500);
86+
}, 120000);
87+
88+
/**
89+
* **The tile generator runs at grid 1, measured.** Feeding it the cliff
90+
* generator's 4-tile elevation instead collapses agreement with the game's own
91+
* `get_tile` output. The 1-tile field is not merely adequate - it is the one
92+
* that matches, and the metric is demonstrably sensitive to the difference,
93+
* which is what stops "no change" from being the vacuous answer it was for
94+
* PR #57's field substitution.
95+
*
96+
* Measured 2026-08-01 over the 381 oracle positions: **0.9816 at grid 1
97+
* against 0.8609 at grid 4** - 46 tiles the port would name wrongly if the
98+
* tile generator shared the cliff generator's channel. The gap is the whole
99+
* point; the grid-4 number is pinned only loosely, as an upper bound well
100+
* below the grid-1 floor.
101+
*/
102+
it("TILES read the 1-tile channel - the 4-tile field disagrees with the game", () => {
103+
const base = makeVulcanusStack({ seed0: fixture.seed0 });
104+
const atGrid1 = agreement(makeVulcanusTileResolverFrom(base));
105+
const atGrid4 = agreement(makeVulcanusTileResolverFrom(swappedStack(base)));
106+
107+
// Grid 1 is the shipping configuration and `vulcanusTiles.spec.ts` owns its
108+
// floor; repeated here only so the comparison is self-contained.
109+
console.log(`vulcanus tile agreement: grid1=${atGrid1.toFixed(4)} grid4=${atGrid4.toFixed(4)}`);
110+
expect(atGrid1).toBeGreaterThan(0.978);
111+
// The substitution must actually move the answer. Without this, a swap that
112+
// silently failed to apply would read as "the grids agree".
113+
expect(atGrid4).toBeLessThan(atGrid1 - 0.02);
114+
}, 120000);
115+
116+
/**
117+
* The same question asked of the binary lava classification, which is the only
118+
* thing the cliff collision rejection reads. It is exact at grid 1 (49/49 both
119+
* ways, pinned in `vulcanusTiles.spec.ts`); at grid 4 it is **27 wrong out of
120+
* 381**.
121+
*
122+
* This is the sharper of the two arms for the audit's purpose. A tile-name
123+
* argmax can flip between two members of one biome family for precision
124+
* reasons, but lava/not-lava is a wide margin, so a mismatch here means the
125+
* field is genuinely different rather than near a tie. 0 -> 27 is that.
126+
*
127+
* It also settles a question the cliff work left hanging in the other
128+
* direction: the lava perimeter that costs 13 real cliffs their placement
129+
* (`vulcanusCliffEntities.spec.ts`) is NOT the tile resolver reading the wrong
130+
* elevation channel. Reading the other channel makes lava dramatically worse,
131+
* not better.
132+
*/
133+
it("LAVA classification is exact at grid 1 and not at grid 4", () => {
134+
const base = makeVulcanusStack({ seed0: fixture.seed0 });
135+
const count = (resolve: (x: number, y: number) => { name: string }): number => {
136+
let mismatch = 0;
137+
for (let i = 0; i < positions.length; i++) {
138+
const ours = VULCANUS_CLIFF_BLOCKING_TILES.has(
139+
resolve(positions[i].x, positions[i].y).name,
140+
);
141+
if (ours !== VULCANUS_CLIFF_BLOCKING_TILES.has(want[i])) mismatch++;
142+
}
143+
return mismatch;
144+
};
145+
const at1 = count(makeVulcanusTileResolverFrom(base));
146+
const at4 = count(makeVulcanusTileResolverFrom(swappedStack(base)));
147+
console.log(`vulcanus lava mismatches: grid1=${String(at1)} grid4=${String(at4)} of 381`);
148+
expect(at1).toBe(0);
149+
// Non-vacuity in the same breath: the swap must actually change the field.
150+
expect(at4).toBeGreaterThan(10);
151+
}, 120000);
152+
});

0 commit comments

Comments
 (0)