Files
llamamod/notes/objects.md
T

136 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MAP chunk & objects (strats) – "Ein Königreich für ein Lama" (PC)
Source: groove.exe, MAP loader `FUN_0042ac50`, strat spawn `FUN_0054cfc0` → `FUN_0054bfc0`,
strat VM `FUN_0054d180`, chunk dispatcher at 0x558960. Tools in `../tools/`.
All 34 original MAPs are read completely by `mapchunk.parse()` (0 bytes left over) and
written back **byte-identically** by `mapchunk.build()`.
## Terms
* **Tile** = placed geometry model from **TRAK** (the level "construction kit", 1x1 grid).
* **Strat** = object/script instance (enemy, coin, door, player …). The strat code
(bytecode, u32 words) and the object models live in **STPC**; MAP only contains instances
with an offset into STPC. There is **no type ID** – the type *is* the code address.
Type names are in the code: opcode `0xd4, 0xea, s32 rel` → name = C string at
(address after rel) + rel. `strats.py` resolves this.
* Coordinates: world y points **up**. Tiles store float x, y, z with **z negative**;
strats/fixed point (20.12, 4096 = 1.0) store z **positive** (= −tile.z).
Angles: 4096 = 360°. Floor tiles (flat model, vertex y = +0.25) → objects stand at
`tile.y + 0.25` (confirmed on player/checkpoints/coins).
## MAP layout (all little endian) — confidence: high (from loader + roundtrip)
WFPC (u32 flags, global 0x6da330) controls optional blocks.
| # | Block | Format |
|---|---|---|
| 1 | Header | u32 nTiles, u32 W, u32 H (grid size in cells) |
| 2 | Tiles | nTiles × {f32 x, f32 y, f32 z, u32 rotY(0/1024/2048/3072), u32 model, u32 hasAlt} |
| 3 | Parameter pool | u32 n, n × u32 (strat parameters, referenced by strats via index) |
| 4 | Zones ("Environment") | u32 n, n × 90 bytes (see below) |
| 5 | Waypoints | u32 n, n × 34 bytes {u32 next(≠0: next entry belongs to the same chain, 0 = end of chain), u32, u16 rx, u16 ry, u16 rz, s32 x, y, z (fixed, z positive), u32, u32} |
| 6 | Region table | 32 × {u32 flags, u32 value} (→ 0x6da350) |
| 7 | Region grid | W×H × u32 region index (0..31) per cell, row = z cell |
| 8 | Tiles fixed | nTiles × 6 u32 {rx=0, rotY, rz=0, x·4096, y·4096, −z·4096} – **exactly derivable from block 2** |
| 9 | Tile models | nTiles × u32 TRAK model index (= field 5 of block 2) |
| 10 | Alt models (WFPC&0x10000) | u32 n, n × 5 u32; n = number of tiles with hasAlt≠0, in tile order; field 4 = alternative TRAK model (e.g. door open/closed, stMapreplace) |
| 11 | Vertex colours | per tile: nv(TRAK model) × u32 RGBA (byte 3 = frame count F); if max F>1: (F−1)×nv more u32; then the same for the alt model, if present. Colour is ×2 (clamped to 255) |
| 12 | Strats | u32 nStrats, u32 poolExtra, nStrats × 58 bytes (see below) |
| 13 | Paths (WFPC&0x200000) | u32 n; each: u32 nsub; each sub: u16 cnt, u16 flags; cnt × {u32,u32,[u32 if WFPC&0x4000000],u16 type,u16} – not used in any original level |
| 14 | (WFPC&0x10) | u32 (default 200; size of a 0x48 buffer) |
| 15 | | u16 (→ mapobj+0xc, meaning open) |
Cell assignment of a tile: `col = int(x−0.5)`, `row = −int(z+0.5)`, index `col + row·W` (linked
list per cell). **Tiles outside 0..W−1 / 0..H−1 write into foreign memory.**
### Strat entry (58 bytes) — `mapchunk.STRAT = '3H3iIIi4Iiih'+'H'`
| Off | Type | Field | Confidence |
|---|---|---|---|
| 0 | u16 ×3 | rotation x, y, z (4096=360°; x/z almost always 0) | high |
| 6 | s32 ×3 | position x, y, z fixed 20.12 (z positive) | high |
| 18 | u32 | **code offset in STPC** (= object type) | high |
| 22 | u32 | number of parameters n | high |
| 26 | s32 | index into parameter pool (−1 if n=0); parameters are pushed onto the strat stack | high |
| 30 | u32 | number of strat variables (from the compiler, **constant per type**) | high |
| 34 | u32 | number of 16-byte slots (collision/sub-objects; constant per type) | medium |
| 38 | u32 | type flags (0x8000/0x80000/0x40000 = collision/physics block; constant per type) | medium |
| 42 | u32 | extra memory (&0x3fffffff, mostly 0..3; constant per type) | medium |
| 46 | s32 | waypoint index (block 5) or −1 | high |
| 50 | u32 | always 0 | – |
| 54 | u16 | flags; bit 1 (2) = do not create at level start | high |
| 56 | u16 | always 0 | – |
`poolExtra` (second u32 in the strat header) = max. simultaneous strats − 30 (the engine allocates
poolExtra+30 slots of 0x154 bytes). **Must be ≥ number of strats + dynamically spawned ones.**
### Zones (90 bytes, 0x5c in memory) — confidence medium
u32 cellX, cellY, cellZ, s32 x,y,z (fixed), u32 ?, u32 rot?, u32 flags(+0x20; **bit 8 = start zone**
→ DAT_00584644), u32, u32 ptr offset in STPC (0=none), u32, u32, u32, u16, u32 fog near/far
(+0x3c/+0x40), u32 ×2 (+0x44/+0x48), u32 colour (e.g. 0x404040), u32 colour, u16, u16, u32 music ID
(+0x58; FUN_00546b70). Camera/fog/music areas; not light sources (those are in LGHT).
## Player, checkpoints, exit
* **Player start** = position/rotation of the strat instance **`Llama`** (exactly 1× in every
running level; type fields nvars 78, flags 0x248000). Special levels: river (t1l3*) =
`KuscoAndPachaTogetherOnATree`, roller coaster (t1l7m001/3/5) = `RollerCoaster`,
t1l2m003 = `LlamaChase`.
* **Checkpoints** = `CheckPointPost` (spawns `CheckPointHead`), variant `CheckPointBox`.
* **Exit** = `LevelDoor` (1×/level), special cases `VillageEndDoor`, `EndGameDoor`.
* Always present: `CheatTeleport`, `Wompy` (function not checked), `CutSceneTrigger`.
## Object types
Complete table type × level (instance counts): `strat_types_table.md`
(generated with `tools/strat_table.py <wads>`). Summary:
* in practically all levels: Coin, CutSceneTrigger, CutSceneCameraDirectional, LevelDoor,
Secret, Llama, CheatTeleport, ChargeBooster, Wompy, CheckPointPost, Coin5, CameraSide, Health,
HealthFull, SmashPot, RubyIdol, Grapes, HealthGenerator, RubyIdolDoor …
* ~300 types in total, ~200 more only spawned internally (splinters, ropes, effects).
* **Every level has its own compiled strat code + models in STPC.** A type can only be
placed in levels whose STPC contains it (`strats.py level.wad --all` lists all
named strats in the code, including ones never placed directly). The code contains **absolute
STPC offsets** (e.g. spawn stubs `b2 <abs> e0 …`), so STPC cannot be moved
without relocating. Taking types from other levels = merging STPC (+ TEXT indices)
→ laborious, not for now.
* "Spawn stub" entries (e.g. SmashPot): MAP points to a small piece of code
`push X; spawn(abs target, nvars, …); end` → `strats.py` reports `Name(spawn)`.
## Rules for writing a new MAP
1. Keep tile blocks 2/8/9/11 in parallel (same count/order); derive block 8 from 2;
block 9 = field 5 of 2; vertex colour length exactly = Σ nv(model)·frames (+alt).
2. Alt block: count = tiles with hasAlt≠0, same order. Simply append new tiles
at the end (strats can replace tiles by index – `stMapadd/stMapreplace` – so
do not reorder existing indices).
3. All tiles within 0..W × 0..H (z negative!). Region grid has W·H entries.
4. Strat: code offset must be a strat start in **this** STPC; **copy** nvars/slots/flags/extra
**from the original type**; append parameters to the pool, set index/count; waypoint
−1 or a valid index (some types need waypoints, otherwise "removing strat").
5. Increase poolExtra if there are more strats.
6. WFPC flags determine which optional blocks must be present – do not change them.
## Tools
* `mapchunk.py` – parse/build (identical roundtrip for all 34 MAPs), `vcol_split` per tile.
* `map_dump.py level.wad [--strats --tiles --lights]`
* `strats.py level.wad [--all]` – types with instance counts, code offsets, type fields.
* `strat_table.py wads_dir` – type×level table.
* `stpcwalk.py` – roughly walks STPC (models → animsets → [WFPC&0x100 block] → strat code).
* `make_testlevel.py src.wad dst.wad [--name …] [--spacing 3] [--floor-model N] [--keep-original]`
– finds a free grid area, lays a floor plate from copies of the most common flat
floor tile (t1l1m001: TRAK model 12), places one instance per type (parameters from the
original) on a 6-unit grid, the player at the edge, and a parkour (6 stepping plates +0.5 with
gaps, coins) leading to a goal platform with a LevelDoor. Skipped: Camera*/CutScene*/
Force*/Trigger. Test (not checked in game): T1L9M001 → 1753 tiles, 36 strats, 27 types.
## Other chunks (dispatcher 0x558960)
* WFPC: u32 feature flags (→ 0x6da330), e.g. 0x18410690.
* INFO/VERS: u32 1 each (ignored).
* NAME: u32 + level name.
* TRAK: tile models (u32 n, n×0x84 header, then data; header+0x6c u16 nVert, +0x6e nFace(0x1c),
+0x78/7a/7c count of 0x20 primitives). Face+8 u16 = index into the TEXT texture list.
* STPC: u32 n models (each 0x8c header directly + data), u32 n animsets (0x20 header + data),
[WFPC&0x100: extra models], then strat code up to the end of the chunk.
* LGHT: u32 n; per light u8 type, u8 r, u8 g, u8 b (×2), type 1/2/…: more u32 (direction/position,
z negated). FUN_0042c180.
* LNFO: 2 u32 (→ 0x5ff070, 0x5ff060), e.g. 55, 2.
* LGPC: u32 n(+1), u32 m, u32; then (n+1)·m × u32 sizes, followed by the blobs. FUN_00558db0.
* FONT: 256 × 4 u16 (glyph rectangles). SPRT: u32 (list if WFPC&0x100000).
* SMPC / AMPC / SRPC: sound banks ("cvg" samples / "ambi" ambient / references), FUN_00545350.
* 'GRIM' (not seen in level WADs): FUN_00437b40.