Skip to content

test(cliffs): score the ore rule against the LEVER, and refute the cascade (#84) - #110

Merged
wormeyman merged 1 commit into
mainfrom
cliffs-ore-cascade-refuted
Aug 3, 2026
Merged

test(cliffs): score the ore rule against the LEVER, and refute the cascade (#84)#110
wormeyman merged 1 commit into
mainfrom
cliffs-ore-cascade-refuted

Conversation

@wormeyman

Copy link
Copy Markdown
Owner

vulcanusOreRejection.ts carried an explicit open question about the "run remainders": a cascade along cliff connections, or a wider box. #108 makes the cascade half concrete and testable - a rejection zeroes the cell's edge registers, so a neighbour's code, hence its ORIENTATION, hence its collision box, changes. Re-testing to a fixpoint is that cascade. rejectionCascades is the arm.

The cascade is refuted

matched wrong surplus missing
collapsed rule, one pass 18654 693 1200 103
collapsed rule, fixpoint 18640 700 1196 110

At the shipping settings it is bit-for-bit identical; the collapsed-rule row is what makes that a result rather than an untriggered branch. A rejected cell never turns a neighbour into a rejectable orientation.

The crossing stage explains 2 remainders for free

The predicate fires on 20 placed cells; the placement loses 22, because zeroing a rejected cell's edges leaves two neighbours with codes that no longer place. No tuning - it falls out of #108's mechanism, and it is the first thing to reduce the remainder count since the rule was characterised.

The rule now has a precision and a recall, not a total

oracle-vulcanus-cliff-ore-direction re-ran [1500,1500] with the resources switched off through autoplace_controls, so the ore's effect is a known set of cells.

game, resources ON / ALL OFF (in region) 861 / 892
cells the ore suppresses 31
cells that APPEAR when ore is added 0 (the one-way property of #99, re-confirmed on the entity region)
our model suppresses 22, all 22 correct
precision 1.000, recall 0.710

"Exactly right where it fires, simply too narrow" is a far more useful statement than "explains 21 of 31" - it says which direction is safe to move in.

Attribution from the per-control arms: 27 calcite, 4 geyser, 0 tungsten/coal. Of the 9 misses, 4 are geyser cells that includeGeyser: false deliberately excludes (already measured as harmful to include - the geyser rolls), 5 are calcite, and all 9 are adjacent to another suppressed cell.

Half of [1500,1500]'s residual is not ore at all

matched wrong surplus missing
resources ON, both sides 842 16 19 3
resources OFF, both sides 876 13 10 3

13 wrong orientations and 10 surplus cells survive with the ore entirely out of the picture. Tuning the ore rule cannot reach them, and that non-ore half is the larger target now.

What stays open

The wider-box half - which is the one #88 says must not be tuned into fitting. rejectionCascades stays in the tree as a measurement lever, defaulted off, in the same spirit as fixImpossibleCells and includeGeyser.

Item of #84; the issue stays open.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ExK3ASs5i2KMm9NwWqnZuj

…scade (#84)

`vulcanusOreRejection.ts` left an explicit open question: the run remainders
are "a cascade along cliff connections OR a wider box". #108 makes the
cascade half concrete - a rejection zeroes the cell's edge registers, so a
neighbour's code, hence its ORIENTATION, hence its collision box, changes -
so re-testing to a fixpoint IS that cascade. `rejectionCascades` is the arm.

**Refuted.** Bit-for-bit identical at the shipping settings, and on the
collapsed rule it loses 14 matched cells and 7 orientations to gain 4 of the
over-placement (18654/693/1200/103 -> 18640/700/1196/110). The collapsed-rule
row is what makes the shipping no-op a result rather than an untriggered
branch. A rejected cell never turns a neighbour into a rejectable
orientation.

**The crossing STAGE explains 2 remainders for free.** The predicate fires on
20 placed cells; the placement loses 22, because zeroing a rejected cell's
edges leaves two neighbours with codes that no longer place. No tuning - it
falls out of #108's mechanism, and it is the first thing to reduce the
remainder count since the rule was characterised.

**And the rule now has a precision and a recall, not a total.**
`oracle-vulcanus-cliff-ore-direction` re-ran the region with resources off
through `autoplace_controls`, so the ore's effect is a known SET: 31 cells
suppressed, 0 appeared (the one-way property of #99, re-confirmed on the
entity region), 5 re-coded. Our model suppresses 22, **all 22 correct** -
**precision 1.000, recall 0.710**. Exactly right where it fires, simply too
narrow, which says which direction is safe to move in.

Attribution from the per-control arms: 27 calcite, 4 geyser, 0 tungsten/coal.
Of the 9 misses, 4 are geyser cells `includeGeyser: false` deliberately
excludes, 5 are calcite, and all 9 are adjacent to another suppressed cell.

**Half of [1500,1500]'s residual is not ore at all.** Running BOTH sides with
resources off: 876 matched, **13 wrong, 10 surplus**, 3 missing - against
842/16/19/3 with them on. Tuning the ore rule cannot reach that half, and it
is the larger target now.

What stays open is the wider-box half - the one #88 says must not be tuned
into fitting.

Item of #84; the issue stays open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ExK3ASs5i2KMm9NwWqnZuj
@wormeyman
wormeyman merged commit 96d076e into main Aug 3, 2026
2 checks passed
@wormeyman
wormeyman deleted the cliffs-ore-cascade-refuted branch August 3, 2026 17:48
wormeyman added a commit that referenced this pull request Aug 3, 2026
…169 (#84) (#111)

#110 asked for a lever that isolates the non-ore suppressor the way
`autoplace_controls` isolated the ore, with the suspect unnamed. This adds
two, through `map_gen_settings.autoplace_settings` - a knob
`autoplace_controls` cannot reach, since a control only touches prototypes
that name one and the rocks, chimneys and `crater-cliff` name none.

**No placed entity suppresses a Vulcanus cliff.** Switching the whole
`entity` category off removes 409 rocks, 115 chimneys, 45 rock explosions
and all 8 `crater-cliff`s, and the cliff set does not move by one cell.
Rocks were previously refuted only statistically against our own rock
model (#109); this is the class excluded positively, with the lever's own
proof in the same run three ways.

**Cliffs do not collide with each other**, refuted by the game's own
output rather than a model: 293 pairs of its own cliffs have overlapping
collision rectangles.

**Lava suppresses 169 cells and our rejection gets 166, with 5 false
positives** - precision 0.9708, recall 0.9822, the first real score that
rule has had. Its errors ARE the residual: the 3 it misses are 3 of the 10
surplus cells and the 5 it invents include all 3 of the port's missing
ones. They point both ways, so the box's shape is wrong rather than its
size - left measured and unfixed, per #88.

**With neither ore nor lava, recall is 1.0000** (1049 matched, 9 wrong, 12
surplus, 0 missing of 1058). The port produces a strict superset of the
game's cells, so everything left is over-placement and the question is now
what else the game refuses.


Claude-Session: https://claude.ai/code/session_015C8AbtvYMdc7ed3rnAZbhP

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
wormeyman added a commit that referenced this pull request Aug 4, 2026
#84) (#125)

`vulcanusOreRejection.ts` records box overlap as explaining 21 of the 31 cells
the ore suppresses, the other ten as "run remainders", and the cascade half of
that question as REFUTED.

That refutation is about a DIFFERENT cascade. It tested #108's crossing-stage
mechanism - a rejection zeroes the cell's edge registers, a neighbour's code
changes, re-test to a fixpoint - and its conclusion ("rejected cells do not turn
neighbours rejectable") still stands. The `applyCliffs` cascade is
`Cliff::onDestroy` taking the facing end of every connected neighbour and
destroying a neighbour left with no end at all. It was read out of the binary in
#113, AFTER #110 ran, and nothing had re-run the remainder question against it.

Replayed entirely on the game's own data - its ore-off cliff set (892 with the
game's orientations), its resource entity positions, the prototype boxes the
fixture itself carries - so none of the port's field, ore model or geyser roll
appears in the measurement:

| arm                                  | matched | wrong | surplus |
| ---                                  | ---     | ---   | ---     |
| control - destroy the lever's own 31 | 861     | 0     | 0       |
| direct overlap only, no cascade      | 856     | 5     | 10      |
| direct overlap + onDestroy CASCADE   | 859     | 2     | 6       |

- Direct overlap fires on 21 and every one is in the lever's 31 - precision
  1.000 re-derived with no data of ours.
- The cascade closes 4 of the 10 remainders and 3 of the 5 orientation errors.
  Recall 21/31 = 0.677 -> 25/31 = 0.806, no new parameter, no wider box.
- `missing` is 0 in both arms, so no cell the game kept is ever removed. That is
  what separates a mechanism from a bigger rectangle - and #124 established the
  box is not the engine's collision test to begin with.

The control validates the harness, and getting it exact needs
`noUpdateConnections: true`: the game's dumped set is POST-pipeline, so running
`updateConnections` over it again double-applies the pass and scores wrong=13,
which reads like a defect in the cascade model and is an artifact of the
harness. Any experiment starting from a dumped set has the same hazard.

Six remain: 1546,1550.5 1546,1554.5 1606,1590.5 1606,1594.5 1622,1614.5
1626,1614.5. Two are the geyser pair, and the first is the one cell in this
whole residual whose destruction the game's own orientations directly witness
(#124's n=1) - so the best-evidenced destruction is still unexplained.

Measurement only - the renderer rejects at the crossing stage and does not run
`applyCliffConnections` at all.


Claude-Session: https://claude.ai/code/session_015jeznBmWiZywUc7cBw9xFU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
wormeyman added a commit that referenced this pull request Aug 4, 2026
…count is 14 (#84) (#126)

Everything the last three PRs established about the ore rule was measured on
[1500,1500], because that is the only region
`oracle-vulcanus-cliff-ore-direction` re-runs with the resources off. A rule
characterised on one region and never tested on another is fitted until proven
otherwise, so the lever was captured for the two regions the entities fixture
covers and it never did.

| region        | resources        | ON  | OFF | suppressed |
| ---           | ---              | --- | --- | ---        |
| [0,0]         | 945 tungsten-ore | 283 | 283 | 0          |
| [-1200,800]   | 1047 coal        | 387 | 387 | 0          |

- PRECISION 1.000 SURVIVES. Our predicate fires on zero cells in both regions,
  so 1992 resource entities across two fresh regions produce no false positive.
  This is the arm that would have caught a rule fitted to [1500,1500].
- THE UNEXPLAINED POPULATION IS 14, NOT 11. #123 could only park `106,26.5`,
  `90,38.5` and `-1050,1022.5` as undetermined because the lever's region did
  not cover them. It does now, and the ore suppresses nothing there.
- Re-confirms #110's attribution - 27 calcite, 4 geyser, 0 tungsten and coal -
  at far larger scale than the arm that produced it.

The non-vacuity check is in the fixture rather than argued: "0 suppressed" is
also what a lever that never reached the generator prints, and the OFF arms read
back 0 resources against 945 and 1047.

What this does NOT establish: neither region contains calcite or a geyser, the
only two controls that suppress anything. So this is a PRECISION test, not a
recall test - the rule's positive evidence is still one region, and #125's
cascade result is still measured on [1500,1500] alone. Breaking the rule would
need a region with calcite in it, and the oracle set has none.

New fixture is deliberately separate from `oracle-vulcanus-cliff-ore-direction`
so regenerating it cannot rewrite ground truth four merged PRs depend on.
PROVENANCE entry added.


Claude-Session: https://claude.ai/code/session_015jeznBmWiZywUc7cBw9xFU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant