# 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 `). 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 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.