Skip to content

test(cliffs): the residual is 31 DESTRUCTION decisions and nothing else (#84) - #114

Merged
wormeyman merged 2 commits into
mainfrom
cliffs/destruction-residual
Aug 3, 2026
Merged

test(cliffs): the residual is 31 DESTRUCTION decisions and nothing else (#84)#114
wormeyman merged 2 commits into
mainfrom
cliffs/destruction-residual

Conversation

@wormeyman

Copy link
Copy Markdown
Collaborator

Answers the question #113 left open and named as unmeasured: are the 5 wrong orientations that survive the surplus cascade the fallout of the 5 false rejections, or a second defect?

They are the fallout. Measuring it the direct way settles considerably more than the question asked.

The result

Stop scoring predicates. Hand the port the game's own destruction set - destroy the raw cells the game does not have, keep everything else, let applyCliffs and the Cliff::onDestroy cascade run. Over all three oracle regions, with the real ore and the real lava in the world:

1531 of 1531. Zero wrong, zero surplus, zero missing.

So the exactness #113 found at [1500,1500] was not a property of that stripped-down levers world - it holds everywhere the port has ground truth. There is no orientation defect, no crossing defect, no field defect and no missing suppressor left anywhere in the Vulcanus cliff port.

Fitted vs predicted

  • FITTED: the 225-cell destruction set - 225 booleans, the only thing taken from the answer.
  • PREDICTED: all 1531 orientations, of which 14 are ones the cascade actively rewrites (19 ways each to be wrong); that the cascade destroys nothing beyond the 225, which would have shown as missing; and halo-independence, checked by re-running with our own lava+ore predicate outside the region instead of nothing (identical).
  • Control: destroy the same 225 without telling their neighbours and the 14 come back wrong.

The 18 wrong orientations split cleanly into the two destruction errors

collides matched wrong surplus missing
lava + ore (what #113 scored) 1508 18 22 5
...plus the 22 the game destroys 1521 5 0 5
...instead sparing the cells the game keeps 1517 14 22 0
the game's set - both fixed 1531 0 0 0

Row 2 is #113's prediction confirmed. Row 3 is the half it could not measure.

The target is 31, not 27

Surplus and missing are outcomes and the cascade has already blurred them. One boolean per raw cell, before any cascade:

count
raw cells inside the three regions 1756
the game destroys 225
we destroy 206
agree 200
we destroy, the game keeps 6
the game destroys, we keep 25

One of the 6 never shows as missing because a neighbour's cascade gets there first, and 3 of the 25 are cascade casualties anyway. Quote 31 for the rule and 27 for the rendered output - same defect, two stages.

Precision 200/206 = 0.971, recall 200/225 = 0.889, errors both ways - #111's finding restated on the stage that actually does the rejecting. #88 still stands: do not tune the box until it fits.

Two facts that narrow the next step

  • All 6 false rejections are the LAVA box; the ore rule invents none. The whole two-sided error set belongs to one predicate, the tile half of Surface::wouldCollide.
  • The raw set is a strict superset of the game's in all three regions (292/283, 1070/861, 394/387), not just at [1500,1500] where test(cliffs): two LEVERS - no entity suppresses a cliff, lava is 166/169 (#84) #111 measured it with the levers. Nothing left is a failure to generate a cliff.

Next

Surface::wouldCollide runs constCollideWithTile against the real surface; the port resolves tiles from our own Vulcanus tile model, and the two have never been compared inside a cliff's box - oracle-vulcanus-tile-names is a ~2600-tile spiral that does not cover these regions densely. A small capture over the 31 disputed boxes plus a control set would settle it either way.

Nothing shipping changes; this is measurement only. pnpm run verify green (1418 tests).

🤖 Generated with Claude Code

https://claude.ai/code/session_01KVXnqZPGsD7A2SaPVA9ArA

wormeyman and others added 2 commits August 3, 2026 14:09
…se (#84)

#113 left one question open and named it unmeasured: are the 5 wrong
orientations that survive the surplus cascade the fallout of the 5 false
rejections, or a second defect? They are the fallout - and measuring it the
direct way settles more than the question asked.

Hand the port the game's OWN destruction set in place of our lava and ore
predicates - destroy the raw cells the game does not have, let applyCliffs and
the onDestroy cascade run - and over all three oracle regions, with the real ore
and lava in the world:

  1531 of 1531. Zero wrong, zero surplus, zero missing.

So the exactness #113 found at [1500,1500] was not a property of that
stripped-down levers world. There is no orientation defect, no crossing defect,
no field defect and no missing suppressor left in this port.

FITTED: the 225-cell destruction set, 225 booleans. PREDICTED: all 1531
orientations, of which 14 are ones the cascade actively rewrites and each had 19
ways to be wrong; that the cascade destroys nothing beyond the 225, which would
have shown as missing; and halo-independence, checked by re-running with our own
predicate outside the region. Control: destroy the same 225 without the cascade
and the 14 come back wrong.

The 18 wrong orientations split cleanly into the two destruction errors:

  lava + ore (what #113 scored)         1508 / 18 / 22 / 5
  ...plus the 22 the game destroys      1521 /  5 /  0 / 5
  ...instead sparing the game's cells   1517 / 14 / 22 / 0
  the game's set, both fixed            1531 /  0 /  0 / 0

Row 2 is #113's prediction confirmed; row 3 is the half it could not measure.

Restated as the predicate's own confusion matrix - one boolean per raw cell,
before any cascade blurs it - the target is 31, not 27: of 1756 raw cells the
game destroys 225 and we destroy 206, agreeing on 200, with 6 false rejections
and 25 missed destructions. One false rejection never shows as `missing` because
a neighbour's cascade gets there first, and 3 of the 25 are cascade casualties
anyway. Quote 31 for the rule and 27 for the rendered output.

Two facts narrow the next step. All 6 false rejections are the LAVA box and none
is the ore rule, so the whole two-sided error set belongs to one predicate. And
the raw set is a strict superset of the game's in all three regions, not just at
[1500,1500] - so nothing left is a failure to generate a cliff.

Nothing shipping changes; this is measurement only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KVXnqZPGsD7A2SaPVA9ArA
@wormeyman
wormeyman merged commit 7a24589 into main Aug 3, 2026
2 checks passed
@wormeyman
wormeyman deleted the cliffs/destruction-residual branch August 3, 2026 21:28
wormeyman added a commit that referenced this pull request Aug 3, 2026
#84) (#115)

#114 reduced the residual to 31 destruction disagreements and handed over one
question: Surface::wouldCollide runs constCollideWithTile against the REAL
surface while the port resolves tiles from our own Vulcanus model, so a
disagreement between the two inside a cliff's box would produce exactly this
two-sided error set. It read as needing a new capture. It needed none.

The tile half is exonerated for all 6 false rejections.
oracle-vulcanus-lava-boundary is a committed 994-position dense capture of
surface.get_tile(x, y).name, taken for a different question back when the
collision box was the defect, and it covers every tile of all six boxes: 70 of
70 covered, ZERO disagreements, both directions. The game read the same lava we
do and placed the cliff anyway. Vacuity arms included, because "0 mismatches" is
also what a comparison that never ran would print.

A second candidate died in factorio-data rather than in a capture:
tiles-vulcanus.lua gives 17 tiles ground() and exactly 2 - lava and lava-hot -
lava(), so VULCANUS_CLIFF_BLOCKING_TILES is provably complete.

The 25 in the other direction do NOT share a cause. By Chebyshev distance from
each box to the nearest tile our own model calls lava, scored against the 1525
cells the port gets right so there is a base rate:

  within 2 tiles   missed 9 (36%)   matched 52 (3.4%)
  4 to 11 tiles    missed 6         matched 436
  none within 12   missed 10        matched 1037

The near group is enriched 10.5x - the signature of a boundary or a box a tile
short. But ten of the 25 have no lava within twelve tiles, so no adjustment to a
lava collision box can reach them; neither can the ore rule (all 25 are
ore=false) nor any entity (#111's lever moved zero cliffs). They also cluster
where the near group does not.

So the handover framing was too narrow: "which cells does wouldCollide reject
that ours does not" is the right question for at most 15 of the 25 and for none
of the 6. Fitting one box to all 31 would be fitting a rule to two causes at
once, which is the failure #88 exists to record.

Refuted on the way, and recorded so nobody re-derives it: cliff-versus-cliff
collision. Attractive because applyCliffs adds each cliff to the surface right
after testing it, and because #111's autoplace_settings lever cannot remove
cliffs - so this is the one case that lever never covered. It dies on the base
rate: 9 of the far 10 overlap another cliff's rotbb box, and so do 1405 of the
1531 cliffs the game KEEPS (91.8%). Independently, the cliff prototype's generic
collision_box is {{-0.99,-0.49},{0.99,0.49}} - "intentionally small" - and cliff
cells sit on a 4-tile grid, so generic boxes cannot overlap at all.

Nothing shipping changes; this is measurement only.


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

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
wormeyman added a commit that referenced this pull request Aug 3, 2026
#115 handed over two threads on the far ten and named the first unmeasured:
`applyCliffs` assumes destruction, but a strict superset says nothing about
whether the game's crossing field emitted those cells at all.

It is decidable from a fixture already on disk, because the two hypotheses
leave different marks on the NEIGHBOURS. `Cliff::onDestroy` trims a connected
neighbour's facing end unconditionally; a cell that was never queued only costs
its neighbour that end if `updateConnections` runs on it, and that is gated on
the neighbour sitting on its chunk's outer ring. So a disputed cell with a
non-border neighbour the game KEPT is settled by the game's own orientation.

- `1546,1550.5` and `1746,1538.5` are decidable and both say DESTROYED, one in
  each of the far group's two multi-cell clusters. Across all 225 cells the
  game destroys, the never-queued signature appears ZERO times.
- The counterfactual is what makes that more than reading a table: removing
  either from the QUEUE contradicts the game at its neighbour (wrong 1 each,
  2 together), while removing four far cells with no decidable neighbour
  changes nothing.
- #114's `1531 of 1531` is informative for only 14 of the 225 destructions and
  VACUOUS for the other 211. Those 14 are exactly #114's 14 no-cascade
  rewrites, cell for cell - two independent routes to the same set.
- The verdict is CONDITIONAL on the chunk-border gate, which
  `cliffConnections.spec.ts` records as unscored and which has been inert until
  now. Switch it off and both counterfactuals stop disagreeing with the game.
  Planting `onChunkBorder` returning true fails 7 of this file's 10 tests. If
  the gate was misread those cells revert to UNDECIDABLE, not to never-queued.

So the search stays on the unmodelled half of `Surface::wouldCollide` and not
on the crossing field, which #107 already exonerated once.

Measurement only - nothing shipping changed; recall 0.9961, precision 0.9858.


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