Skip to content

Commit 90776d1

Browse files
wormeymanclaude
andauthored
test(cliffs): the crater RNG hypothesis is UNSUPPORTED - n is 1 (#84) (#136)
#130 proposed a mechanism class new to #84 - entity autoplace ROLLS, so removing calcite shifts the per-chunk RNG stream - and said "nothing here tests it". This is that test. No capture: every fact is in fixtures already committed, one of them since #111. The hypothesis is not refuted. The evidence for it is. 1. **n is ONE crater, not eight.** The eight `crater-cliff` entities are eight segments of a single ring - centre (1646.62, 1679.75), radii 4.95 to 7.00, against the prototype's own `crater_radius = 7`. FFF #386 describes exactly that. Effective sample size 1; #130's table invites reading it as 8. 2. **The spatial reading would have been vacuous.** All eight sit within 0.33 tiles of a calcite entity - and their chunk holds 805 calcite over 1024 tiles, 78.6% coverage. Landing on calcite there is expected. 3. **#130's `geyser OFF` control is VACUOUS.** Zero geysers in the ring's chunk; all 19 are in (48,48), (48,49), (53,54). The lever works globally - removes all 19, moves cliff-vulcanus 885 -> 889 - and cannot touch that chunk's stream by construction. 4. **A lever unrelated to calcite also produces craters**, unread in the #111 fixture: `LAVA TILES OFF only` gives 7 craters with resources still ON, and both levers together give 18 against 8 and 7 alone. 5. **And that row is a data fact.** `crater-cliff` overrides the default cliff mask with `{item, object, player, water_tile}`, and on Vulcanus only lava and lava-hot carry `water_tile`. No RNG needed. Calcite's own route stays open, with two more closed by reading rather than assuming: not entity collision (`resource` mask is `{resource}`, disjoint from every cliff mask including crater-cliff's - #124's argument extended rather than carried over), and not lava tiles (#128 measured zero blocking tiles moved). A third fact from the same file closes a route this session had to consider: the default `cliff` mask carries `not_colliding_with_itself = true`, so cliffs never collide with same-prototype cliffs. That matters because `applyCliffs` adds each cliff to the surface before testing the next, and 57 of 400 adjacent orientation pairs have overlapping boxes. No new fixtures, no shipping behaviour change. Claude-Session: https://claude.ai/code/session_01MmitdaFJxN9Wm8LkQu33kc Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ea810e9 commit 90776d1

2 files changed

Lines changed: 310 additions & 0 deletions

File tree

docs/noise/vulcanus-cliffs-NOTES.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3583,6 +3583,17 @@ writing down because it is the first mechanism class in this investigation that
35833583
is not already closed - and because the alternative, that craters share the 31's
35843584
unknown cause, is a much bigger claim needing much better evidence.
35853585

3586+
> **UPDATE, 2026-08-03 (#136): the hypothesis is UNSUPPORTED - n is 1, the
3587+
> `geyser OFF` row above is VACUOUS, and a documented mechanism explains craters
3588+
> without it.** The eight craters are eight segments of **one ring**
3589+
> (`crater_radius = 7`), so the effective sample size is 1. There are **zero
3590+
> geysers in the ring's chunk**, so that control could never have discriminated
3591+
> anything. And `LAVA TILES OFF only` - a lever with nothing to do with calcite -
3592+
> brings back **7 craters with resources still ON**, which `crater-cliff`'s
3593+
> explicit `water_tile` collision mask explains as a prototype-level data fact.
3594+
> Calcite's own route stays open; entity collision and lava tiles are closed for
3595+
> it. See the last section of this file.
3596+
35863597
The craters are also **16.4 tiles** from the nearest suppressed cell at closest
35873598
approach, against their own 2.8-tile collision box, so crater placement is not
35883599
the 31's mechanism even before the ordering argument.
@@ -3815,3 +3826,85 @@ system is gated on `proto->place_as_crater == nullptr`), and
38153826
`CliffEditor::buildCliffs` - so **there is no second connection pass during map
38163827
generation**, and the "a later chunk cleans up after an earlier one" reading is
38173828
closed.
3829+
3830+
## The crater-cliff RNG hypothesis is UNSUPPORTED - n is 1 and both controls fail (2026-08-03, #84)
3831+
3832+
#130 proposed a mechanism class that was new to #84: entity autoplace **rolls**,
3833+
so removing calcite shifts the per-chunk RNG stream and moves everything drawn
3834+
after it. It marked itself a hypothesis and said "nothing here tests it".
3835+
3836+
`test/craterCliffRngHypothesis.spec.ts`. **No capture** - every fact below sits
3837+
in fixtures already committed, one of them since #111.
3838+
3839+
**The hypothesis is not refuted. The evidence for it is.**
3840+
3841+
### 1. n is ONE crater, not eight
3842+
3843+
The eight `crater-cliff` entities are eight segments of a **single ring** -
3844+
centre `(1646.62, 1679.75)`, radii 4.95 to 7.00, against the prototype's own
3845+
`crater_radius = 7`. FFF #386 describes exactly this: "a ring of special cliffs
3846+
where sections of the ring can randomly be removed."
3847+
3848+
Effective sample size **1**. #130's table invites reading it as 8.
3849+
3850+
### 2. The spatial reading would have been vacuous
3851+
3852+
All eight segments sit within **0.33 tiles** of a calcite entity - which looks
3853+
decisive and is not. Their chunk `(51,52)` holds **805 calcite entities over 1024
3854+
tiles, 78.6% coverage**. A ring landing on calcite there is expected.
3855+
3856+
### 3. The `geyser OFF` control is VACUOUS
3857+
3858+
#130 offers it as the arm separating calcite from resources generally. **There
3859+
are zero geysers in the ring's chunk** - all 19 are in `(48,48)`, `(48,49)` and
3860+
`(53,54)`. The lever works globally (removes all 19, moves `cliff-vulcanus`
3861+
885 -> 889) and cannot touch that chunk's stream by construction.
3862+
3863+
### 4. A lever unrelated to calcite also produces craters
3864+
3865+
Unread in `oracle-vulcanus-cliff-suppressor-levers` since **#111**:
3866+
3867+
| arm | `cliff-vulcanus` | `crater-cliff` |
3868+
| --- | --- | --- |
3869+
| default, resources ON | 885 | **0** |
3870+
| resources OFF via controls | 916 | 8 |
3871+
| **LAVA TILES OFF only** | 1053 | **7** |
3872+
| resources OFF + LAVA TILES OFF | 1082 | **18** |
3873+
3874+
**Craters appear with calcite still ON.** Every #84 spec filters to
3875+
`name === "cliff-vulcanus"`, so the column was never looked at - the same failure
3876+
#94 caught once already, in the same investigation.
3877+
3878+
### 5. And that row is a DATA FACT, not a hypothesis
3879+
3880+
`crater-cliff` overrides the default cliff mask with an explicit
3881+
`{item, object, player, water_tile}`. On Vulcanus only `lava` and `lava-hot`
3882+
carry `water_tile`. Lava blocks crater segments **by prototype**; row 3 needs no
3883+
RNG at all.
3884+
3885+
### Where calcite stands, and two routes closed properly
3886+
3887+
Its route is still unexplained and the RNG stream is still the only unclosed
3888+
candidate. Two others are now closed by reading rather than by assuming:
3889+
3890+
- **Not entity collision.** `core/lualib/collision-mask-defaults.lua` gives
3891+
`resource` the mask `{resource}`, disjoint from every cliff mask including
3892+
`crater-cliff`'s. That is #124's prototype-level argument extended to the
3893+
second entity type rather than assumed to carry over.
3894+
- **Not lava tiles.** #128 measured calcite moving **zero** blocking tiles.
3895+
3896+
A third fact from the same file closes a route this session had to consider:
3897+
the default `cliff` mask carries **`not_colliding_with_itself = true`**, so
3898+
cliffs never collide with cliffs of their own prototype. That matters because
3899+
`applyCliffs` adds each cliff to the surface *before* testing the next one -
3900+
without the flag the entire run would be placement-order dependent, and 57 of
3901+
400 adjacent orientation pairs have overlapping boxes.
3902+
3903+
### Status and the real test
3904+
3905+
**UNSUPPORTED, not refuted**: n = 1, no surviving control, and a competing
3906+
documented mechanism for the phenomenon it was invented to explain. Testing it
3907+
properly needs a lever that changes the roll count in the ring's own chunk
3908+
without touching that chunk's tiles or lava - switching off one of the rock
3909+
prototypes listed before `crater-cliff` in `autoplace_settings.entity` would do
3910+
it. One capture arm, and worth having a real control before spending it.
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import { describe, expect, it } from "vite-plus/test";
2+
3+
import ore from "./fixtures/oracle-vulcanus-cliff-ore-direction.seed123456.json";
4+
import levers from "./fixtures/oracle-vulcanus-cliff-suppressor-levers.seed123456.json";
5+
6+
/**
7+
* **The crater-cliff RNG hypothesis: n is ONE, both of its supporting rows are
8+
* VACUOUS, and a documented mechanism explains craters without it** (#84).
9+
*
10+
* #130 recorded "calcite suppresses crater-cliffs" and proposed a mechanism
11+
* class new to #84 - entity autoplace ROLLS, so removing calcite shifts the
12+
* per-chunk RNG stream. It marked itself a hypothesis and said "nothing here
13+
* tests it". This is that test, and it needs no capture: every fact below is in
14+
* fixtures that were already committed, one of them since #111.
15+
*
16+
* The hypothesis is not refuted. What is refuted is the evidence for it.
17+
*
18+
* ## 1. n is ONE crater, not eight
19+
*
20+
* The eight `crater-cliff` entities are eight segments of a **single ring** -
21+
* centre `(1646.62, 1679.75)`, radii 4.95 to 7.00, against the prototype's own
22+
* `crater_radius = 7` (`decoratives-vulcanus.lua`). FFF #386 describes exactly
23+
* this: "a ring of special cliffs where sections of the ring can randomly be
24+
* removed."
25+
*
26+
* So the effective sample size is **1**, and #130's table invites reading it as
27+
* 8. This is [[below-chance-needs-a-clustered-null]] in a second place.
28+
*
29+
* ## 2. The spatial reading would have been vacuous
30+
*
31+
* All eight segments sit within **0.33 tiles** of a calcite entity, which looks
32+
* like a smoking gun for a local mechanism and is not one: their chunk `(51,52)`
33+
* holds **805 calcite entities over 1024 tiles - 78.6% coverage**. A ring landing
34+
* on calcite there is the expected outcome, not a surprising one. Worth
35+
* recording because the check is cheap and the wrong conclusion is attractive.
36+
*
37+
* ## 3. #130's control row is VACUOUS
38+
*
39+
* Its table offers "geyser OFF -> 0 craters" as the arm distinguishing calcite
40+
* from resources generally. **There are zero geysers in the crater's chunk** -
41+
* all 19 in the region sit in chunks `(48,48)`, `(48,49)` and `(53,54)`. The
42+
* lever is non-vacuous globally (it removes all 19, and moves `cliff-vulcanus`
43+
* 885 -> 889) and inert *for this chunk's stream by construction*. It cannot
44+
* discriminate anything about the ring, and #130 read it as if it could.
45+
*
46+
* ## 4. A lever with NOTHING to do with calcite also produces craters
47+
*
48+
* Unread in `oracle-vulcanus-cliff-suppressor-levers` since #111:
49+
*
50+
* | arm | `cliff-vulcanus` | `crater-cliff` |
51+
* | --- | --- | --- |
52+
* | default, resources ON | 885 | **0** |
53+
* | resources OFF via controls | 916 | 8 |
54+
* | **LAVA TILES OFF only** | 1053 | **7** |
55+
* | resources OFF + LAVA TILES OFF | 1082 | **18** |
56+
*
57+
* **Craters appear with calcite still ON.** Every #84 spec filters to
58+
* `name === "cliff-vulcanus"`, so the column had never been looked at - the same
59+
* failure #94 caught once already.
60+
*
61+
* ## 5. And that one is a DATA FACT, not a hypothesis
62+
*
63+
* `crater-cliff` overrides the default cliff mask with an explicit
64+
* `{item, object, player, water_tile}`. On Vulcanus only `lava` and `lava-hot`
65+
* carry `water_tile`. So lava blocks crater segments **by prototype**, and row 3
66+
* of that table needs no RNG at all.
67+
*
68+
* ## Where that leaves calcite
69+
*
70+
* Its route is still unexplained, and the RNG stream is still the only unclosed
71+
* candidate - but two others are closed here rather than assumed:
72+
*
73+
* - **Not entity collision.** `collision-mask-defaults.lua` gives `resource` the
74+
* mask `{resource}`, disjoint from every cliff mask including
75+
* `crater-cliff`'s. That is #124's prototype-level argument, extended to the
76+
* second entity type rather than assumed to carry over.
77+
* - **Not lava tiles.** #128 measured calcite moving **zero** blocking tiles.
78+
*
79+
* A third fact falls out of the same file and closes a route this session had to
80+
* consider: the default `cliff` mask carries **`not_colliding_with_itself`**, so
81+
* cliffs never collide with cliffs of their own prototype. `applyCliffs` adds
82+
* each cliff to the surface *before* testing the next one, so without that flag
83+
* the whole run would be order-dependent.
84+
*
85+
* ## Status
86+
*
87+
* **The RNG hypothesis is UNSUPPORTED, not refuted.** It has n = 1, no surviving
88+
* control, and a competing documented mechanism for the phenomenon it was
89+
* invented to explain. Testing it properly needs a lever that changes the roll
90+
* count in the crater's own chunk without touching that chunk's tiles or lava -
91+
* switching off one of the rock prototypes listed before `crater-cliff` in
92+
* `autoplace_settings.entity` would do it. That is one new capture arm, and it
93+
* is worth having a real control before spending it.
94+
*/
95+
96+
interface Ent {
97+
x: number;
98+
y: number;
99+
name: string;
100+
}
101+
interface Res {
102+
x: number;
103+
y: number;
104+
name: string;
105+
}
106+
interface Case {
107+
label: string;
108+
cliffs: Ent[];
109+
resources?: Res[];
110+
}
111+
112+
const oreCases = ore.cases as unknown as Case[];
113+
const leverCases = levers.cases as unknown as Case[];
114+
const arm = (cases: Case[], label: string): Case => {
115+
const c = cases.find((v) => v.label === label);
116+
if (c === undefined) throw new Error(`no arm ${label}`);
117+
return c;
118+
};
119+
const named = (c: Case, name: string): Ent[] => c.cliffs.filter((e) => e.name === name);
120+
const chunkOf = (v: number): number => Math.floor(v / 32);
121+
122+
const ON = arm(oreCases, "entity region, resources ON");
123+
const CALCITE_OFF = arm(oreCases, "entity region, calcite OFF");
124+
const GEYSER_OFF = arm(oreCases, "entity region, geyser OFF");
125+
const RING = named(CALCITE_OFF, "crater-cliff");
126+
127+
describe("the eight craters are ONE ring, so n is 1", () => {
128+
it("has all eight segments on a circle of the prototype's crater_radius", () => {
129+
expect(RING.length).toBe(8);
130+
const cx = RING.reduce((a, e) => a + e.x, 0) / RING.length;
131+
const cy = RING.reduce((a, e) => a + e.y, 0) / RING.length;
132+
const radii = RING.map((e) => Math.hypot(e.x - cx, e.y - cy));
133+
// `crater_radius = 7` in decoratives-vulcanus.lua, and the segment centres
134+
// sit on it. The bound carries a tolerance because `(cx, cy)` is the
135+
// centroid of the SURVIVING segments, not the crater's true centre - a ring
136+
// missing a section has a centroid slightly off it, which is worth a hair
137+
// rather than a rounder-looking assertion that would fail on the next ring.
138+
for (const r of radii) {
139+
expect(r).toBeGreaterThan(4.9);
140+
expect(r).toBeLessThan(7.1);
141+
}
142+
// One ring means one chunk, which is what makes the effective sample size 1.
143+
const chunks = new Set(RING.map((e) => `${String(chunkOf(e.x))},${String(chunkOf(e.y))}`));
144+
expect(chunks.size).toBe(1);
145+
});
146+
});
147+
148+
describe("the spatial coincidence with calcite is vacuous", () => {
149+
/**
150+
* Every segment is within a third of a tile of a calcite entity - and the
151+
* chunk is 78.6% calcite, so that is the expected outcome. Both halves are
152+
* asserted, because the first without the second is the wrong conclusion.
153+
*/
154+
it("puts every segment within 0.4 tiles of calcite", () => {
155+
const cal = (ON.resources ?? []).filter((r) => r.name === "calcite");
156+
for (const e of RING) {
157+
const d = Math.min(...cal.map((r) => Math.hypot(e.x - r.x, e.y - r.y)));
158+
expect(d).toBeLessThan(0.4);
159+
}
160+
});
161+
162+
it("but the ring's chunk is 78.6% calcite, so that means nothing", () => {
163+
const cal = (ON.resources ?? []).filter((r) => r.name === "calcite");
164+
const [cx, cy] = [chunkOf(RING[0].x), chunkOf(RING[0].y)];
165+
const inChunk = cal.filter((r) => chunkOf(r.x) === cx && chunkOf(r.y) === cy).length;
166+
expect(inChunk).toBe(805);
167+
expect(inChunk / 1024).toBeGreaterThan(0.75);
168+
});
169+
});
170+
171+
describe("#130's geyser control cannot discriminate anything", () => {
172+
/**
173+
* The lever works - it removes all 19 geysers and moves `cliff-vulcanus` - and
174+
* it is inert for the crater's chunk by construction, because no geyser is in
175+
* it. Both arms are needed: "the lever did something" and "it did nothing
176+
* HERE" are different claims.
177+
*/
178+
it("removes every geyser and still moves cliff-vulcanus", () => {
179+
expect((ON.resources ?? []).filter((r) => r.name === "sulfuric-acid-geyser").length).toBe(19);
180+
expect(
181+
(GEYSER_OFF.resources ?? []).filter((r) => r.name === "sulfuric-acid-geyser").length,
182+
).toBe(0);
183+
expect(named(ON, "cliff-vulcanus").length).toBe(885);
184+
expect(named(GEYSER_OFF, "cliff-vulcanus").length).toBe(889);
185+
});
186+
187+
it("but touches no entity in the crater's own chunk", () => {
188+
const [cx, cy] = [chunkOf(RING[0].x), chunkOf(RING[0].y)];
189+
const gey = (ON.resources ?? []).filter((r) => r.name === "sulfuric-acid-geyser");
190+
expect(gey.length).toBeGreaterThan(0);
191+
expect(gey.filter((r) => chunkOf(r.x) === cx && chunkOf(r.y) === cy).length).toBe(0);
192+
expect(named(GEYSER_OFF, "crater-cliff").length).toBe(0);
193+
});
194+
});
195+
196+
describe("a lever unrelated to calcite also produces craters", () => {
197+
/**
198+
* The row that had been sitting unread in the #111 fixture. Resources are ON
199+
* in the lava arm, so calcite is not necessary for craters to appear - and
200+
* `crater-cliff`'s explicit mask carries `water_tile`, which on Vulcanus only
201+
* `lava` and `lava-hot` have, so the mechanism is a prototype-level data fact.
202+
*/
203+
it("brings back 7 craters with LAVA TILES OFF and calcite still ON", () => {
204+
expect(named(arm(leverCases, "default, resources ON"), "crater-cliff").length).toBe(0);
205+
expect(named(arm(leverCases, "LAVA TILES OFF only"), "crater-cliff").length).toBe(7);
206+
});
207+
208+
it("and 18 when both levers are pulled, more than either alone", () => {
209+
const both = named(arm(leverCases, "resources OFF, LAVA TILES OFF"), "crater-cliff").length;
210+
const res = named(arm(leverCases, "resources OFF via controls"), "crater-cliff").length;
211+
const lava = named(arm(leverCases, "LAVA TILES OFF only"), "crater-cliff").length;
212+
expect(res).toBe(8);
213+
expect(lava).toBe(7);
214+
expect(both).toBe(18);
215+
expect(both).toBeGreaterThan(Math.max(res, lava));
216+
});
217+
});

0 commit comments

Comments
 (0)