diff --git a/docs/reference/objects.md b/docs/reference/objects.md index 2c70c6f..84b4a7a 100644 --- a/docs/reference/objects.md +++ b/docs/reference/objects.md @@ -81,27 +81,49 @@ nothing solid right behind the llama (the camera moves aside). | cameras | `CameraSide`, `CutSceneCameraDirectional`, `CutSceneTrigger` | camera zones and cut scenes | | always present | `CheatTeleport`, `Wompy`, `CutSceneTrigger` | | -### The exit (LevelDoor) – *certain* +### The exit (LevelDoor) -The LevelDoor has no model. Every frame it checks: +An exit is **two parts working together** (decompiled strat code; the details below were checked +in game in Jungle Day 2 with a debugger, the basic rule also in Village 1): + +* The **LevelDoor** object (no model) writes its three parameters – the next level (tribe, world, + chapter) – to globals g101–g103 and its grid cell to g104/g105. It does **not** end the level + itself in the cases we tested (see the details below). +* **Kuzco's own script** ends the level when **all** of these hold: ``` -game state is "running" -and the floor polygon under the llama has collision material 16 -and floor(llama.x) == floor(door.x) and floor(llama.z) == floor(door.z) ; same grid cell --> endLevel(tribe, world, chapter) ; its three parameters +floor material under Kuzco == 16 ; the remembered floor (see "floor test" below) +and floor(x), floor(z) == the cell in g104/g105 ; the exit's grid cell, any height +and Kuzco is at most ~0.6 above that floor's height +-> level change to g101..g103 (tally screen) ``` -So an exit needs **a floor with collision material 16 in the door's grid cell** – height does not -matter. In several original levels the pad only appears when a tile swap opens the door (Village 1: -the RubyIdolDoor swaps the tile to a model that has the material-16 pad). The parameters are the -next level; the originals use (1, world, chapter + 1), and **(7, 7, 7)** in the last chapter of a -world means "world finished" – in TestLevel that crashes, so custom levels use (1, 9, chapter). -`endLevel` shows the tally (coins x / LNFO total, secrets) and unlocks the next chapter. + On a material-16 floor **outside** the exit cell the same check is a house door: fade-out and + the teleport of the environment zone of that cell. -A second path in the Llama's own code ends the level when it stands on material 16 in the cell -stored in globals g104/g105 with the target in g101–g103 – the arena uses this -([Arena](../strats/arena.md)). +So a normal exit needs **a floor with collision material 16 in the exit's grid cell**. In several +original levels that floor only exists after a tile swap opens the door (Village 1 and Jungle Day 2: +the RubyIdolDoor swaps the tile to a model that has the material-16 pad; checked in game in Village +1). The parameters of the originals are (1, world, chapter + 1); **(7, 7, 7)** in the last chapter of +a world means "world finished" – in TestLevel that crashes, so custom levels use (1, 9, chapter). +The tally shows coins x / LNFO total and secrets and unlocks the next chapter. The arena sets +g101–g105 itself to make its own exit ([Arena](../strats/arena.md)). + +**Details checked in game (Jungle Day 2, t1l4m002, 2026-11-07):** + +* The LevelDoor stores its parameters and cell in g101–g105, then checks global 0x21 **once** + (after 5 ticks); if it is not 3 the door removes itself. In Jungle Day 2 it was gone after the + start, so the Llama's check is what ends the level. That check needs floor material 16, the + door's cell, and the llama no more than 0.6 above the recorded floor height. Off the exit cell + the same material-16 check is the house door (fade-out and zone teleport). +* The floor test only looks at tiles in the player's cell and takes the **nearest upward floor + below the player, at any distance**. Material 17 is ignored for the llama. A llama hovering 3.7 + units above a house-door pad records material 16. +* Each tick runs all strat scripts first and then the floor tests. A position change made after + the floor tests is seen by the next tick's scripts with the **old** floor record. So hovering + above any house-door pad and then moving the llama straight to floor height in the exit cell + ends the level, even though the exit cell has no material 16 yet (it appears only in the swap + model when the RubyIdolDoor opens). Moving there from normal ground does nothing. ## Environment zones (90 bytes, *certain* for the fields below) diff --git a/notes/img/debug/exit-hover-trick.jpg b/notes/img/debug/exit-hover-trick.jpg new file mode 100644 index 0000000..14471e3 Binary files /dev/null and b/notes/img/debug/exit-hover-trick.jpg differ diff --git a/notes/levelbau.md b/notes/levelbau.md index 6c60b79..cf68192 100644 --- a/notes/levelbau.md +++ b/notes/levelbau.md @@ -173,6 +173,11 @@ stairs, platforms register as shadow casters (`solids`). 1.0 = texture colour ## Exit (LevelDoor) – how it works +> **Corrected 2026-11-07 (in game, Jungle Day 2):** the listing below was the first reading. The +> LevelDoor only sets g101–g105 and checks global 0x21 *once*; the level is ended by the **Llama's +> script** (material 16 + exit cell + at most ~0.6 above the floor). See "Exit via stale floor +> material" further down and docs/reference/objects.md. + Strat bytecode (village, STPC 0x430332, 3 parameters, invisible, no model), decoded via the VM `FUN_0054d180` (opcode < 0x45: table 0x5796bc with 16-bit operand, otherwise 0x578ff4+op·4): ``` @@ -199,6 +204,46 @@ loop: if g21 == 3 ; game state "running" (zone teleport). t1l3m003: 2 of the 4 doors (cells 71,48/71,49) have no pad at all. * Second path (strat code at 0x4252aa, player logic [likely]): floor material 16 and cell = g104/g105 (set by the LevelDoor at start, Llama sets them to −1) → level change(g101..g103). + +### Exit via stale floor material (hover + position jump, t1l4m002) – checked 2026-11-07 + +Speedrun video (position/hover hack): Kuzco hovers above a house-door stump, is moved to the exit and +the level ends. Reproduced in game (apptest, gdb through `winedbg --gdb`, position written by gdb): +* **Jungle Day chapter 2 = t1l4m002** (LNFO 95 coins / 2 secrets; tally "Dschungeltag Kapitel 2, + 0/95, 0/2"). LevelDoor at 44.5/25.5 (cell 44,25), params (1,4,3); RubyIdolDoor in the same + cell. Cell 44,25 (tile 255) has material 16 **only in swap model 95** (pad x 44.67–45.0, + z 25.25–25.75, y 2.25); main model 94 has none → with the door closed the exit cannot fire. + House-door pads (material 16, main models) in cells 38,20 / 51,0 / 52,5 / 41,24 / 44,27 / + 50,27 / 41,29 / 57,55 / 41,38 / 41,44 plus swap pads in 22,21 / 26,21 / 23,23 / 50,1 / 43,21 / + 46,30 / 58,49 / 31,50; the zones pair them (e.g. 44,27 → 50.5/27.5, 50,27 → 44.5/27.5). +* **Floor test (FUN_004036d0):** only tiles of the player's own cell; per upward polygon the plane + height at (x,z) is compared with a probe point (y + 0.25, or y + (floor record +0x18)/2 with + +0xe8 flag 0x200000 as for the llama); + the **nearest floor below the probe wins, with no distance limit**. Material 17 is skipped for + the llama (+0xe8 = 0x24882a, no 0x8000000). If nothing is below: for the llama (+0xe8 & 0x40000) + material/height are **not** updated (stale) and x/z go back to the previous position. + In game: llama held at 44.9/**6.0**/27.5 (3.7 above the pad of the stump at 44,27) → floor + record h 2.30, **material 16**, tile 282 poly 15, every frame. +* **Order per tick (FUN_0054c4a0):** all strat scripts (VM FUN_0054d180) first, then the + collision pass (FUN_00403020 for every strat with +0xe8 & 0x8000). A position written after the + collision pass is seen by the scripts of the next tick with the **old floor record**. + In game: after hovering above the 44,27 pad, llama set to 44.5/2.3/25.5 right after its floor + test → next event endLevel `FUN_0054d620` (called from the VM, floor record still 16), before + any new floor test → tally (twice with screenshot, once traced). Control: same jump from normal + ground (material 0) → no exit; jump written just *before* the floor test → record becomes 0, no exit. +* **Who ends the level here: the Llama, not the LevelDoor.** LevelDoor bytecode: wait 5 ticks, + g101..g105 = params + cell, then **once** `if global 0x21 != 3 → end` (so "g21 == 3" above is a + one-time check, and global 0x21 is not the game state 0x5846e4), then the per-tick loop. In + t1l4m002 the LevelDoor was gone from the strat list (scan of 0x6d9e38) while g101..g105 = + 1,4,3,44,25 and global 0x21 = 0. The Llama block at STPC 0x39ff4c–0x3a0064: if floorMaterial + == 16 and health > 0 and (y < floorHeight + 0.6 and global 0x21 != 2, or y < floorHeight + 0.025 + and (g1 == 0 or global 0x23)): cell == (g104, g105) → level change(g101..g103), else fade-out + + `getDoorPos` (op 0x100, zone teleport = house door) + `forceCollision`. The 0.6 limit is why + hovering high above a house pad does not trigger the house teleport (bytecode; not separately + tested), while landing at floor height in the exit cell with the stale record does. +* Not explained: in two sessions with a moved player start (boot-to-level `start` 43.3/25.5) the + same jump did not end the level (Llama script ran with record 16 in cell 44,25, no endLevel). +* Images: `img/debug/exit-hover-trick.jpg` (top: video; bottom: our pool/door platform, tally). * Parameters in the originals: (1, world, chapter+1) = next chapter; in the last chapter of a world **(7,7,7)** = "world completed" → back to the selection map, unlock the next world. * `FUN_0054d620`: remembers the current level (0x585008..14), sets the next (0x584eb4/b8/bc), shows the