test(cliffs): the border effect is ORIENTATION-BLIND - a rival mechanism REFUTED (#84) - #134
Merged
Merged
Conversation
…ism REFUTED (#84) #132 gave `updateConnections` the lead on the clause "nothing else in the pipeline treats border cells differently at all". That clause was false, and the counter-example makes the lead stronger rather than weaker. A cliff's collision box reaches 3.371 tiles; a border cell's centre is 1.5-2.5 tiles from the chunk edge and every interior cell's is 5.5-6.5. So 16 of the 20 orientations cross a chunk edge from the outer ring and none can from anywhere else - a second border-only channel. And there is a rule on the far side of it: `Surface::checkTileCollisions` skips a tile whose id is 0, and `getEffectiveTileID` returns 0 for an absent chunk, so a box reaching into a not-yet-generated chunk reads no tile and does not collide. Scored on twelve regions already on disk, with the losing condition registered before the run - "a non-crossing border rate that is not lower than the crossing one refutes it": | population | unexplained | rate | | --- | --- | --- | | interior | 9 / 4484 | 0.20% | | border, box crosses an edge | 7 / 1407 | 0.50% | | border, box does not cross | 14 / 2479 | 0.56% | No-information predicts 7.6 crossing cells; observed 7. Refuted. Its own direction fails too: an absent chunk makes the game KEEP a cliff, so its signature is a false rejection with a blocking tile across an edge - 0 of 9, and the predicate is non-vacuous. The gain is a property the 2.91-sigma result did not have: the enrichment is orientation-BLIND, which is what a cell-index gate looks like. Both halves keep the 2.7x enrichment over the interior. `Cliff`'s connection system is also now decompiled whole. Three facts change what to try next: - `onDestroy`'s cascade has FOUR gates, including `map->[0x240] == 0` and an entity flag set from the creation parameters. Map generation satisfies all four; a Lua or editor probe need not - a hazard for exactly the runtime probe #127 asked for. - `crater-cliff` has no connections at all: the whole system is gated on `proto->place_as_crater == nullptr`. - There is no second connection pass during map generation. `updateAndFixConnections` is called from one site in the binary, `CliffEditor::buildCliffs`. No new fixtures, no shipping behaviour change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmitdaFJxN9Wm8LkQu33kc
This was referenced Aug 4, 2026
wormeyman
added a commit
that referenced
this pull request
Aug 4, 2026
…#84) (#135) #134 recorded `Cliff::onDestroy`'s four cascade gates and warned that a Lua or editor destroy "need not" satisfy them, which would make #127's runtime probe vacuous. **That warning is withdrawn.** It was stated from inference - "map generation evidently satisfies all four, because #113 measured the cascade running" - and inference is what keeps going wrong on this issue. | gate | cascade needs | `applyCliffs` | `luaCreateEntity` | | --- | --- | --- | --- | | entity flag bit 5 of `+0x6e` | clear | `params[0x80] = 0` | `params[0x80] = 0` | | `this+0x82` | set | ctor writes 1 unconditionally | same ctor | | `this+0x83` | set | `place_as_crater == nullptr` | same | | `map->[0x240]` | zero | only `Map::~Map` writes 1 | same | - Bit 5 is exactly `params[0x80] != 0` at construction. `applyCliffs` builds its params at `sp+0x20` and zeroes that byte with `str wzr, [x26]`, `x26 = params + 0x80` (`0x101623d9c`). `luaCreateEntity` builds its own at `sp+0x228` and zeroes the same byte on both paths (`0x1019e5ba8`, `0x1019e5c18`) - and that arm is identifiably the cliff one, storing the `0x14` sentinel into `params+0x87` before `LuaTable::getDefault<CliffOrientation>`. - `map->[0x240]` is "the map is being torn down": `Map::~Map` sets it as its first act, before `ToDeleteList::clear` (`0x10163461c`), and `Map::resume` bails on it. Dozens of unrelated `onDestroy` handlers read the same byte. `Cliff+0x78` is the `Map` - `Entity::getGame` is `[[this+0x78]+0x490]`. - The one path that WOULD differ, `Cliff::destroyWithoutCorrection`, is unreachable: zero direct callers under a scan of every `BL` AND `B` in `__text`, and no pointer in any vtable - only four in the STAB tables. The same widened scan re-confirms #134's other caller claim, which had been made on `BL` alone and could have missed a tail call: `updateAndFixConnections` still has exactly one caller, `CliffEditor::buildCliffs`. Documentation only - no code, no fixtures, no behaviour change. Claude-Session: https://claude.ai/code/session_01MmitdaFJxN9Wm8LkQu33kc Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 4, 2026
Merged
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.
#132 gave
Cliff::updateConnectionsthe lead on one clause: chunk borders are its entire domain, and "nothing else in the pipeline treats border cells differently at all". That clause was false. Finding the counter-example and killing it is what this PR does, and it leaves the lead stronger than it found it.The rival
Pure arithmetic over two things already in the repo. The largest half-extent in
CLIFF_ORIENTATION_COLLISION_BOXis 3.371 tiles, and:So 16 of the 20 orientations reach across a chunk edge from the outer ring and none can from anywhere else. Asserted against the real population, not left as arithmetic: 1407 crossing cells, every one on a border.
And a rule waits on the far side.
applyCliffsrejects throughSurface::wouldCollide(0x10160c088) ->constCollideWithTile(0x100732eec) ->Surface::checkTileCollisions(0x101b579e0), which per tile callsSurface::getEffectiveTileID(0x10049399c) and skips the tile when the id is 0.getEffectiveTileIDreturns exactly 0 for an absent chunk. A box reaching into a not-yet-generated chunk reads no tile and does not collide; this port reads the real tile everywhere.The discriminator, registered before the run
The two mechanisms differ in exactly one observable: whether the ORIENTATION matters.
updateConnections' gate is!onChunkBorder, a cell-index test blind to orientation. The box mechanism fires only when the box actually crosses. Losing condition written down first: a non-crossing border rate that is not lower than the crossing one refutes the box mechanism.Result: refuted, not marginally
Spread the 21 border cells over the two populations in proportion and the crossing share is 7.6; observed 7. Indistinguishable from no information, with the non-crossing rate the higher of the two.
Its own direction fails independently. An absent chunk makes the game KEEP a cliff we destroy, so its signature is a false rejection whose blocking tile lies across a chunk edge. 0 of 9, and the predicate is non-vacuous.
What it buys
The border enrichment is orientation-blind - what a cell-index gate looks like, not what a geometric reach looks like. That is a property the 2.91-sigma result did not have and could not get from the border statistic alone. Both halves keep the 2.7x enrichment over the interior, so the partition had something to find and found nothing. Every border-only channel this port can name is now scored; only
updateConnectionsis left, and #127 already showed why it cannot be scored from map-gen output.Seven engine facts from the same read
Cliff's connection system is decompiled whole. Three change what to try next:onDestroy's cascade has FOUR gates (0x1007a8854on): entity flag bit 5 of+0x6eclear,+0x82set,+0x83set,map->[0x240]zero. Map generation satisfies all four (feat(cliffs): the queue has a CONSUMER, and the wrong orientations are its cascade (#84) #113 measured the cascade running) but a Lua or editor probe need not - a live hazard for exactly the runtime probe test(cliffs): the chunk-border gate cannot be scored from any fixture (#84) #127 asked for.crater-cliffhas no connections at all. The whole system is gated onproto->place_as_crater == nullptr(cset w8, eqonproto+0xb90), andscaled_cliff_crateris the only thing that sets it.updateAndFixConnections(0x1007a94d0) is called from exactly one site in__text,CliffEditor::buildCliffs- found by scanning everyBLencoding. The "a later chunk cleans up after an earlier one" reading is closed.Also recorded:
destroyEndrefuses toforceDestroywhen entity flag bit 4 is set and leaves the orientation unchanged; after a shrink it re-searches its new bbox and destroys colliding non-cliff entities;getNeighborrequires an exact prototype-id AND position match.Scope
No new fixtures, no shipping behaviour change. Recall 0.9961 / precision 0.9858 unchanged.
pnpm run verifygreen - 182 files, 1507 tests.🤖 Generated with Claude Code
https://claude.ai/code/session_01MmitdaFJxN9Wm8LkQu33kc