Skip to content

feat(cliffs): capture cliff_orientation, and localise #18 to the crossings (#18) - #75

Merged
wormeyman merged 1 commit into
mainfrom
feat/cliff-orientation-oracle
Jul 30, 2026
Merged

feat(cliffs): capture cliff_orientation, and localise #18 to the crossings (#18)#75
wormeyman merged 1 commit into
mainfrom
feat/cliff-orientation-oracle

Conversation

@wormeyman

Copy link
Copy Markdown
Owner

Does both follow-ups from #74.

1. The fixtures now carry cliff_orientation

The cliff dumps recorded only {x, y, name}. Both now record each entity's LuaEntity.cliff_orientation (a 20-value string union whose order matches CLIFF_ORIENTATION_NAMES exactly, checked). placedCells returns the crossing code it already computed, so the comparison scores the shipping path rather than a parallel re-derivation.

Re-capturing reproduced every prior position exactly - Nauvis 282/52 in the same order, Vulcanus 283/885/409 - so only the new column changed. Nauvis's fixture moves from a 2.1.11 capture to 2.1.12 in the process, which incidentally shows Nauvis cliff placement did not move between those versions.

CLIFF_CODE_TO_ORIENTATION is confirmed against the game: all 334 Nauvis cliffs match exactly. Its only previous check was against the same jump table it was read from, where a misread and a mistranscription would have agreed.

2. Vulcanus does not match - #18, seen up close

region matched wrong orientation
[0,0] 228 68 = 29.8%
[1500,1500] 830 67 = 8.1%
[-1200,800] 342 40 = 11.7%
total 1400 175 = 12.5%

Since the table is right, these are disagreements about the four crossings. The dominant failure is exactly two edges differing (125 of 175) - one of the cell's two crossings on the wrong side, i.e. a single corner on the wrong side of a band boundary. Errors spread evenly over L:87 R:80 T:87 B:89, so it is not a directional off-by-one.

A cell can land in the right place for the wrong reason, and 175 do. Counts could never show that.

Three causes tested against the new metric; all three fail

3. Binary reading, recorded in cliffs-NOTES.md

  • generateCliffs' full body (0x1016229b4, 1080 bytes, decompiled whole). Confirms the code packing including both edge-array strides; confirms toMaybeCliffOrientation is inlined with exactly the <0x51 jump table plus explicit 0xC0/0xC1/0xCC/0xF0 compares storing (orientation << 32) | 2 → 12/8/4/0, agreeing with our table entry for entry; and confirms the centre formula grid_offset + chunk*32 + c*grid + (grid >> 1) - so grid_offset is applied to the centre and nowhere else, which is fix(cliffs): sample the fields at the game's lattice - Nauvis is now EXACT (#18) #70's finding from the other side.
  • tryToAddCliff takes a fifth argument and it is !onChunkBorder - previously unmodelled. It does not gate the collision test, and measurement says it does not gate placement either.
  • tryToAddCliff has two paths and the earlier note described only one. It branches on a mode byte at this + 0x10; mode == 2 does the collision test, otherwise none. They are sequential, not alternatives - on a miss the collision path falls through into the same tail, which appends a 16-byte record to a vector, storing that fifth argument at +0xc. Our tileCollides post-filter models the mode == 2 path, which is the one map generation takes.
  • crater-cliff is an entity autoplace, not a lattice cliff - confirmed rather than assumed, from decoratives-vulcanus.lua:2776 (scaled_cliff_crater, probability_expression = "crater_cliff") and planet-map-gen.lua:122. Excluding it is correct.

The gap this exposes, and the next capture

No field capture covers [0,0]. The corner-fields fixture's three regions are [1500,1500], [1100,2600] and [-1700,1900] - all calcite regions chosen for issue #24. So "the fields are exact" has been measured where the port is already good (8.1%) and never where it is worst (29.8%). Capturing corner fields over [0,0] and [-1200,800] is the next step, and it is now a sharp experiment rather than a fishing trip: the orientation metric will answer it at 4 bits per cell.

pnpm run verify green: 303 files checked, 1272 tests.

🤖 Generated with Claude Code

https://claude.ai/code/session_013eczw9uoWDfN6Wc6kA7UBY

…sings

The cliff dumps recorded only {x, y, name}. Both now carry each entity's
`cliff_orientation`, and `placedCells` returns the crossing code it
already computed, so the comparison scores the shipping path rather than
a parallel re-derivation.

Re-capturing reproduced every prior position exactly - Nauvis 282/52 in
the same order, Vulcanus 283/885/409 - so only the new column changed.
Nauvis's fixture moves from a 2.1.11 capture to 2.1.12 in the process,
which also shows Nauvis cliff placement did not move between those
versions.

CLIFF_CODE_TO_ORIENTATION is now confirmed against the game: all 334
Nauvis cliffs match exactly. Its only previous check compared it against
the same jump table it was read from, where a misread and a
mistranscription would have agreed.

Vulcanus does not match, and that is issue #18 seen up close. Over the
1400 cells the port and the game both place, 175 have the wrong
orientation - 29.8% at [0,0], 8.1% at [1500,1500], 11.7% at [-1200,800].
Since the table is right, those are disagreements about the four
CROSSINGS. The dominant failure is exactly two edges differing (125 of
175): one of the cell's two crossings on the wrong side, i.e. a single
corner on the wrong side of a band boundary. Errors spread evenly over
L:87 R:80 T:87 B:89, so it is not a directional off-by-one. A cell can
land in the right place for the wrong reason, and 175 do - which counts
alone could never show.

Three causes tested against the new metric, all three fail. Re-running
PR #57's field substitution leaves it at 67/830, identical to the digit,
while a +3 bias moves it to 122/793 - so the substitution is live, the
metric is sensitive, and the fields are right; #57 scored placement only,
one bit per cell. fixImpossibleCells accounts for 12.5% vs 14.3%. Chunk
borders are 13.3% against interior 11.9%, no concentration.

Also read, and recorded in cliffs-NOTES.md: generateCliffs' full body
(confirms the code packing, the inlined orientation table, and that
grid_offset is applied to the CENTRE only), the fifth `!onChunkBorder`
argument to tryToAddCliff, tryToAddCliff's two sequential paths - the
earlier note described only the collision one - and that crater-cliff is
an entity autoplace, not a lattice cliff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013eczw9uoWDfN6Wc6kA7UBY
@wormeyman
wormeyman merged commit 8e2599f into main Jul 30, 2026
2 checks passed
@wormeyman
wormeyman deleted the feat/cliff-orientation-oracle branch July 30, 2026 22:08
wormeyman added a commit that referenced this pull request Jul 30, 2026
…er (#76)

PR #75 exposed that "substituting the game's own fields moves nothing"
had only ever been measured on the corner-fields fixture's regions -
[1500,1500], [1100,2600], [-1700,1900] - which were chosen for issue #24
and are all calcite-dominated. Only one of them is a region the cliff
port is scored on, and it is the one the port already handles best (8.1%
orientation error). [0,0], at 29.8%, had never had its fields checked.

This captures both fields at every corner of all three cliff-entity
regions (12,675 corners) and scores the substitution on ORIENTATION -
four bits per cell against the game's own cliff_orientation, where PR
#57 scored placement at one bit.

[1500,1500] is deliberately in both fixtures. The overlap is the check
on this capture's corner indexing, since an off-by-one there would look
exactly like a field error at [0,0]; the two agree bit-for-bit on all
4225 shared corners.

Result: the game's own values reproduce ours to the unit in every
region, including [0,0] - same cells placed, same matched, same wrong
(335/228/68, 1065/830/67, 375/342/40). The +3 bias arm moves both
placement and orientation in all three, so the substitution is live
everywhere it is claimed to be.

So the entire residual is in the rule as ported, with no input left to
suspect: crossingsForChunk's sampling geometry, the cliff_smoothing knot
model, or crossesCliff. crossingsForChunk (0x10160c9cc, 2244 bytes) is
the one never decompiled whole.

Also corrects the older fixture's comment, which claimed
vulcanus_elevation was sampled only at the smoothing knots. It never
was: the corner set is a full nested loop over each region and both
samples take all of it, which 3 x 65 x 65 = 12675 confirms.


Claude-Session: https://claude.ai/code/session_013eczw9uoWDfN6Wc6kA7UBY

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