# MAP-Chunk & Objekte (Strats) – "Ein Königreich für ein Lama" (PC) Quelle: groove.exe, MAP-Lader `FUN_0042ac50`, Strat-Spawn `FUN_0054cfc0` → `FUN_0054bfc0`, Strat-VM `FUN_0054d180`, Chunk-Dispatcher bei 0x558960. Werkzeuge in `../tools/`. Alle 34 Original-MAPs werden von `mapchunk.parse()` vollständig gelesen (Rest 0 Byte) und von `mapchunk.build()` **byte-identisch** zurückgeschrieben. ## Begriffe * **Tile** = platziertes Geometrie-Modell aus **TRAK** (Level-"Baukasten", 1x1-Raster). * **Strat** = Objekt/Script-Instanz (Gegner, Münze, Tür, Spieler …). Der Strat-Code (Bytecode, u32-Wörter) und die Objektmodelle liegen in **STPC**; MAP enthält nur Instanzen mit Offset in STPC. Es gibt **keine Typ-ID** – der Typ *ist* die Code-Adresse. Typnamen stehen im Code: Opcode `0xd4, 0xea, s32 rel` → Name = C-String bei (Adresse hinter rel) + rel. `strats.py` löst das auf. * Koordinaten: Welt-y nach **oben**. Tiles speichern float x, y, z mit **z negativ**; Strats/fixed-point (20.12, 4096 = 1.0) speichern z **positiv** (= −tile.z). Winkel: 4096 = 360°. Boden-Tiles (flaches Modell, Vertex-y = +0.25) → Objekte stehen auf `tile.y + 0.25` (bestätigt an Spieler/Checkpoints/Münzen). ## Layout MAP (alles little endian) — Konfidenz: hoch (aus Lader + Roundtrip) WFPC (u32 Flags, globale 0x6da330) steuert optionale Blöcke. | # | Block | Format | |---|---|---| | 1 | Kopf | u32 nTiles, u32 W, u32 H (Rastergröße in Zellen) | | 2 | Tiles | nTiles × {f32 x, f32 y, f32 z, u32 rotY(0/1024/2048/3072), u32 modell, u32 hatAlt} | | 3 | Parameterpool | u32 n, n × u32 (Strat-Parameter, von Strats per Index referenziert) | | 4 | Zonen ("Environment") | u32 n, n × 90 Byte (s.u.) | | 5 | Wegpunkte | u32 n, n × 34 Byte {u32 weiter(≠0: nächster Eintrag gehört zur selben Kette, 0 = Kettenende), u32, u16 rx, u16 ry, u16 rz, s32 x, y, z (fixed, z positiv), u32, u32} | | 6 | Regionstabelle | 32 × {u32 flags, u32 wert} (→ 0x6da350) | | 7 | Regionsraster | W×H × u32 Regionsindex (0..31) je Zelle, Zeile = z-Zelle | | 8 | Tiles fixed | nTiles × 6 u32 {rx=0, rotY, rz=0, x·4096, y·4096, −z·4096} – **exakt aus Block 2 ableitbar** | | 9 | Tile-Modelle | nTiles × u32 TRAK-Modellindex (= Feld 5 aus Block 2) | | 10 | Alt-Modelle (WFPC&0x10000) | u32 n, n × 5 u32; n = Anzahl Tiles mit hatAlt≠0, in Tile-Reihenfolge; Feld 4 = alternatives TRAK-Modell (z. B. Tür offen/zu, stMapreplace) | | 11 | Vertexfarben | je Tile: nv(TRAK-Modell) × u32 RGBA (Byte 3 = Frameanzahl F); wenn max F>1: (F−1)×nv weitere u32; danach dasselbe für das Alt-Modell, falls vorhanden. Farbe wird ×2 (Clamp 255) | | 12 | Strats | u32 nStrats, u32 poolExtra, nStrats × 58 Byte (s.u.) | | 13 | Pfade (WFPC&0x200000) | u32 n; je: u32 nsub; je sub: u16 cnt, u16 flags; cnt × {u32,u32,[u32 wenn WFPC&0x4000000],u16 typ,u16} – in keinem Original-Level benutzt | | 14 | (WFPC&0x10) | u32 (Default 200; Größe eines 0x48-Puffers) | | 15 | | u16 (→ mapobj+0xc, Bedeutung offen) | Zellenzuordnung Tile: `col = int(x−0.5)`, `row = −int(z+0.5)`, Index `col + row·W` (verkettete Liste je Zelle). **Tiles außerhalb 0..W−1 / 0..H−1 schreiben in fremden Speicher.** ### Strat-Eintrag (58 Byte) — `mapchunk.STRAT = '3H3iIIi4Iiih'+'H'` | Off | Typ | Feld | Konfidenz | |---|---|---|---| | 0 | u16 ×3 | Rotation x, y, z (4096=360°; x/z fast immer 0) | hoch | | 6 | s32 ×3 | Position x, y, z fixed 20.12 (z positiv) | hoch | | 18 | u32 | **Code-Offset in STPC** (= Objekttyp) | hoch | | 22 | u32 | Anzahl Parameter n | hoch | | 26 | s32 | Index in Parameterpool (−1 wenn n=0); Parameter werden auf den Strat-Stack gelegt | hoch | | 30 | u32 | Anzahl Strat-Variablen (vom Compiler, **typkonstant**) | hoch | | 34 | u32 | Anzahl 16-Byte-Slots (Kollision/Unterobjekte; typkonstant) | mittel | | 38 | u32 | Typflags (0x8000/0x80000/0x40000 = Kollisions-/Physikblock; typkonstant) | mittel | | 42 | u32 | Zusatzspeicher (&0x3fffffff, meist 0..3; typkonstant) | mittel | | 46 | s32 | Wegpunkt-Index (Block 5) oder −1 | hoch | | 50 | u32 | immer 0 | – | | 54 | u16 | Flags; Bit 1 (2) = beim Levelstart nicht erzeugen | hoch | | 56 | u16 | immer 0 | – | `poolExtra` (zweites u32 im Strat-Kopf) = max. gleichzeitige Strats − 30 (Engine legt poolExtra+30 Slots à 0x154 Byte an). **Muss ≥ Anzahl Strats + dynamisch gespawnte sein.** ### Zonen (90 Byte, im Speicher 0x5c) — Konfidenz mittel u32 cellX, cellY, cellZ, s32 x,y,z (fixed), u32 ?, u32 rot?, u32 flags(+0x20; **Bit 8 = Startzone** → DAT_00584644), u32, u32 ptr-Offset in STPC (0=keiner), u32, u32, u32, u16, u32 Nebel nah/fern (+0x3c/+0x40), u32 ×2 (+0x44/+0x48), u32 Farbe (z. B. 0x404040), u32 Farbe, u16, u16, u32 Musik-ID (+0x58; FUN_00546b70). Kamera-/Nebel-/Musikbereiche; nicht Lichtquellen (die stehen in LGHT). ## Spieler, Checkpoints, Ausgang * **Spielerstart** = Position/Rotation der Strat-Instanz **`Llama`** (genau 1× in jedem Lauf-Level; Typfelder nvars 78, flags 0x248000). Sonderlevel: Fluss (t1l3*) = `KuscoAndPachaTogetherOnATree`, Achterbahn (t1l7m001/3/5) = `RollerCoaster`, t1l2m003 = `LlamaChase`. * **Checkpoints** = `CheckPointPost` (spawnt `CheckPointHead`), Variante `CheckPointBox`. * **Ausgang** = `LevelDoor` (1×/Level), Sonderfälle `VillageEndDoor`, `EndGameDoor`. * Immer vorhanden: `CheatTeleport`, `Wompy` (Funktion nicht geprüft), `CutSceneTrigger`. ## Objekttypen Vollständige Tabelle Typ × Level (Instanzzahlen): `strat_types_table.md` (erzeugt mit `tools/strat_table.py `). Kurz: * in praktisch allen Levels: Coin, CutSceneTrigger, CutSceneCameraDirectional, LevelDoor, Secret, Llama, CheatTeleport, ChargeBooster, Wompy, CheckPointPost, Coin5, CameraSide, Health, HealthFull, SmashPot, RubyIdol, Grapes, HealthGenerator, RubyIdolDoor … * ~300 Typen insgesamt, ~200 weitere nur intern gespawnt (Splitter, Seile, Effekte). * **Jeder Level hat seinen eigenen kompilierten Strat-Code + Modelle in STPC.** Ein Typ ist nur in Levels platzierbar, deren STPC ihn enthält (`strats.py level.wad --all` listet alle benannten Strats im Code, auch nie direkt platzierte). Der Code enthält **absolute STPC-Offsets** (z. B. Spawn-Stubs `b2 e0 …`), STPC ist also nicht verschiebbar, ohne zu relozieren. Typen aus anderen Levels zu übernehmen = STPC (+ TEXT-Indizes) zusammenführen → aufwendig, vorerst nicht. * "Spawn-Stub"-Einträge (z. B. SmashPot): MAP zeigt auf einen kleinen Code `push X; spawn(abs Ziel, nvars, …); end` → `strats.py` meldet `Name(spawn)`. ## Regeln beim Schreiben einer neuen MAP 1. Tile-Blöcke 2/8/9/11 parallel halten (gleiche Anzahl/Reihenfolge); Block 8 aus 2 ableiten; Block 9 = Feld 5 aus 2; Vertexfarben-Länge exakt = Σ nv(Modell)·Frames (+Alt). 2. Alt-Block: Anzahl = Tiles mit hatAlt≠0, gleiche Reihenfolge. Neue Tiles einfach hinten anhängen (Strats können Tiles per Index ersetzen – `stMapadd/stMapreplace` – deshalb bestehende Indizes nicht umsortieren). 3. Alle Tiles in 0..W × 0..H (z negativ!). Regionsraster W·H Einträge. 4. Strat: Code-Offset muss Strat-Anfang in **diesem** STPC sein; nvars/slots/flags/extra **vom Original-Typ kopieren**; Parameter in Pool anhängen, Index/Anzahl setzen; Wegpunkt −1 oder gültiger Index (manche Typen brauchen Wegpunkte, sonst "removing strat"). 5. poolExtra erhöhen, wenn mehr Strats. 6. WFPC-Flags bestimmen, welche optionalen Blöcke vorhanden sein müssen – nicht ändern. ## Werkzeuge * `mapchunk.py` – parse/build (Roundtrip identisch für alle 34 MAPs), `vcol_split` je Tile. * `map_dump.py level.wad [--strats --tiles --lights]` * `strats.py level.wad [--all]` – Typen mit Instanzzahlen, Code-Offsets, Typfeldern. * `strat_table.py wads_dir` – Typ×Level-Tabelle. * `stpcwalk.py` – STPC grob ablaufen (Modelle → Animsets → [WFPC&0x100-Block] → Strat-Code). * `make_testlevel.py quelle.wad ziel.wad [--name …] [--spacing 3] [--floor-model N] [--keep-original]` – sucht freien Rasterbereich, legt Bodenplatte aus Kopien des häufigsten flachen Boden-Tiles (t1l1m001: TRAK-Modell 12) an, stellt je Typ eine Instanz (Parameter vom Original) im 6er-Raster auf, Spieler an den Rand, Parkour (6 Trittplatten +0.5 mit Lücken, Münzen) zu einer Zielplattform mit LevelDoor. Ausgelassen: Camera*/CutScene*/ Force*/Trigger. Test (nicht im Spiel geprüft): T1L9M001 → 1753 Tiles, 36 Strats, 27 Typen. ## Andere Chunks (Dispatcher 0x558960) * WFPC: u32 Feature-Flags (→ 0x6da330), z. B. 0x18410690. * INFO/VERS: je u32 1 (ignoriert). * NAME: u32 + Levelname. * TRAK: Tile-Modelle (u32 n, n×0x84-Header, dann Daten; Header+0x6c u16 nVert, +0x6e nFace(0x1c), +0x78/7a/7c Anzahl 0x20-Primitive). Face+8 u16 = Index in TEXT-Texturliste. * STPC: u32 n Modelle (je 0x8c-Header direkt + Daten), u32 n Animsets (0x20-Header + Daten), [WFPC&0x100: Zusatzmodelle], dann Strat-Code bis Chunkende. * LGHT: u32 n; je Licht u8 typ, u8 r, u8 g, u8 b (×2), Typ 1/2/…: weitere u32 (Richtung/Position, z negiert). FUN_0042c180. * LNFO: 2 u32 (→ 0x5ff070, 0x5ff060), z. B. 55, 2. * LGPC: u32 n(+1), u32 m, u32; dann (n+1)·m × u32 Größen, danach die Blobs. FUN_00558db0. * FONT: 256 × 4 u16 (Glyphen-Rechtecke). SPRT: u32 (bei WFPC&0x100000 Liste). * SMPC / AMPC / SRPC: Soundbänke ("cvg"-Samples / "ambi" Ambient / Referenzen), FUN_00545350. * 'GRIM' (nicht in Level-WADs gesehen): FUN_00437b40.