Skip to content

Commit ea810e9

Browse files
wormeymanclaude
andauthored
docs(cliffs): the runtime probe is SAFE - all four cascade gates READ (#84) (#135)
#134 recorded `Cliff::onDestroy`'s four cascade gates and warned that a Lua or editor destroy "need not" satisfy them, which would make #127's runtime probe vacuous. **That warning is withdrawn.** It was stated from inference - "map generation evidently satisfies all four, because #113 measured the cascade running" - and inference is what keeps going wrong on this issue. | gate | cascade needs | `applyCliffs` | `luaCreateEntity` | | --- | --- | --- | --- | | entity flag bit 5 of `+0x6e` | clear | `params[0x80] = 0` | `params[0x80] = 0` | | `this+0x82` | set | ctor writes 1 unconditionally | same ctor | | `this+0x83` | set | `place_as_crater == nullptr` | same | | `map->[0x240]` | zero | only `Map::~Map` writes 1 | same | - Bit 5 is exactly `params[0x80] != 0` at construction. `applyCliffs` builds its params at `sp+0x20` and zeroes that byte with `str wzr, [x26]`, `x26 = params + 0x80` (`0x101623d9c`). `luaCreateEntity` builds its own at `sp+0x228` and zeroes the same byte on both paths (`0x1019e5ba8`, `0x1019e5c18`) - and that arm is identifiably the cliff one, storing the `0x14` sentinel into `params+0x87` before `LuaTable::getDefault<CliffOrientation>`. - `map->[0x240]` is "the map is being torn down": `Map::~Map` sets it as its first act, before `ToDeleteList::clear` (`0x10163461c`), and `Map::resume` bails on it. Dozens of unrelated `onDestroy` handlers read the same byte. `Cliff+0x78` is the `Map` - `Entity::getGame` is `[[this+0x78]+0x490]`. - The one path that WOULD differ, `Cliff::destroyWithoutCorrection`, is unreachable: zero direct callers under a scan of every `BL` AND `B` in `__text`, and no pointer in any vtable - only four in the STAB tables. The same widened scan re-confirms #134's other caller claim, which had been made on `BL` alone and could have missed a tail call: `updateAndFixConnections` still has exactly one caller, `CliffEditor::buildCliffs`. Documentation only - no code, no fixtures, no behaviour change. Claude-Session: https://claude.ai/code/session_01MmitdaFJxN9Wm8LkQu33kc Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 8409b48 commit ea810e9

3 files changed

Lines changed: 97 additions & 19 deletions

File tree

docs/noise/cliffs-NOTES.md

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -978,11 +978,8 @@ what anyone should try next:
978978
connections at all** - do not attribute anything crater-shaped to these rules.
979979
- **`Cliff::onDestroy`'s cascade has FOUR gates, not zero** (`0x1007a8854`
980980
onwards): entity flag bit 5 of `+0x6e` clear, `+0x82` set, `+0x83` set, and
981-
`map->[0x240]` zero. Bit 5 is `params[0x80] != 0` at construction, `+0x82` is
982-
what `destroyWithoutCorrection` zeroes. Map generation evidently satisfies all
983-
four (#113 measured the cascade running), but **a Lua or editor probe will
984-
not necessarily** - which is a live hazard for the runtime probe #127 asked
985-
for, and the reason to check the four before believing such a probe.
981+
`map->[0x240]` zero. All four are now READ on both paths - see the next
982+
section, which withdraws the warning this bullet used to carry.
986983
- **There is no second connection pass during map generation.**
987984
`Cliff::updateAndFixConnections` (`0x1007a94d0`) is called from exactly one
988985
site in the whole `__text` segment - `CliffEditor::buildCliffs`
@@ -997,6 +994,47 @@ what anyone should try next:
997994
prototype-id match AND an exact position match, so a different cliff
998995
prototype on an adjacent cell is not a neighbour.
999996

997+
### The four cascade gates, READ on both paths - the runtime probe is SAFE
998+
999+
#134 recorded the four gates and warned that a Lua or editor destroy "need not"
1000+
satisfy them, which would make #127's runtime probe vacuous. That warning was
1001+
stated from inference - "map generation evidently satisfies all four, because
1002+
#113 measured the cascade running" - and inference is what keeps going wrong on
1003+
this issue. Reading the bytes settles it, and the answer is the opposite:
1004+
1005+
| gate | cascade needs | `applyCliffs` | `LuaSurface::luaCreateEntity` |
1006+
| --- | --- | --- | --- |
1007+
| entity flag bit 5 of `+0x6e` | clear | `params[0x80] = 0` | `params[0x80] = 0` |
1008+
| `this+0x82` | set | ctor writes 1 unconditionally | same ctor |
1009+
| `this+0x83` | set | `place_as_crater == nullptr` | same |
1010+
| `map->[0x240]` | zero | only `Map::~Map` writes 1 | same |
1011+
1012+
- Bit 5 of `+0x6e` is exactly `params[0x80] != 0` at construction
1013+
(`0x1007a7abc`). `applyCliffs` builds its `EntityCreationParameters` at
1014+
`sp+0x20` and zeroes that byte with `str wzr, [x26]`, `x26 = params + 0x80`
1015+
(`0x101623d9c`). `luaCreateEntity` builds its own at `sp+0x228` and zeroes the
1016+
same byte on **both** of its paths (`strb wzr, [sp, #0x2a8]`, `0x1019e5ba8`
1017+
and `0x1019e5c18`). That arm is identifiably the cliff one: it stores the
1018+
sentinel `mov w8, #0x14` into `params+0x87` and then overwrites it with
1019+
`LuaTable::getDefault<CliffOrientation>("cliff_orientation")`.
1020+
- `this+0x82` is written 1 by the constructor with no condition
1021+
(`0x1007a7b14`), so it holds for any ordinary destroy.
1022+
- **`map->[0x240]` is "the map is being torn down".** `Map::~Map` sets it to 1
1023+
as its first act, before `ToDeleteList::clear` (`0x10163461c`), and
1024+
`Map::resume` bails out on it (`0x10163b294`). Dozens of unrelated
1025+
`onDestroy` handlers read the same byte. Nothing during normal play sets it.
1026+
(`Cliff+0x78` is the `Map`: `Entity::getGame` is `[[this+0x78]+0x490]`.)
1027+
1028+
**So the probe reproduces map generation's cascade.** The one path that would
1029+
differ, `Cliff::destroyWithoutCorrection` (`0x1007aa568`, which zeroes `+0x82`
1030+
around the destroy), is unreachable: **zero** direct callers under a scan of
1031+
every `BL` *and* `B` in `__text`, and no pointer to it in any vtable - only four
1032+
in the STAB debug tables. `entity.destroy()` cannot land on it by accident.
1033+
1034+
The same widened scan re-confirms #134's other caller claim, which had been made
1035+
on `BL` alone and could have missed a tail call: `updateAndFixConnections` still
1036+
has exactly one caller, `CliffEditor::buildCliffs`.
1037+
10001038
### The tile half reads ZERO for an ungenerated chunk, and it is border-only
10011039

10021040
`Surface::wouldCollide` -> `constCollideWithTile` (`0x100732eec`) ->

docs/noise/vulcanus-cliffs-NOTES.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,38 @@
2020
> in `cliffs-NOTES.md` and `cliffConnections.ts`). Three change what to try next:
2121
>
2222
> - **`onDestroy`'s cascade has FOUR gates**, including `map->[0x240] == 0` and an
23-
> entity flag set from the creation parameters. Map generation satisfies all
24-
> four; **a Lua or editor probe need not** - which is a hazard for exactly the
25-
> runtime probe #127 asked for. Check the four before believing such a probe.
23+
> entity flag set from the creation parameters. All four are now READ on both
24+
> paths and **the runtime probe #127 asked for is SAFE** - see UPDATE 11. The
25+
> warning this bullet first carried is withdrawn.
2626
> - **`crater-cliff` has no connections at all.** The whole system is gated on
2727
> `proto->place_as_crater == nullptr`. Nothing crater-shaped belongs to these
2828
> rules.
2929
> - **There is no second connection pass during map generation.**
3030
> `updateAndFixConnections` is called from exactly one site in the binary,
3131
> `CliffEditor::buildCliffs`.
3232
>
33+
> ## UPDATE 11, 2026-08-03: the runtime probe is SAFE - all four cascade gates READ on both paths
34+
>
35+
> UPDATE 10 warned that #127's runtime probe might not reproduce map generation,
36+
> because `Cliff::onDestroy`'s cascade has four gates. **That warning is
37+
> withdrawn.** It was stated from inference, and inference is what keeps going
38+
> wrong on this issue; reading the bytes settles it the other way.
39+
>
40+
> | gate | cascade needs | `applyCliffs` | Lua `create_entity` |
41+
> | --- | --- | --- | --- |
42+
> | entity flag bit 5 of `+0x6e` | clear | `params[0x80] = 0` | `params[0x80] = 0` |
43+
> | `this+0x82` | set | ctor writes 1 unconditionally | same ctor |
44+
> | `this+0x83` | set | `place_as_crater == nullptr` | same |
45+
> | `map->[0x240]` | zero | only `Map::~Map` writes 1 | same |
46+
>
47+
> `map->[0x240]` is **"the map is being torn down"** - `Map::~Map` sets it as its
48+
> first act and `Map::resume` bails on it. And the one path that WOULD differ,
49+
> `Cliff::destroyWithoutCorrection`, is **unreachable**: zero direct callers under
50+
> a scan of every `BL` and `B` in `__text`, and no pointer in any vtable.
51+
>
52+
> **So the probe is unblocked and is the next thing to build.** Addresses and the
53+
> non-vacuity arms are in `cliffs-NOTES.md`.
54+
>
3355
> ## UPDATE 9, 2026-08-03: the residual is on CHUNK BORDERS (2.91 sigma), and the ore mechanism is a CLOSED IMPOSSIBILITY
3456
>
3557
> Two results supersede the framing below. Read this before acting on any earlier

src/noise/cliffs/cliffConnections.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,35 @@
6767
* destroyed cell can take a non-border neighbour with it, and why this is a
6868
* cascade rather than a filter.
6969
*
70-
* **Three conditions on that cascade are unmodelled here, deliberately.**
71-
* `onDestroy` reaches its neighbour loop only when all four of these hold
72-
* (`0x1007a8854`-`0x1007a8874`): entity flag bit 5 of `this+0x6e` CLEAR,
73-
* `this+0x82` set, `this+0x83` set, and `map->[0x240]` zero. Bit 5 is
74-
* `params[0x80] != 0` at construction and `this+0x82` is what
75-
* `Cliff::destroyWithoutCorrection` (`0x1007aa568`) temporarily zeroes, so both
76-
* are about who is doing the destroying rather than about the cliff. The port
77-
* models map generation, where #113 measured the cascade running - 1058 of 1058
78-
* at `[1500,1500]` - so all four evidently hold there. They are recorded because
79-
* a future probe that destroys a cliff from Lua or the editor will NOT get the
80-
* same behaviour, and that is exactly the probe #127 asked for.
70+
* **Four conditions gate that cascade, and all four are read rather than
71+
* inferred.** `onDestroy` reaches its neighbour loop only when all of these hold
72+
* (`0x1007a8854`-`0x1007a8874`), and the right-hand column is what each is on
73+
* the two paths that matter - `applyCliffs` and a Lua `create_entity` +
74+
* `destroy`:
75+
*
76+
* | gate | cascade needs | map generation | Lua |
77+
* | --- | --- | --- | --- |
78+
* | entity flag bit 5 of `+0x6e` | clear | `params[0x80] = 0` | `params[0x80] = 0` |
79+
* | `this+0x82` | set | ctor writes 1 unconditionally | same ctor |
80+
* | `this+0x83` | set | `place_as_crater == nullptr` | same |
81+
* | `map->[0x240]` | zero | only `Map::~Map` writes 1 | same |
82+
*
83+
* Bit 5 is exactly `params[0x80] != 0` at construction. `applyCliffs` zeroes it
84+
* with `str wzr, [x26]` where `x26 = params + 0x80` (`0x101623d9c`), and
85+
* `LuaSurface::luaCreateEntity`'s cliff arm zeroes the same byte on both of its
86+
* paths (`strb wzr, [sp, #0x2a8]` at `0x1019e5ba8` and `0x1019e5c18`; that arm is
87+
* identifiable by the `mov w8, #0x14` sentinel it puts in `params+0x87` before
88+
* `LuaTable::getDefault<CliffOrientation>`). `map->[0x240]` is "the map is being
89+
* torn down": `Map::~Map` sets it to 1 as its first act (`0x10163461c`) and
90+
* `Map::resume` bails on it.
91+
*
92+
* **So a runtime probe reproduces map generation's cascade, and #134's warning
93+
* that it might not is withdrawn.** It was a correct risk stated before the
94+
* bytes were read, and reading them settles it. The one path that WOULD differ,
95+
* `Cliff::destroyWithoutCorrection` (`0x1007aa568`, which zeroes `+0x82` around
96+
* the destroy), is unreachable: zero direct callers under a scan of every `BL`
97+
* and `B` in `__text`, and no pointer to it in any vtable. `entity.destroy()`
98+
* cannot land on it by accident.
8199
*
82100
* **There is no second connection pass during map generation.**
83101
* `Cliff::updateAndFixConnections` (`0x1007a94d0`) looks like one and is not: a

0 commit comments

Comments
 (0)