Skip to content

test(cliffs): the residual is TWO defects and the field is NOT either (#84) - #93

Merged
wormeyman merged 1 commit into
mainfrom
cliffs/residual-splits-into-two
Aug 2, 2026
Merged

test(cliffs): the residual is TWO defects and the field is NOT either (#84)#93
wormeyman merged 1 commit into
mainfrom
cliffs/residual-splits-into-two

Conversation

@wormeyman

Copy link
Copy Markdown
Collaborator

Closes item 1 of #84 as stated (it named the wrong suspect), and re-scopes item 2.

What #91 handed over, and why it was wrong

The handoff named one lead - "the defect is in the grid-4 cliff-elevation field, the one input in the chain with no direct per-corner oracle" - and one next step: capture cliff_elevation at the 4-tile corner lattice.

The field is fine. So is everything else upstream of the crossing test. All measured, not read:

input verdict how
grid-1 vulcanus_elevation exact, worst 4.8e-2 all 12,675 captured corners
grid-4 multisample min-filter exact through the cliff generator at [0,0] and [1500,1500]; grid 1/2/8/centred all far worse
multisample(e,0,0) identity in this channel arm A ≡ arm B, cell for cell
cliffiness_basic exact, worst 6.4e-6 over the 4,266 unclamped corners
cliff_smoothing = 1 measured read back off the planet's surface, nothing overridden
smoothing stencil exact delta probe, both axes
crossesCliff exact disasm 0x10160c914
crossingsForChunk smoothing matches smoothingKnots disasm 0x10160c9cc (the notes' VA had moved)
fixImpossibleCells give-up never fires 0 chunks need a retry

The split

Force cliff_smoothing = 0, leave everything else real:

region s = 0 s = 1 (ships)
[0,0] 0 wrong 7
[-1200,800] 0 wrong, precision 1.0000 4
[1500,1500] 21 wrong 26

Two regions carry a smoothing-only defect; one carries a defect that survives smoothing being off - all over-detections, all at the high bands (670/790/1030), margins 0.69-46.6 elevation units, so not float32, and cliffiness there sits up to 1.0 clear of its gate.

Scoring [0,0] alone says "it's the smoothing". That is false for 21 of the 37. Two regions out of three agreeing is the same trap as #88.

Two things worth carrying forward

The old cliffiness_basic exoneration was two-thirds vacuous. cliffiness_basic is clamp(qmn,0,1)+0.5, and 8,409 of the 12,675 corners sit ON a clamp. Agreeing there says both sides clamped, nothing about qmn. The interior is 4,266 corners and agrees to 6.4e-6. Any "field X is exact" claim about a clamped expression has to report the interior separately.

cliff_smoothing = 1 was an inference until today - taken from the CliffPlacementSettings prototype default since #28, never read out of a running game. It is right, but that class of assumption is exactly what #28 was.

Re-scoping #84 item 2

"Over-placement below elevation 120" is not spread over the low band. In [0,0] every excess cell - at all 19 sweep levels, in both probe arms, and at real settings - sits in one contiguous blob (cells cx 43-48, cy 34-40). The game places zero cliffs there even when a completely synthetic cliff_elevation is routed onto it. A field-independent hole is a rule the port does not implement, not a field error. It is not lava either (most of those cells have no lava within 10 tiles).

That is the sharpest remaining lead.

Contents

Two oracle fixtures + capture functions + provenance, and test/cliffSmoothingModel.spec.ts. No src change - this is evidence, not a fix. The stencil spec's residual (4 cells, one place) is pinned by exact shape rather than bounded away, so a change in cause is visible.

🤖 Generated with Claude Code

https://claude.ai/code/session_015yqZhJCUVLDU5q7SVwAVkj

…#84)

#91 handed over one lead: "the defect is in the grid-4 cliff-elevation field,
the one input with no per-corner oracle". It is refuted, and so is the idea
that there is a single cause.

Everything upstream of the crossing test is now measured, not read:

- grid-1 `vulcanus_elevation` exact to 4.8e-2 over all 12,675 captured corners
- the grid-4 `multisample` min-filter reproduces the game through the CLIFF
  GENERATOR at both [0,0] and [1500,1500]; grid 1/2/8/centred all score far
  worse, and `multisample(e,0,0)` is the identity in this channel
- `cliffiness_basic` exact to 6.4e-6 - over the 4,266 UNCLAMPED corners.
  8,409 of 12,675 sit ON a clamp, so the old "exact" claim was two-thirds
  vacuous: it was measuring the clamp, not `qmn`
- `cliff_smoothing = 1` READ BACK off Vulcanus's own surface. It had been
  inferred from the prototype default since #28 and never once measured
- the smoothing stencil measured on both axes with a delta probe, whose
  in-chunk-3 arms predict the game places NOTHING and it places nothing
- `crossingsForChunk` re-disassembled at 0x10160c9cc (the VA in the notes had
  moved); `smoothingKnots` matches it line for line
- `fixImpossibleCells`'s give-up branch never fires: 0 chunks need a retry

With `cliff_smoothing = 0` and every other term real:

  [0,0]        0 wrong   (7 at s=1)
  [-1200,800]  0 wrong   (4 at s=1), precision 1.0000
  [1500,1500]  21 wrong  (26 at s=1)

So two regions carry a smoothing-only defect and one carries a defect that
survives smoothing being off - all over-detections, all at the high bands
(670/790/1030), margins 0.69-46.6 units, so not float32. Scoring [0,0] alone
says "it's the smoothing", which is false for 21 of the 37; two regions out of
three agreeing is the same trap as #88.

Also re-scopes #84 item 2: the "over-placement below elevation 120" is not
spread over the low band, it is one contiguous blob (cells cx 43-48, cy 34-40)
where the game places zero cliffs under every setting tried AND under a
synthetic cliff_elevation. A field-independent hole is a rule we do not
implement, not a field error - and it is not lava.

Two new oracle fixtures + provenance; no src change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yqZhJCUVLDU5q7SVwAVkj
@wormeyman
wormeyman merged commit fb7d899 into main Aug 2, 2026
2 checks passed
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>
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