Skip to content

test(cliffs): the far ten were DESTROYED, not never queued (#84) - #122

Merged
wormeyman merged 1 commit into
mainfrom
test/cliff-far-ten-provenance
Aug 3, 2026
Merged

test(cliffs): the far ten were DESTROYED, not never queued (#84)#122
wormeyman merged 1 commit into
mainfrom
test/cliff-far-ten-provenance

Conversation

@wormeyman

Copy link
Copy Markdown
Owner

#115 handed over two threads on the far ten and named the first one unmeasured:

The whole applyCliffs framing assumes destruction. #114's exact result is consistent with destruction but does not prove it - a strict superset says nothing about whether the game's crossing field emitted them at all.

It is decidable, and it needed no capture. The two hypotheses leave different marks on the neighbours, and both rules were already ported off the disassembly:

hypothesis what happens to a connected neighbour
destroyed by applyCliffs Cliff::onDestroy calls destroyEnd(opposite(side)) - unconditionally
never queued by generateCliffs the cell is simply absent; the neighbour loses that end only if updateConnections runs on it, which is gated on the neighbour sitting on its chunk's outer ring

So a disputed cell is decidable whenever it has a neighbour that is in the game's kept set, not on a chunk border, and queued with an end facing it. Then the game's own recorded orientation settles it.

2 of the far ten are decidable, and both say DESTROYED

cell neighbour the game's orientation verdict
1546,1550.5 1546,1546.5 north-to-none end gone - destroyed
1746,1538.5 1746,1542.5 east-to-none end gone - destroyed

Across all 225 cells the game destroys, the never-queued signature appears zero times. One decidable cell sits in each of the far group's two multi-cell clusters - the 1542/1546, 1550.5..1558.5 knot and the 1746, 1530.5..1538.5 vertical run - which is the argument for reading both clusters as destruction events. The two singletons are in neither.

The counterfactual is what makes it more than reading a table. Re-run the model with the cell removed from the QUEUE rather than destroyed in it:

arm result
the game's destruction set (baseline) 283 / 861 / 387, wrong 0
1546,1550.5 never queued wrong 1 - north-to-south vs the game's north-to-none
1746,1538.5 never queued wrong 1 - east-to-north vs east-to-none
both never queued wrong 2
four far cells with no decidable neighbour wrong 0 - unchanged

The last row is the contrast arm: this is a property of those two cells, not something removing any cell would do.

#114's exactness covers 14 of 225, and is VACUOUS for 211

The finding to carry forward. Only 14 of the 225 destroyed cells have a decidable neighbour at all - the rest have neighbours the game also destroyed, neighbours on a chunk border, or no facing end. 1531 of 1531 must not be quoted as having confirmed destruction for the whole 225.

group n decidable destroyed never queued
far (no lava within 12) 10 2 2 0
near (within 2) 9 2 2 0
mid (4 to 11) 6 0 - -
agreed 200 10 10 0

Those 14 are exactly #114's 14 no-cascade rewrites, cell for cell. A neighbour is observable precisely when the cascade is the only thing that would have trimmed it, so the two routes must agree - which is what says the decision rule was read out of cliffConnections.ts rather than fitted to the answer.

The result is CONDITIONAL on the chunk-border gate, and that is new

The discriminator is a reading of applyCliffs' fifth-argument test, and cliffConnections.spec.ts records that gate as explicitly unscored - updateConnections fires zero times on the port's own set, so everyCell had never changed an answer.

It stays unscored. What changed is that it is no longer inert: removing a cell from the queue creates the dangling end the port's own set never has, and the gate then decides whether the neighbour trims it. With the gate off, both counterfactuals go to wrong 0 and the hypotheses become indistinguishable. The baseline is identical either way, so nothing before this could have caught a misreading.

This is the first thing that has ever depended on that gate. Planting onChunkBorder returning true fails 7 of this file's 10 tests. If the gate was misread, those two cells revert to undecidable - they do not become never-queued. Scoring it against the game is now worth doing on its own account.

Where it leaves #84

Destruction is the mechanism, so the search stays on the unmodelled half of Surface::wouldCollide and not on a crossing-field difference - worth knowing before anyone re-opens the field, which #107 already exonerated once.

Verification

pnpm run verify green - 1449 app (up 10) + 13 worker + 3 container tests, 0 warnings, exit 0. Measurement only; nothing shipping changed, recall 0.9961 / precision 0.9858 unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_015jeznBmWiZywUc7cBw9xFU

#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.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jeznBmWiZywUc7cBw9xFU
@wormeyman
wormeyman merged commit 9c26aa0 into main Aug 3, 2026
6 checks passed
@wormeyman
wormeyman deleted the test/cliff-far-ten-provenance branch August 3, 2026 23:54
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>
wormeyman added a commit that referenced this pull request Aug 4, 2026
…e destroy stage (#84) (#124)

The section before this put 11 of the 25 missed destructions on the ore rule
and named widening it as the next move. The mechanism was worth one more look
first, because `vulcanusOreRejection.ts` has always said the rule is
characterised rather than ported. It should NOT be widened.

Entity collision is excluded, now VERIFIED by three routes rather than asserted
from the call ordering alone:

1. Order - `computeInternal` calls `generateCliffs` at +0x2c, before it even
   builds the NoiseCache the three `generateEntities` passes use; `apply` calls
   `applyCliffs`, `applyDecoratives`, `applyEntities` in that order.
2. Inputs - `generateCliffs`' entire call list is `crossingsForChunk`,
   `MaybeCliffOrientation::value`, `tryToAddCliff`. No resource input at all.
3. Masks, at the PROTOTYPE level rather than the type default - calcite,
   tungsten-ore and sulfuric-acid-geyser are all `type = "resource"` and none
   overrides `collision_mask`, so all take `{layers={resource=true}}` against
   the cliff's `{item, meltable, object, player, water_tile, is_lower_object,
   is_object, cliff}`. Disjoint.

Route 3 kills the variant nobody had written down: CROSS-CHUNK ordering. Chunk
N's entities really are on the surface before chunk N+1's cliffs are applied,
so "the resource was already there" is available as an escape - and it still
cannot matter, at any box size, because the masks never intersect.

The one entity-versus-cliff test that exists runs the other way: `applyEntities`
calls `Surface::mapGeneratorWouldCollide` per queued entity and SKIPS the entity
on a hit. It never destroys a cliff.

The stage is measured: applying #122's discriminator to the lever's 31 cells,
exactly one is decidable and it says DESTROYED (`1546,1550.5`, a geyser cell,
neighbour `1546,1546.5` at `north-to-none`). So the effect enters at
`applyCliffs`/`Surface::wouldCollide`, not at `crossingsForChunk`. n=1 - the
spec says so, and carries the contrast arm showing the other 30 cost nothing.

Consequence: widening the box would fit a shape to an unexplained effect rather
than model a code path - exactly what #88 shipped and #90 had to undo. Close the
recall gap by finding what the resource control actually changes.

Also records `Surface::mapGeneratorWouldCollide` (0x101624a44), which was in
neither notes file and is distinct from both other wouldCollide overloads.

Measurement only - nothing shipping changed.


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
…#84) (#127)

#122 turned `applyCliffs`' fifth-argument test - `updateConnections` runs on the
chunk's outer ring and nowhere else - from an inert reading into the thing its
destroyed-versus-never-queued verdict rests on, and said scoring it was now
worth doing on its own account. This is that attempt. It is negative, and the
reason is worth more than the attempt.

The test that ought to work: a cliff end pointing at an absent cell is a
DANGLING END. If `updateConnections` ran everywhere there could be none; if it
runs only on the outer ring, one could survive on a non-border cell. So the
game's own output should separate the readings with no new capture.

It does not, because there are none at all. Over THIRTEEN arms from all three
Vulcanus cliff fixtures - real settings and the collapsed rule, resources on and
off, all four regions, 2785 border and 3750 interior cells judged - zero cells
have a dangling end, in either population, in any arm. The detector is not
asleep: deleting one cell from each arm makes the same code report one
immediately, in all thirteen.

Why: every removal mechanism preserves connection consistency. A destruction
runs `Cliff::onDestroy`, which trims the neighbour's facing end;
`updateConnections` trims dangling ends by definition; and the crossing field
never emits one. So BOTH readings of the gate predict exactly what the game
shows, and no capture of map-generation output can separate them however many
regions it covers.

Consequence for #122: its conditional stands and cannot be discharged with what
is on disk - and not for want of data. Map generation never produces the world
that would settle it, because it never truncates a run without the cascade
running. Anyone revisiting needs a different KIND of evidence - the disassembly,
or a runtime probe that destroys a cliff outside map generation - not another
region.

Also pins connection consistency of the game's cliff output across every
capture, which nothing asserted before.


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
… is on chunk borders (#84) (#131)

Two things were stuck at a sample size rather than at an idea. The unexplained
population was 14 cells, and every structural test on it landed at 1.4-1.9 sigma
against its base rate - what a partition looks like at n=14 whether or not a
cause exists. And the shipped accuracy figure was measured on THREE regions,
chosen years into this investigation for reasons unrelated to sampling.

Four more regions with the ore lever, ~2.5s a capture.

THE PORT GENERALISES, measured where it was never fitted:

|                     | original 3 | the 4 new |
| ---                 | ---        | ---       |
| raw cells           | 1756       | 2789      |
| game cliffs         | 1531       | 2590      |
| raw a strict SUPERSET | yes      | yes, all four |
| predicate precision | 0.971      | 0.9877    |
| predicate recall    | 0.889      | 0.8090    |
| false rejections    | 6          | 2         |

[3000,3000] is exact - 362 raw, 362 game, nothing to explain - and is also the
one region with no resource entity at all, which is what every result from #123
onward predicts.

THE CHUNK-BORDER ENRICHMENT REPLICATES OUT OF SAMPLE:

|                | unexplained | on border  | base rate |
| ---            | ---         | ---        | ---       |
| original 3     | 14          | 9 (64.3%)  | 45.0%     |
| the 4 new      | 13          | 9 (69.2%)  | 47.2%     |
| combined       | 27          | 18 (66.7%) | ~46.3%    |

Read the status before the number. The replication is 1.59 sigma alone and the
combined figure ~2.1 - short of decisive, and this is a LEAD not a result. What
changed is the KIND of evidence: the hypothesis was formed on the original 14,
dismissed there as noise at 1.45 sigma, and is tested here on 13 cells captured
afterwards in regions chosen before any of them was known.

Why it matters: chunk borders are `updateConnections`' entire domain - the one
rule that treats border cells differently, which our port measures firing ZERO
times and models as an UPPER bound. #122 promoted its gate from inert to
load-bearing; #127 showed it cannot be scored from map-gen output. This is the
first positive evidence that it does anything.

And n is 27 now, which is what unblocks the next attempt: at 14 nothing could be
settled; at 27 the border question is answerable with one more capture round.

New fixture + PROVENANCE entry.


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
…, 2.91 sigma (#84) (#132)

#131 reported a chunk-border enrichment as a lead and said one more capture
round would settle it. It did.

| batch                 | unexplained | on border  | base  | z    |
| ---                   | ---         | ---        | ---   | ---  |
| original 3 regions    | 14          | 9 (64.3%)  | 45.0% | 1.45 |
| +4 regions (#131)     | 13          | 9 (69.2%)  | 47.2% | 1.59 |
| +8 regions (here)     | 17          | 12 (70.6%) | 46.0% | 2.03 |
| combined, 15 regions  | 44          | 30 (68.2%) | ~46.3%| 2.91 |

p is about 0.002. This is a RESULT, not a lead.

THE PREDICTION WAS REGISTERED BEFORE THE CAPTURE, in
`captureVulcanusCliffEntitiesBorderBatch`'s own doc comment: ~26 more
unexplained cells with ~17 on a border if the enrichment is real at ~66%,
combined ~2.9 sigma - and a FALL back toward the base rate if it is noise. It
came back 12 of 17, at a higher rate on fewer cells, combined landing on 2.91.
Effect size stable across all three batches (64-71%) against a stable base rate
(45-47%).

The first row was correctly dismissed as noise when it was all there was. Its
value came from being written down so it could be re-tested once n grew.

WHY IT POINTS AT `updateConnections`: chunk borders are that rule's entire
domain - `applyCliffs` gates it on `tryToAddCliff`'s fifth argument, which is
`!onChunkBorder`. Nothing else in the pipeline treats border cells differently.
And it is the rule the port has the weakest grip on: measured firing ZERO times
on our own set, modelled as an UPPER bound (the one place the port is knowingly
not a transcription), promoted from inert to load-bearing by #122, and shown by
#127 to be unscoreable from map-gen output. This is the first positive evidence
it does anything.

WHAT IT DOES NOT SAY: not that those 30 are destroyed BY updateConnections. An
enrichment on a rule's domain is not the rule firing, and 14 of the 44 are not
on a border at all - if this is one cause it is not the only one.

The port also generalises: 5581 raw / 5134 game cliffs, strict superset in all
eight regions, precision 0.9818, recall 0.8434. Two regions have no unexplained
cells and [-1600,3200] reproduces the game exactly (686 = 686), so the residual
is concentrated rather than a background rate - itself a constraint on any
mechanism proposed for it.

New fixture + PROVENANCE entry; capture ~31s.


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