test(cliffs): the direction is ORE -> CLIFF, and it is worth 31 cells (#84) - #99
Merged
Conversation
…#84) #94 ruled out lava, every other tile, the cliffiness gate and entity collision, then handed the mechanism over open: the cliff/ore correlation fits two stories demanding opposite fixes, and the note closed "settle the direction, not add a rejection." Settled with a lever rather than an argument. `autoplace_controls` is settable on the surface exactly like `cliff_settings`, so the resources can be switched off (`size = 0`) and the same regions regenerated - #82's collapse trick pointed one subsystem over. | arm | cliff-vulcanus | resources | | --- | --- | --- | | `[1500,1500]` resources ON | 885 | 3,933 | | `[1500,1500]` calcite OFF | 912 | 19 | | `[1500,1500]` geyser OFF | 889 | 3,914 | | `[1500,1500]` ALL OFF | 916 | 0 | | `[0,0]` collapsed, ON | 335 | 945 | | `[0,0]` collapsed, ALL OFF | **345** | 0 | **Both arms, not one.** Ore off puts a cliff in all ten blob cells, 0/10 -> 10/10. The converse arm is what makes it a direction: the collapsed settings force 335 cliffs through the region against the default's 283, straight through the tungsten field, and the ore does not move - the same 945 entities. Three properties, each constraining the mechanism: it is ONE-WAY (removing a resource only ever adds cliffs, in all four paired arms), ADDITIVE (27 calcite + 4 geyser = exactly the 31 of all-off, disjoint), and LOCAL with a geometry that is a BOX OVERLAP against the resource ENTITY's rectangle - measurable only because the geyser's collision half-extent is 1.398 against the ores' 0.098, which is why every point-at-tile-centre geometry scored 20-27 of 31. Scored as box overlap: 21 of 31 explained, ZERO false alarms in 885, and all six connected components of the suppressed set contain a directly-overlapped cell (adjacency is not a free pass - only 8 of 885 kept cliffs touch one). Worth 31 of the 42 cells the port over-places at `[1500,1500]`: every one of the 31 is a cell the port places and none is a cliff the game kept, so the rule is pure precision. Still not a collision, now doubly established. The mask argument stands and the fixture carries the layers rather than a claim about them. The disassembly says it more strongly: `computeInternal` (0x101622860) calls `generateCliffs` at +44 and `generateEntities` at +148; `apply` (0x101623b48) calls `applyCliffs` at +124 and `applyEntities` at +164 - the cliffs are computed AND placed before any resource exists, which also refutes #94's "maybe ore avoids cliffs" guess. Harness: `sampleCliffEntitiesFull` gains `autoplaceControls`, `alsoResources` and `protoNames`, dumping cliffs, resources, prototype collision geometry and the controls the SURFACE read back from ONE generated surface - so an override that failed to apply cannot be mistaken for a term that does not matter, and "the ore moved" and "the cliffs moved" are never compared across two worlds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LT6oy76sLRqoC1QzLocQ1h
wormeyman
force-pushed
the
feat/cliff-ore-exclusion
branch
from
August 2, 2026 18:18
6187d70 to
b540470
Compare
wormeyman
added a commit
that referenced
this pull request
Aug 2, 2026
#99 characterised the rule and stopped short of porting it, flagging one open sub-question: whether driving it from our own resource model, rather than the game's entities, is accurate enough. It is - and it costs exactly one cell. Scored across all three oracle regions, driving `makeVulcanusOreRejection` off the same field stack the ore overlay paints from: | region | game | placed | fires | false rejections | surplus | | --- | --- | --- | --- | --- | --- | | [0,0] | 283 | 283 | 0 | 0 | 2 -> 2 | | [1500,1500] | 885 | 900 | 20 | 0 | 42 -> 22 | | [-1200,800] | 401 | 387 | 0 | 0 | 1 -> 1 | Precision at [1500,1500] 0.953 -> 0.975 with the 858 true positives untouched. **Recall is not touched anywhere**, which was the gate: this rule may only ever cost precision. Three variants were scored and the two that lose are kept in the spec rather than dismissed in a comment, because #88/#90 already paid for that lesson here: | variant | fires | correct of 31 | false rejections | | --- | --- | --- | --- | | base box, ores only (SHIPPED) | 20 | 20 | 0 | | base box + geyser | 21 | 20 | 1 | | per-orientation box | 23 | 21 | 2 | The geyser arm is strictly HARMFUL - one more false rejection and not one additional correct suppression - so it is implemented behind `includeGeyser`, defaulting off. The per-orientation rotbb box catches one more true cell and pays two kept cliffs for it; higher `correct` is exactly the trap. Not claimed: the mechanism is still open (the disassembly says cliffs are placed before any resource entity exists), and 11 of the 31 stay unexplained with the box deliberately not widened to cover them. The spec pins that 11. Lands as `CliffBands.cellRejects`, an opaque per-cell predicate beside `tileCollides` - so the shared cliff core stays planet-agnostic, and the model the specs score is the model the renderer ships. The predicate enumerates no entities: the overlapping tiles follow in closed form (2 tiles for an ore against the lava rejection's ~30), guarded by a wider brute-force scan. Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
wormeyman
added a commit
that referenced
this pull request
Aug 3, 2026
…0.9961 (#84) (#101) * feat(cliffs): port the ORE -> CLIFF rejection (#84 item 1) #99 characterised the rule and stopped short of porting it, flagging one open sub-question: whether driving it from our own resource model, rather than the game's entities, is accurate enough. It is - and it costs exactly one cell. Scored across all three oracle regions, driving `makeVulcanusOreRejection` off the same field stack the ore overlay paints from: | region | game | placed | fires | false rejections | surplus | | --- | --- | --- | --- | --- | --- | | [0,0] | 283 | 283 | 0 | 0 | 2 -> 2 | | [1500,1500] | 885 | 900 | 20 | 0 | 42 -> 22 | | [-1200,800] | 401 | 387 | 0 | 0 | 1 -> 1 | Precision at [1500,1500] 0.953 -> 0.975 with the 858 true positives untouched. **Recall is not touched anywhere**, which was the gate: this rule may only ever cost precision. Three variants were scored and the two that lose are kept in the spec rather than dismissed in a comment, because #88/#90 already paid for that lesson here: | variant | fires | correct of 31 | false rejections | | --- | --- | --- | --- | | base box, ores only (SHIPPED) | 20 | 20 | 0 | | base box + geyser | 21 | 20 | 1 | | per-orientation box | 23 | 21 | 2 | The geyser arm is strictly HARMFUL - one more false rejection and not one additional correct suppression - so it is implemented behind `includeGeyser`, defaulting off. The per-orientation rotbb box catches one more true cell and pays two kept cliffs for it; higher `correct` is exactly the trap. Not claimed: the mechanism is still open (the disassembly says cliffs are placed before any resource entity exists), and 11 of the 31 stay unexplained with the box deliberately not widened to cover them. The spec pins that 11. Lands as `CliffBands.cellRejects`, an opaque per-cell predicate beside `tileCollides` - so the shared cliff core stays planet-agnostic, and the model the specs score is the model the renderer ships. The predicate enumerates no entities: the overlapping tiles follow in closed form (2 tiles for an ore against the lava rejection's ~30), guarded by a wider brute-force scan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c * test(cliffs): the budget FLIPPED - recall is now the bigger defect (#84) Every cliff defect found since #18 has been a rule the port over-places without (lava collision, the rotbb box shape, the ore suppression), so "find another rejection" has been the shape of the work throughout. After #100 that is no longer where the error is. | region | surplus | missing | lava-killed | ore-killed | never generated | | --- | --- | --- | --- | --- | --- | | [0,0] | 2 | 2 | 2 | 0 | 0 | | [1500,1500] | 22 | 27 | 3 | 0 | 24 | | [-1200,800] | 1 | 15 | 1 | 0 | 14 | | total | 25 | 44 | 6 | 0 | 38 | **The port now misses more cells than it over-places, 44 to 25**, and 38 of the 44 are cells the crossings stage never produces at all - a different defect in a different part of the port from everything solved so far. `[0,0]` generates every cell the game does: its whole miss is the two the lava rejection took, and `neverGenerated` is zero there. All 38 sit in the two far-field regions, which agrees with #93 finding the port exact at [0,0] and [-1200,800] at cliff_smoothing = 0 and still wrong at [1500,1500]. Also closes item 3 (the entity half of `Surface::wouldCollide`) UNPORTED, by size rather than by difficulty - the same move that retired fixImpossibleCells as a suspect. It is a rejection, and rejections can only remove cells: total surplus is 25, which bounds what rocks and craters together could ever be worth against a 44-cell recall gap they cannot touch. The crater arm is settled exactly, since craters are already in the fixtures: all 8 sit in [-1200,800] and not one touches a cell the port over-places, nor any cliff the game kept. Worth zero. The rock arm needs no fixture - the ceiling covers it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c * test(cliffs): the recall gap was a QUERY-WINDOW ARTIFACT - recall is 0.9961 (#84) Supersedes this branch's first commit, whose central claim was wrong. `find_entities_filtered` selects entities whose BOUNDING BOX touches the query area; `placedCells` emits cells whose CENTRE lies inside it. Different inclusion rules, so the fixtures carry cliffs centred just outside the box and every one has been scored as a miss. | region | game rows | centred inside | centred OUTSIDE | | --- | --- | --- | --- | | [0,0] | 283 | 283 | 0 | | [1500,1500] | 885 | 861 | 24 | | [-1200,800] | 401 | 387 | 14 | That is 38 cells - the entire apparent recall gap - and the port places 38 of 38 once the query box includes their centres. Every one is an agreement being scored as a failure. The widening arm is the load-bearing one: "we never looked there" alone is equally consistent with the port being wrong. Corrected budget, both sides scored alike: | region | game | port | matched | surplus | missing | | --- | --- | --- | --- | --- | --- | | [0,0] | 283 | 283 | 281 | 2 | 2 | | [1500,1500] | 861 | 880 | 858 | 22 | 3 | | [-1200,800] | 387 | 387 | 386 | 1 | 1 | | total | 1531 | 1550 | 1525 | 25 | 6 | **Recall 0.9961, precision 0.9839.** The 0.972 recall in the notes divided the same 1525 matches by 1569 rather than 1531 - the match count was never wrong, only the denominator. All 6 missing cells are ones our own lava rejection removed; there is no cell the port simply fails to generate. So precision is the only real defect left. Consequently item 3 (the entity half of Surface::wouldCollide) is RE-OPENED. The earlier commit closed it by size, arguing a rejection cannot help a 44-cell recall gap - that argument died with the gap. With recall at 0.9961 the dominant defect is the 25 surplus cells, which is exactly what a rejection removes. The crater arm stays settled at zero (all 8 sit in [-1200,800], none touches a surplus cell). The rock arm has no oracle capture at all, so capturing one is the next step - now with a 25-cell target rather than a ceiling against it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c * test(cliffs): the rock arm fails on the mechanism's own geometry (#84) Completes the item 3 thread in this branch rather than opening a second PR for it, since the previous commit had just re-opened it. No rock capture is needed to kill it. `computeInternal` runs `generateCliffs` before `generateEntities`, and `apply` runs `applyCliffs` (+124) before `applyEntities` (+164), so within a chunk no rock exists when the cliff is applied. A rock can only block a cliff from an ALREADY-GENERATED NEIGHBOUR, which confines the whole mechanism to cells near a 32-tile chunk border. | | n | near chunk border | | --- | --- | --- | | surplus | 25 | 11 = 44.0% | | matched | 1525 | 673 = 44.1% | The base rate to three significant figures. The surplus has no chunk-border character at all, so the one geometry the mechanism is confined to is not where the errors are. The direct overlap test agrees and is deliberately the weaker arm: 3 of 25 surplus cells overlap a modelled rock against a 6.6% base rate (~1.7 expected), which is nothing - and our rock placement is a salt-dependent roll whose individual positions are unreliable exactly as the geyser's were in #100. So item 3 explains approximately none of the 25, and is closed on the mechanism's geometry rather than on the ceiling argument that died with the recall gap. Remaining unexplained: 25 surplus, 6 missing (all lava-rejection over-rejections), 33 wrong orientations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
wormeyman
added a commit
that referenced
this pull request
Aug 3, 2026
) * feat(cliffs): port the ORE -> CLIFF rejection (#84 item 1) #99 characterised the rule and stopped short of porting it, flagging one open sub-question: whether driving it from our own resource model, rather than the game's entities, is accurate enough. It is - and it costs exactly one cell. Scored across all three oracle regions, driving `makeVulcanusOreRejection` off the same field stack the ore overlay paints from: | region | game | placed | fires | false rejections | surplus | | --- | --- | --- | --- | --- | --- | | [0,0] | 283 | 283 | 0 | 0 | 2 -> 2 | | [1500,1500] | 885 | 900 | 20 | 0 | 42 -> 22 | | [-1200,800] | 401 | 387 | 0 | 0 | 1 -> 1 | Precision at [1500,1500] 0.953 -> 0.975 with the 858 true positives untouched. **Recall is not touched anywhere**, which was the gate: this rule may only ever cost precision. Three variants were scored and the two that lose are kept in the spec rather than dismissed in a comment, because #88/#90 already paid for that lesson here: | variant | fires | correct of 31 | false rejections | | --- | --- | --- | --- | | base box, ores only (SHIPPED) | 20 | 20 | 0 | | base box + geyser | 21 | 20 | 1 | | per-orientation box | 23 | 21 | 2 | The geyser arm is strictly HARMFUL - one more false rejection and not one additional correct suppression - so it is implemented behind `includeGeyser`, defaulting off. The per-orientation rotbb box catches one more true cell and pays two kept cliffs for it; higher `correct` is exactly the trap. Not claimed: the mechanism is still open (the disassembly says cliffs are placed before any resource entity exists), and 11 of the 31 stay unexplained with the box deliberately not widened to cover them. The spec pins that 11. Lands as `CliffBands.cellRejects`, an opaque per-cell predicate beside `tileCollides` - so the shared cliff core stays planet-agnostic, and the model the specs score is the model the renderer ships. The predicate enumerates no entities: the overlapping tiles follow in closed form (2 tiles for an ore against the lava rejection's ~30), guarded by a wider brute-force scan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c * test(cliffs): the budget FLIPPED - recall is now the bigger defect (#84) Every cliff defect found since #18 has been a rule the port over-places without (lava collision, the rotbb box shape, the ore suppression), so "find another rejection" has been the shape of the work throughout. After #100 that is no longer where the error is. | region | surplus | missing | lava-killed | ore-killed | never generated | | --- | --- | --- | --- | --- | --- | | [0,0] | 2 | 2 | 2 | 0 | 0 | | [1500,1500] | 22 | 27 | 3 | 0 | 24 | | [-1200,800] | 1 | 15 | 1 | 0 | 14 | | total | 25 | 44 | 6 | 0 | 38 | **The port now misses more cells than it over-places, 44 to 25**, and 38 of the 44 are cells the crossings stage never produces at all - a different defect in a different part of the port from everything solved so far. `[0,0]` generates every cell the game does: its whole miss is the two the lava rejection took, and `neverGenerated` is zero there. All 38 sit in the two far-field regions, which agrees with #93 finding the port exact at [0,0] and [-1200,800] at cliff_smoothing = 0 and still wrong at [1500,1500]. Also closes item 3 (the entity half of `Surface::wouldCollide`) UNPORTED, by size rather than by difficulty - the same move that retired fixImpossibleCells as a suspect. It is a rejection, and rejections can only remove cells: total surplus is 25, which bounds what rocks and craters together could ever be worth against a 44-cell recall gap they cannot touch. The crater arm is settled exactly, since craters are already in the fixtures: all 8 sit in [-1200,800] and not one touches a cell the port over-places, nor any cliff the game kept. Worth zero. The rock arm needs no fixture - the ceiling covers it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c * test(cliffs): the recall gap was a QUERY-WINDOW ARTIFACT - recall is 0.9961 (#84) Supersedes this branch's first commit, whose central claim was wrong. `find_entities_filtered` selects entities whose BOUNDING BOX touches the query area; `placedCells` emits cells whose CENTRE lies inside it. Different inclusion rules, so the fixtures carry cliffs centred just outside the box and every one has been scored as a miss. | region | game rows | centred inside | centred OUTSIDE | | --- | --- | --- | --- | | [0,0] | 283 | 283 | 0 | | [1500,1500] | 885 | 861 | 24 | | [-1200,800] | 401 | 387 | 14 | That is 38 cells - the entire apparent recall gap - and the port places 38 of 38 once the query box includes their centres. Every one is an agreement being scored as a failure. The widening arm is the load-bearing one: "we never looked there" alone is equally consistent with the port being wrong. Corrected budget, both sides scored alike: | region | game | port | matched | surplus | missing | | --- | --- | --- | --- | --- | --- | | [0,0] | 283 | 283 | 281 | 2 | 2 | | [1500,1500] | 861 | 880 | 858 | 22 | 3 | | [-1200,800] | 387 | 387 | 386 | 1 | 1 | | total | 1531 | 1550 | 1525 | 25 | 6 | **Recall 0.9961, precision 0.9839.** The 0.972 recall in the notes divided the same 1525 matches by 1569 rather than 1531 - the match count was never wrong, only the denominator. All 6 missing cells are ones our own lava rejection removed; there is no cell the port simply fails to generate. So precision is the only real defect left. Consequently item 3 (the entity half of Surface::wouldCollide) is RE-OPENED. The earlier commit closed it by size, arguing a rejection cannot help a 44-cell recall gap - that argument died with the gap. With recall at 0.9961 the dominant defect is the 25 surplus cells, which is exactly what a rejection removes. The crater arm stays settled at zero (all 8 sit in [-1200,800], none touches a surplus cell). The rock arm has no oracle capture at all, so capturing one is the next step - now with a 25-cell target rather than a ceiling against it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c * test(cliffs): the rock arm fails on the mechanism's own geometry (#84) Completes the item 3 thread in this branch rather than opening a second PR for it, since the previous commit had just re-opened it. No rock capture is needed to kill it. `computeInternal` runs `generateCliffs` before `generateEntities`, and `apply` runs `applyCliffs` (+124) before `applyEntities` (+164), so within a chunk no rock exists when the cliff is applied. A rock can only block a cliff from an ALREADY-GENERATED NEIGHBOUR, which confines the whole mechanism to cells near a 32-tile chunk border. | | n | near chunk border | | --- | --- | --- | | surplus | 25 | 11 = 44.0% | | matched | 1525 | 673 = 44.1% | The base rate to three significant figures. The surplus has no chunk-border character at all, so the one geometry the mechanism is confined to is not where the errors are. The direct overlap test agrees and is deliberately the weaker arm: 3 of 25 surplus cells overlap a modelled rock against a 6.6% base rate (~1.7 expected), which is nothing - and our rock placement is a salt-dependent roll whose individual positions are unreliable exactly as the geyser's were in #100. So item 3 explains approximately none of the 25, and is closed on the mechanism's geometry rather than on the ceiling argument that died with the recall gap. Remaining unexplained: 25 surplus, 6 missing (all lava-rejection over-rejections), 33 wrong orientations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c * test(cliffs): the orientation residual is not a boundary tie (#84) Stacked on the error-budget branch. The residual's known shape - exactly one edge per wrong cell, always an OVER-detection - has an obvious cheap explanation that turns out to be wrong, and ruling it out eliminates a whole class of cause. `crossesCliff` decides on the sign of `elevation - boundary`. If an endpoint sat within float noise of a band boundary, the ~1e-6 our fields agree to would flip the crossing, and the residual would be an irreducible precision limit with nothing to fix. Measured: every crossing edge in a wrong cell sits at least 0.205 from its boundary, median ~9.9 - four to seven orders of magnitude clear of float noise. For the game to disagree its elevation must differ from ours by more than 0.2, which is a real field or rule difference. Non-vacuity: the overall minimum across all 2,920 crossing edges is 6.4e-3, thirty times tighter, so "far from the boundary" is a property of the wrong cells and not of the sample. Also pins WHICH channel the corner fixture holds, as a number rather than as prose: against our per-tile elevation the worst corner differs by 4.8e-2; against the grid-4 cliff channel the generator actually reads, by 96.09. #83 was the most expensive mistake this subsystem has made and prose is not a guard. The gap that leaves is the point: the grid-4 cliff-elevation channel has NO per-corner oracle, is the only placement input never checked against the game corner by corner, and after the margin result is the only remaining candidate that could move an endpoint the required 0.2. Capturing it is the next step - and not via calculate_tile_properties, which is the 1-tile program that produced the wrong-channel fixture in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GyN97UwFQmwZs1cg4QHS1c --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
wormeyman
added a commit
that referenced
this pull request
Aug 3, 2026
…scade (#84) (#110) `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
This was referenced Aug 4, 2026
wormeyman
added a commit
that referenced
this pull request
Aug 14, 2026
…ty (#84) (#200) * test(oracle): add a data-stage prototype override hook (#84) Checkpoint, NOT PR-ready - no regression test yet. `autoplaceControls` and `cliffSettings` are surface settings and can be changed at on_init. A prototype field cannot: it is read at map-gen from the loaded prototype, so the only lever is the data stage. That is the difference between "switch the ore off" and "leave the ore exactly where it is and change one property of it", and only the second can separate a resource's PRESENCE from a specific behaviour of it. - `extraDataLua` is written to data-final-fixes.lua, not data.lua. This mod declares no dependencies, so Factorio may run its data.lua before space-age's, at which point data.raw.resource["tungsten-ore"] does not exist and the override would silently do nothing. - The protos dump gains `cliff_removal_probability` and `map_grid`, read back off the running game, so an override arm has to prove it applied. What it bought, on the blob region with COLLAPSED cliff settings: arm blob cliffs resources removal_prob A control 0/10 335 945 1 B removal_prob = 0, ore present 10/10 345 945 0 C resources OFF 10/10 345 0 1 B leaves 945 resource entities exactly where the control has them and the cliffs come back regardless, and B is indistinguishable from C. So `ResourceEntityPrototype::cliff_removal_probability` is the mechanism behind the ore -> cliff exclusion that #99, #100, #110, #128 and #129 characterised empirically and that vulcanusOreRejection.ts still records as "NOT established". It defaults to 1.0 and no shipped prototype overrides it, which is why the port's unconditional box-overlap rejection is correct as written - the behaviour does not change, the explanation does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEgTMcwvbm67ddUUtudY6X * test(cliffs): name the ore->cliff mechanism - cliff_removal_probability (#84) `vulcanusOreRejection.ts` opened with "NOT established: the mechanism. This is a characterised empirical rule, not a port of a known engine path." It is established now, and the answer was a documented prototype field: `ResourceEntityPrototype::cliff_removal_probability`, default 1.0, overridden by no shipped prototype - so it is invisible from the data and can only be seen by changing it. Five issues of `autoplace_controls` work could not reach it because a surface lever can only add or remove the ore. A prototype lever leaves the ore in place and changes one thing about it: arm blob cells cliff-vulcanus resources field control 0/10 335 945 1 field = 0 10/10 345 945 0 resources OFF 10/10 345 0 1 The middle arm keeps all 945 resource entities exactly where the control has them and the cliffs return regardless, so no account resting on the ore's PRESENCE survives. It is indistinguishable from the resources-OFF arm, and 345 - 335 is exactly the ten blob cells, so the field accounts for the effect entirely rather than partly. Every arm reads the field back off the running game, so an override that failed to apply cannot be mistaken for a term that does not matter. NO BEHAVIOUR CHANGES. At 1.0 the removal is unconditional, so the ported box-overlap rejection is correct exactly as written. What changes is the record: the disassembly finding (cliffs are computed and placed before any resource entity exists) stops being a dead end and becomes the reason the effect had to be a removal, and the thin n=1 destroy-stage result gains outside support - a field that removes cliffs can only act on cliffs that already exist. Still open, and said so in every place touched: the GEOMETRY the engine removes with. The base collision_box remains an empirical fit and the box still must not be tuned. - `OracleOptions.extraDataLua`, written to data-final-fixes.lua rather than data.lua because this probe mod declares no dependencies and may otherwise load before space-age. - The protos dump gains cliff_removal_probability and map_grid. - test/cliffRemovalProbability.spec.ts, its fixture and provenance. Discrimination checked by planting a confused-arm error: exactly the two mechanism tests fail, and the two that read only the control arm correctly do not. - Stale "mechanism is open" text corrected in vulcanusOreRejection.ts, cliffPlacement.ts, cliffOreActsAtDestroyStage.spec.ts and vulcanus-cliffs-NOTES.md. The point-in-time spec under docs/superpowers/specs/ is left alone by convention. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEgTMcwvbm67ddUUtudY6X --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes the question #94 handed over. That PR ruled out lava, every other tile, the cliffiness gate and entity collision, then stopped: the cliff/ore correlation fits two stories demanding opposite fixes, and its note closed with "settle the direction, not add a rejection."
Settled with a lever rather than an argument.
map_gen_settings.autoplace_controlsis settable on the surface exactly likecliff_settings, so the resources can be switched off (size = 0) and the same regions regenerated - #82's collapse trick pointed one subsystem over.[1500,1500]resources ON[1500,1500]calcite OFF[1500,1500]geyser OFF[1500,1500]ALL OFF[0,0]collapsed, ON[0,0]collapsed, ALL OFFBoth arms, not one
Turning the ore off puts a cliff in all ten blob cells -
0/10 -> 10/10. The converse arm is what makes this a direction rather than a correlation: the collapsed settings force 335 cliffs through that region against the default's 283, straight through the tungsten field, and the ore does not move. The same 945 entities.Three properties, each constraining the mechanism
sulfuric-acid-geyser's collision half-extent is 1.398 against the ores' 0.098 - a point-at-tile-centre test explains the calcite cells and cannot explain the geyser ones, which is exactly the residual that made every candidate geometry score 20-27 of 31.Scored as a box overlap: 21 of 31 explained, zero false alarms across the 885 the game kept. The other 10 are not scattered - the suppressed set has six 4-connected components and every one contains a directly-overlapped cell. Adjacency is not a free pass: only 8 of the 885 kept cliffs touch a suppressed cell.
What it is worth
Every one of the 31 is a cell the port currently places, and none is a cliff the game kept. The rule is pure precision - it can only remove surplus, and it removes 31 of the 42 cells the port over-places at
[1500,1500].Still not a collision, now doubly established
The mask argument stands, and the fixture now carries the layers rather than a claim about them. The disassembly says it more strongly:
computeInternal(0x101622860) callsgenerateCliffsat+44andgenerateEntitiesat+148;apply(0x101623b48) callsapplyCliffsat+124andapplyEntitiesat+164. The cliffs are computed and placed before any resource exists - so no collision test in the cliff path can see one, and #94's "maybe ore avoids cliffs" alternative is refuted by the converse arm.crater-cliffis suppressed by calcite the same way (0 with it on, 8 with it off) and craters are placed on a completely different path (CliffCraterPlacer::tryToPlaceCliffAsCrater,0x10160bcac, fromapplyEntities), so whatever this is, it is common to both cliff kinds.Harness
sampleCliffEntitiesFullgainsautoplaceControls,alsoResourcesandprotoNames. Every arm dumps cliffs, resources, prototype collision geometry and the controls the surface read back, all from one generated surface - so an override that silently failed cannot be mistaken for a term that does not matter, and "the ore moved" and "the cliffs moved" are never compared across two different worlds.Deliberately NOT done
Porting the rejection. The 31/0 score uses the game's resource entities as input, which isolates the rule from the accuracy of the resource port; driving it from
renderVulcanusResourcesis a second question and has not been measured. The notes say so at the point of use.🤖 Generated with Claude Code
https://claude.ai/code/session_01LT6oy76sLRqoC1QzLocQ1h