exit logic corrected from in-game debugging: the leveldoor only sets g101-g105, the llama's script ends the level (material 16, exit cell, near the floor); stale floor record after a position jump explains the hover exit skip in jungle day 2

This commit is contained in:
MineTec committed 2026-11-07 20:57:15 +01:00
1 parent 64259ea15b
commit 0568ae7f24
3 files changed
+82 -15

No files matched your search

+37 -15
View File
@@ -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)
Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

+45
View File
@@ -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