feat(cliffs): port the tile-collision rejection (#18) - #72
Closed
wormeyman wants to merge 2 commits into
Closed
Conversation
`EntityMapGenerationTask::tryToAddCliff` tests the cell's orientation collision box against the tile mask grid and drops the entity on a hit. `generateCliffs` ignores the return value - no retry, no alternative orientation, no effect on neighbouring cells - so this is a pure post-filter on the emit loop, which is why it runs per-cell in any order and leaves tiled output byte-identical. `CliffBands.tileCollides` is the seam, planet-agnostic: a tile blocks a cliff when its CollisionMask shares a layer with the cliff's. The cliff mask holds `water_tile`, so that is lava on Vulcanus (`tile_collision_masks.lava()` sets it) and water on Nauvis. Vulcanus, against `oracle-vulcanus-cliff-entities.seed123456`: | region | ours | recall | precision | ratio | was | | --- | --- | --- | --- | --- | --- | | [0,0] | 317 | 0.784 | 0.700 | 1.120 | 335 / 0.806 / 0.681 / 1.184 | | [1500,1500] | 888 | 0.933 | 0.930 | 1.003 | 1065 / 0.938 / 0.779 / 1.203 | | [-1200,800] | 371 | 0.853 | 0.922 | 0.925 | 375 / 0.853 / 0.912 / 0.935 | Region 1 - the case the rule was found on - drops 173 false positives against 4 true ones and lands within 0.3% on count. Regions 0 and 2 barely move, so this is not the whole of #18. Recall gets WORSE in two regions and that is reported rather than smoothed: 6 true positives lost at [0,0] and 4 at [1500,1500] are cells where the game placed a cliff and our tile resolver puts lava in its box. The resolver is ~98.2% accurate overall and is plausibly worse at a lava boundary, but that is untested and is the first thing to check before suspecting the collision geometry. Nauvis keeps its cheap paint-time water-colour skip rather than resolving tiles, which is only sound while no Nauvis cliff's box touches water. That was a remembered one-off measurement; `test/cliffPlacement.spec.ts` now runs the real rejection and asserts the placed list is unchanged, with three non-vacuity checks - the predicate ran, it found no water under any cliff, and the same resolver does find water elsewhere in the region. `renderVulcanusCliffs` now takes the composite's `sharedStack` like `renderVulcanusRocks` does, and `makeVulcanusCliffFields` grows the same optional-shared-stack seam as `makeVulcanusRockFields`; without it the tile resolver would rebuild the whole field DAG, since `memoXY` is single-entry and separate copies share nothing. Also corrects the header of `renderVulcanusCliffs`, which asserted the opposite of what the game does ("Lava plays that visual role but is not a water tile, and the game does not exclude cliffs from it here") - wrong in both halves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012cd6B2MKoJF5W2T175BDoe
Two arms of the vulcanus block, same machine, back to back, with and without `tileCollides`: | figure | without | with | delta | | ------------------------- | ------- | ----- | ----- | | terrain (the control arm) | 3948 | 3947 | -0.0% | | cliffs marginal | 1500 | 1917 | +28% | | ratio all/terrain, whole | 1.944 | 2.040 | +4.9% | | ratio all/terrain, TILED | 2.455 | 2.569 | +4.6% | Terrain landing within 1 ms across the arms is what makes the number readable - this file's own header records a 4.8% baseline drift between runs, which would otherwise swamp a change this size. It puts the whole-image "under 2x terrain" gate back over the line, 1.944 -> 2.040. Recorded rather than buried, as the same gate was when the Vulcanus V3 overlays crossed it. The tiled figure - the geometry the app actually renders - was already over at 2.455 and is now 2.569. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012cd6B2MKoJF5W2T175BDoe
Collaborator
Author
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.
Stacked on #71 - base is
feat/cliff-orientation-tables, so review or merge that one first. GitHub will retarget this tomainautomatically once #71 lands.Ports the rejection #71's tables exist for.
EntityMapGenerationTask::tryToAddClifftests the cell's orientation collision box against the tile mask grid and drops the entity on a hit;generateCliffsignores the return value - no retry, no alternative orientation, no effect on neighbouring cells - so this is a pure post-filter on the emit loop. That is what makes it safe to run per-cell in any order, and tiled output stays byte-identical (tiledEquality.spec.tsstill passes).Result
Vulcanus, against
oracle-vulcanus-cliff-entities.seed123456:[0,0][1500,1500][-1200,800]Region 1 - the case the rule was found on - drops 173 false positives against 4 true ones and lands within 0.3% on count. Regions 0 and 2 barely move, so this is not the whole of #18.
Recall gets worse in two regions, and that is reported rather than smoothed. 6 true positives lost at
[0,0]and 4 at[1500,1500]are cells where the game placed a cliff and our tile resolver puts lava in its box. The resolver is ~98.2% accurate overall and is plausibly worse at a lava boundary, but that is untested - it is the first thing to check before suspecting the collision geometry.Nauvis
Kept on its cheap paint-time water-colour skip rather than resolving tiles. That is only sound while no Nauvis cliff's collision box touches water, which was a remembered one-off measurement. It is now a standing check:
test/cliffPlacement.spec.tsruns the real rejection and asserts the placed list is unchanged, with three non-vacuity guards - the predicate ran, it found no water under any cliff, and the same resolver does find water elsewhere in the region. If it ever fails,NAUVIS_CLIFF_BLOCKING_TILESis what to pass the placement.Cost
Two arms, same machine, back to back:
Terrain landing within 1 ms across the arms is what makes this readable - the bench's own header records a 4.8% baseline drift between runs.
It puts the whole-image "under 2x terrain" gate back over the line, 1.944 -> 2.040. Recorded rather than buried, as the same gate was when the Vulcanus V3 overlays crossed it. If it needs to come back down, the cheap lever is a "could lava possibly win here" pre-gate in front of the full 19-tile argmax - scoped and deliberately not taken here.
Plumbing
renderVulcanusCliffsnow takes the composite'ssharedStacklikerenderVulcanusRocksdoes, andmakeVulcanusCliffFieldsgrows the same optional-shared-stack seam asmakeVulcanusRockFields. Without it the tile resolver would rebuild the whole field DAG -memoXYis single-entry, so separate copies share nothing.Also corrects that file's header, which asserted the opposite of what the game does ("Lava plays that visual role but is not a water tile, and the game does not exclude cliffs from it here") - wrong in both halves.
pnpm run verifygreen: 143 test files, 1268 tests.🤖 Generated with Claude Code
https://claude.ai/code/session_012cd6B2MKoJF5W2T175BDoe