test(cliffs): the "excess" was the lava rejection, not a second defect (#84) - #86
Merged
Merged
Conversation
#84) #84 opened with two symptoms and they were one measurement error: every arm compared OUR placement, which did not run tryToAddCliff's lava-collision rejection, against the GAME's, which always does. On Vulcanus the lava is the basalt lakes, so the missing deletion rule's effect lands exactly in the low-elevation range where the residual sat. Item 1 - the 187-cell excess. Turning the rejection on removes 198 cells across the three oracle regions: 185 false positives against 13 true. | | game | ours | matched | recall | precision | wrong orient | | -------------- | ---- | ---- | ------- | ------ | --------- | ------------ | | no rejection | 1569 | 1756 | 1531 | 0.9758 | 0.8719 | 37 = 2.42% | | with rejection | 1569 | 1558 | 1518 | 0.9675 | 0.9743 | 31 = 2.04% | Region [0,0] reaches precision exactly 1.000. The port goes from over-placing 12% to under-placing 0.7%. Item 2 - the surviving regime split. The low/high over-placement gap of 0.067 read as a second-order error in the same multisample term. Running both sides with the rejection collapses it to 0.018, and the low regime straddles 1.0 instead of sitting above it (level 20: 1.085 -> 0.988). There is no second multisample defect to find. What is actually left is a TILE boundary, not a cliff field. The rejection costs recall in the same regime (0.951 at level 20 -> 1.000 at 140+), and every contradiction sits at Chebyshev depth 1 in our lava - our own perimeter, never deeper: 32/32 at level 20, 52/52 across the sweep, 13/13 at default settings. Stated carefully in the notes, because depth discriminates at default settings (region 1's 170 correct rejections span depth 1-9, 45 deep in lava) and does NOT at level 20, where the correct rejections are 32/32 perimeter too. - vulcanusCliffEntities.spec.ts: new table; guards tightened from recall>0.77/precision>0.68/ratio<1.15 to 0.95/0.94/1.05. - cliffOrientationOracle.spec.ts: bound 175 -> 37, floor 1350 -> 1500 so a change that shrinks both has not fixed anything, it has stopped comparing. Records why this arm deliberately omits the rejection. - vulcanusElevationLevels.spec.ts: new arm attributing the split, with a non-vacuity guard on cells actually removed (verified by planting a no-op rejection: it fails with "expected 0 to be greater than 100"). - Docs: banners carry the shipping-path row; corrected the Vulcanus rendering note that claimed the game does not exclude cliffs from lava - inferred from the absence of a water TILE rather than measured, and wrong since #71/#73. - Cleared a pre-existing unused-import warning in cliffResidual.spec.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt
This was referenced Aug 2, 2026
wormeyman
added a commit
that referenced
this pull request
Aug 2, 2026
…#84) (#87) #83 left an assumption behind: that cliffs are the only consumer reading a multisample-bearing field on a grid other than 1 tile. That was never measured, which is exactly the status the cliff case itself had before #83. Closes #84 item 4. The surface is much smaller than "audit every consumer", and that is the first finding. multisample appears in factorio-data @ 2.1.12 in exactly ONE place - vulcanus_basalt_lakes_multisample, used only by vulcanus_elev. No other planet uses the primitive. So the audit reduces to "who consumes Vulcanus elevation": | consumer | reads elevation? | grid | how | | ------------------------- | ---------------- | ---- | ---------------- | | cliff generator | yes | 4 | measured, #83 | | tile generator (19 range) | yes, via elev | 1 | measured, here | | vulcanus_temperature | yes | 1 | same program | | tungsten/coal/calcite/SA | no | n/a | read the Lua | | rocks, geyser | no | n/a | read the Lua | The resource row had to be checked rather than assumed, because the generic path DOES couple to elevation: starting_resources_lake_mask = clamp((elevation-1)/10, 0, 1) feeds starting_patches' spot_favorability_expression, and CLAUDE.md flags that coupling as exactly what changed at 2.1.9. Vulcanus does not take that path - each of its four resources uses its own vulcanus_*_region expression, and those four definitions contain 0 references to elevation. The tile generator reads grid 1, measured against the game. Substituting the cliff generator's 4-tile elevation into the tile resolver, over the 381 oracle positions: | | grid 1 (ships) | grid 4 | | ------------------------ | -------------- | ------- | | tile-name agreement | 0.9816 | 0.8609 | | lava misclassifications | 0 / 381 | 27 / 381 | 46 tiles named wrongly, and the binary lava call - the only thing the cliff collision rejection reads - goes from exact to 27 wrong. The metric is demonstrably sensitive to the swap, which is the part worth insisting on: PR #57's substitution failed precisely because "nothing changed" could not be distinguished from "the substitution never ran". Both arms carry that guard explicitly. Side result: this clears the lava perimeter that costs 13 real cliffs their placement (#86). Reading the other elevation channel makes lava dramatically worse, not better, so the perimeter error is not a channel mistake - it is somewhere else in vulcanusCatalog. Also asserts the max(-500, ...) clamp never bites at these positions, so the swap is exactly "the same field at grid 4" rather than something subtly else. Claude-Session: https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
wormeyman
added a commit
that referenced
this pull request
Aug 2, 2026
#84) (#88) The lava perimeter that cost 13 real Vulcanus cliffs their placement was never the lava mask. It was the collision box's shape. rotbb(x, y, size, intersect) (entity-util.lua:9) returns {{cx-x_dist, cy-y_dist}, {cx+x_dist, cy+y_dist}, 1/8} - a rectangle PLUS an orientation of 1/8, i.e. 45 degrees. Sixteen of the twenty cliff orientations are built with it; only the four straight ones are axis-aligned. The port kept only the bounding box, which overruns the rotated rectangle at all four corners, so it rejected cliffs on lava the game never looks at. CLIFF_ORIENTATION_COLLISION_BOX remains the correct BROAD phase - wouldCollide derives its tile rect from a fixed-point floor over an inclusive rectangle, which cliffCollisionTileBox reproduces. cliffBoxCoversTile adds the narrow phase (separating-axis over the two world axes and the rectangle's own two). | | AABB (before) | oriented (after) | | ------------------- | ------------- | ---------------- | | recall | 0.9675 | 0.9758 | | precision | 0.9743 | 0.9727 | | [0,0] recall | 0.9788 | 1.0000 | | level-sweep recall | 0.951 @ lvl20 | ~1.000 everywhere | Clears 13 of 13 false rejections while keeping 182 of the 185 rejections that remove genuine false positives - the correct shape, not a loosening that trades precision for recall. How it was found, because the route matters more than the fix. The standing explanation - in this spec's own comments and in the notes - was that the tile resolver was "off by about one tile SOMEWHERE". A new dense capture (oracle-vulcanus-lava-boundary, radius-4 neighbourhoods around the 35 accusing tiles) found ZERO lava mismatches over 994 positions and 35/35 at the accusing tiles themselves. The mask was innocent. It was worth capturing because the existing 381-position survey structurally could not answer the question: its sensitivity was measured by planting scale factors on lava's probability, and 1.02 and 1.2 both still pass. Two corrections this forces: - PR #86's "gap 0.067 -> 0.018" is wrong; it is 0.024. That was measured with the over-aggressive AABB rejection, which deleted cells the game keeps and so flattered exactly the ratio it reported. A too-strong correction hides the thing it is correcting. The residual below elevation 120 is real, still open, and now pure over-placement with no recall cost. - "All 13 sit at Chebyshev depth 1 in our lava" was a true measurement that pointed at the wrong suspect. The box's four corners ARE its perimeter, so a corner-shaped box error produces the same signature a one-tile-fat mask would. A statistic can only rule a suspect out if it would come out DIFFERENTLY for each candidate. Checked and cleared on the way: the inclusive-floor fringe is real engine behaviour (disassembly), and chunk ordering is not it (2 of 13 cross a chunk boundary against 50 of 185 in the control - less than baseline). 6 of the 13 also carry a wrong orientation (~23x enrichment over the 2.0% base rate), always a smaller -to-none variant of ours; those belong to the standing orientation residual, which is unchanged. Guards: cliffOrientedBox.spec.ts pins the geometry, and the levels sweep asserts per-level recall > 0.99. Both verified non-vacuous by planting AABB behaviour - they fail with "expected 0 to be greater than 20" and "expected 0.9514 to be greater than 0.99". Claude-Session: https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 2, 2026
wormeyman
added a commit
that referenced
this pull request
Aug 2, 2026
cliffOrientationOracle.spec.ts counts the residual and bounds it. This pins what it looks like, because the shape is the lead and a change in shape is a change in cause even when the count holds. Measured after #83 (multisample grid), #86 (lava rejection) and #90 (raw collision box): - 37 of 1531 matched cells, and all 37 differ in EXACTLY ONE edge. Not one two-edge difference survives; before #83 the dominant mode was two edges (125 of 175), a whole corner on the wrong side of a band. - Every one is an OVER-detection: the game reports a -to-none orientation and the port reports a crossing on that edge, never the reverse. - Spread over all four edges (L11/R6/T7/B13) and all three regions (7/26/4), so it is not a directional off-by-one. Two candidate causes are already eliminated, which is why the shape is worth pinning rather than re-deriving next session: - crossesCliff is EXACT. Disassembled at 0x10160c914 under 2.1.12 (the VA in cliffs-NOTES.md had moved); cliffPlacement.ts reproduces it line for line including the a<0||b<0 early-out, the boundary<e0 check, and the strict >0.5 gate and strict crossing comparisons. No >=-vs-> slip exists to find. - cliffiness_basic is EXONERATED. Substituting the game's own corner cliffiness leaves the count at exactly 37/1531. So the residual is in the grid-4 cliff-elevation field - the one input in the chain with no direct per-corner oracle. A single-edge, strictly one-directional over-detection is what a small positive field offset looks like. The direction assertion is the load-bearing one: if under-detections ever appear, the cause has changed and that reading is dead. Claude-Session: https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt 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 items 1 and 2 of #84.
Both symptoms turned out to be one measurement error. Every arm compared our placement, which did not run
tryToAddCliff's lava-collision rejection, against the game's, which always does. On Vulcanus the lava is the basalt lakes, so the missing deletion rule's effect lands exactly in the low-elevation range where the residual sat.Item 1 - the 187-cell excess
Turning the rejection on removes 198 cells across the three oracle regions: 185 false positives against 13 true.
Region
[0,0]reaches precision exactly 1.000. The port goes from over-placing 12% to under-placing 0.7%.Item 2 - the surviving regime split
The low/high over-placement gap of 0.067 read as a second-order error in the same
multisampleterm. Running both sides with the rejection collapses it:cliff_elevation_0Gap 0.067 -> 0.018, and the low regime straddles 1.0 instead of sitting above it. There is no second
multisampledefect to find.What is actually left is a TILE boundary, not a cliff field
The rejection costs recall in the same regime (0.951 at level 20 rising to 1.000 at 140+). Each real cliff is a negative-space oracle - the game ran this rejection and kept it - and every contradiction sits at Chebyshev depth 1 in our lava, our own perimeter, never deeper: 32/32 at level 20, 52/52 across the sweep, 13/13 at default settings.
Stated carefully in the notes, because depth discriminates in one of the two places it was checked and not the other. At default settings it does: region
[1500,1500]'s 170 correct rejections span depth 1 to 9 with 45 bottomed out deep in lava, against wrong rejections that are 100% perimeter. At level 20 it does not - the correct rejections are 32/32 perimeter too. So the supported claim is that these errors are boundary-sited in both directions, not that depth alone proves the perimeter is one tile fat. That is avulcanusCatalogquestion.Changes
vulcanusCliffEntities.spec.ts- new table; guards tightened from recall>0.77 / precision>0.68 / ratio<1.15 to 0.95 / 0.94 / 1.05.cliffOrientationOracle.spec.ts- bound 175 -> 37, floor 1350 -> 1500 so a change that shrinks both has not fixed anything, it has stopped comparing. Records why this arm deliberately omits the rejection (larger comparison set).vulcanusElevationLevels.spec.ts- new arm attributing the split, with a non-vacuity guard on cells actually removed. Verified by planting a no-op rejection: fails withexpected 0 to be greater than 100.cliffResidual.spec.ts, sovp checkis back to zero warnings.pnpm run verifygreen: 1307 passed / 3 skipped, 0 warnings.🤖 Generated with Claude Code
https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt