test(cliffs): the 31 are TWO defects, and the tile resolver is neither (#84) - #115
Merged
Conversation
#84) #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. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KVXnqZPGsD7A2SaPVA9ArA
This was referenced Aug 3, 2026
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>
wormeyman
added a commit
that referenced
this pull request
Aug 4, 2026
#115 ruled the ore out for the whole residual in one clause: "no adjustment to a lava collision box can ever reach them, and neither can the ore rule (all 25 are `ore = false`)". `ore = false` there is OUR predicate's output, not the game's behaviour, and `vulcanusOreRejection.ts` says on its own front page that the rule is "exactly right where it fires, simply too narrow" - it accounts for 20 of the 31 cells the ore actually suppresses. Ruling the ore out with it is circular. The non-circular instrument was already on disk and already covers the right region: `oracle-vulcanus-cliff-ore-direction` re-runs `[1500,1500]`, where every one of the far ten lives, with the resources switched off. Switch them off and SIX OF THE FAR TEN APPEAR. - Of the 25: 11 ore (7 calcite + 4 geyser), 11 unknown, 3 outside the lever's region. The unidentified-mechanism population is 11, not 25. - The far ten's clusters have DIFFERENT causes - the `1542/1546` knot is the geyser, the two singletons are calcite, and only the `1742/1746` vertical run of four survives as unexplained. - #115's lava-distance grouping is not the causal grouping: ore cells land in its near, mid and far groups alike. Do not act on the near/far split. - The 0.710 and 0.645 recall figures are the PLACED and RAW stages of the same predicate, not a disagreement. Precision is 1.000 at both. - Sharpens #122: of its two proven destructions, `1546,1550.5` is a geyser suppression, so the unknown group's destruction proof rests on `1746,1538.5`. The ore gap itself is not new - `vulcanusOreRejection.ts` has documented the narrowness since it was written, with the geyser arm excluded deliberately. What is new is that this known gap is where 11 of the 25 went. #115's careful half stands and is untouched: the distances are right, no lava box reaches the far group, and the tile resolver is exonerated 70/70. Its doc comment is corrected in place rather than left to mislead. Measurement only - nothing shipping changed. Claude-Session: https://claude.ai/code/session_015jeznBmWiZywUc7cBw9xFU Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #114, which reduced the whole Vulcanus cliff residual to 31 destruction disagreements and handed over one question:
Surface::wouldColliderunsconstCollideWithTileagainst 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 headless capture. It needed none.
The tile resolver is exonerated for all 6 false rejections
oracle-vulcanus-lava-boundaryis a committed 994-position dense capture ofsurface.get_tile(x, y).namefrom a real 2.1.12 Vulcanus surface. It was taken for a different question - the 35 tiles our mask called lava inside a real cliff's box, back when the collision box was the defect - and it covers every tile of all six of today's false-rejection boxes.70 of 70 covered. Zero disagreements, both directions. The game read the same lava out of those boxes that we do and placed the cliff anyway. The box or the rule is holding the six, not the resolver.
Vacuity arms are in the spec and they matter here: "0 mismatches" is also what a comparison that never ran would print. Every one of the six boxes does contain lava by our model, and the fixture carries both lava and non-lava names.
A second candidate died in
factorio-data@ 2.1.12 rather than in a capture:tiles-vulcanus.luagives 17 tilestile_collision_masks.ground()and exactly 2 -lavaandlava-hot-tile_collision_masks.lava(). SoVULCANUS_CLIFF_BLOCKING_TILESis provably complete.The 25 in the other direction do NOT share a cause
Chebyshev distance from each cell's collision 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:
The near group is enriched 10.5x - the signature of a lava boundary or a box a tile short, and the one place a small change to the box could be right.
But ten of the 25 have no lava within twelve tiles. No adjustment to a lava collision box can reach them; neither can the ore rule (all 25 are
ore = false) nor any entity (#111'sautoplace_settingslever moved zero cliffs). They also cluster where the near group does not -1746,{1530.5, 1534.5, 1538.5}is a vertical run of three,1542/1546,{1550.5..1558.5}a knot,1622/1626,1614.5a pair.Note "far from lava" is the common case (1037 of 1525 correct cells), so the far group carries no signal by itself - the near group is the unusual one. What the split establishes is only that one mechanism cannot produce both, which is enough to stop anyone tuning a box against all 25 at once.
So #113's framing was too narrow. "Which cells does
Surface::wouldCollidereject that ours does not" is the right question for at most 15 of the 25 and for none of the 6. Fitting one collision box to all 31 would be fitting a rule to two causes at once - the failure #88 exists to record.Refuted on the way: cliff-versus-cliff collision
Recorded rather than left to be re-derived, because it is the first idea the far ten suggest and a good one on its face:
applyCliffsadds each cliff to the surface immediately after testing it, so cliff N+1'swouldCollidesees cliffs 1..N already there - and #111's lever cannot remove cliffs, so this is the one case it never covered. The far ten cluster, which is what a neighbour-versus-neighbour rule produces.It dies on the base rate. 9 of the far 10 overlap another cliff's
rotbbbox - and so do 1405 of the 1531 cliffs the game keeps, 91.8%. Not enriched; marginally below base rate. A rule destroying on box overlap would have destroyed nearly every cliff on the map.Independently: the cliff prototype's generic
collision_boxis{{-0.99,-0.49},{0.99,0.49}}-entity-util.luacalls it "intentionally small" - and cliff cells sit on a 4-tile grid, so two cliffs' generic boxes cannot overlap at all.Next
The far ten are the bigger half and they are an unidentified mechanism, not a mistuned parameter. Everything named so far is ruled out for them.
Nothing shipping changes; this is measurement only.
pnpm run verifygreen (1423 tests).🤖 Generated with Claude Code
https://claude.ai/code/session_01KVXnqZPGsD7A2SaPVA9ArA