documentation for level builders: docs index, step-by-step guide, file format reference (wad, geometry, objects, textures, audio, testlevel chapters, engine limits), strat vm and script language with the arena as worked example, tools overview, developer notes; readme rewritten as project front page with screenshots; review notes moved to notes/dev
This commit is contained in:
1 parent
00b2880d67
commit
624718b893
41 files changed
+2175
-259
No files matched your search
@@ -0,0 +1,56 @@
|
||||
# Reference: game files and engine
|
||||
|
||||
Everything we know about the data of *The Emperor's New Groove* (PC, 2000; German: *Ein Königreich
|
||||
für ein Lama*), found by reverse engineering `groove.exe` and checking every claim against the
|
||||
level files on the CD and in the running game. The game runs on Argonaut's **Croc 2 engine** (a PC
|
||||
port of a PlayStation design), which explains most of its quirks.
|
||||
|
||||
Confidence is marked where it matters: **certain** = verified in code and data (byte-exact round
|
||||
trip or seen in game), **likely** = consistent with the data, **unknown** = open question.
|
||||
|
||||
| Page | What it covers |
|
||||
|---|---|
|
||||
| [Game files](game-files.md) | CD and HD folders, level naming scheme, worlds and chapters, loading pictures (COR), movies, demos, save games (KSV) |
|
||||
| [WAD container](wad.md) | the chunk list of a level file and which chunk does what |
|
||||
| [Geometry and collision](geometry.md) | TRAK tile models, the MAP tile grid, coordinate spaces, collision polygons and materials, vertex colours, visibility regions |
|
||||
| [Objects, waypoints, zones](objects.md) | MAP strat records, parameters, waypoints, environment zones (fog, music, teleports), lights, the player start, checkpoints and the exit |
|
||||
| [Textures](textures.md) | TEXT chunk: pages, pixel format, texture rectangles, texture animations |
|
||||
| [Music and sound](audio.md) | ASF music, the level sound bank, speech, what is still unknown |
|
||||
| [TestLevel (world 9)](testlevel.md) | the hidden world all custom levels live in, and which chapters are safe |
|
||||
| [Engine facts and limits](engine-limits.md) | scale, jump distances, draw distance, memory budget, things that crash the game |
|
||||
|
||||
How objects *behave* (the strat bytecode) has its own section: [Strats](../strats/README.md).
|
||||
|
||||
## Where the details live
|
||||
|
||||
These pages are the readable summary. The full lab notes – with every address in `groove.exe`,
|
||||
open questions and the history of each finding – are in [`notes/`](../../notes/README.md):
|
||||
|
||||
| Topic | Lab notes |
|
||||
|---|---|
|
||||
| geometry, collision, coordinate spaces | [notes/geometry.md](../../notes/geometry.md) |
|
||||
| MAP layout, strat records, other chunks | [notes/objects.md](../../notes/objects.md) |
|
||||
| textures | [notes/textures.md](../../notes/textures.md) |
|
||||
| every object type in every level | [notes/strat_types_table.md](../../notes/strat_types_table.md) |
|
||||
| object parameters | [notes/editor-object-schemas.md](../../notes/editor-object-schemas.md) |
|
||||
| world 9 chapters | [notes/testlevel-chapters.md](../../notes/testlevel-chapters.md) |
|
||||
| level building, physics measurements, the exit | [notes/levelbau.md](../../notes/levelbau.md) |
|
||||
| fog, lights, regions | [notes/editor-tools-environment.md](../../notes/editor-tools-environment.md), [notes/mod-fog.md](../../notes/mod-fog.md) |
|
||||
| importing objects across levels | [notes/editor-cross-import.md](../../notes/editor-cross-import.md) |
|
||||
|
||||
## Code that reads and writes these formats
|
||||
|
||||
Every parser rebuilds all original files byte for byte, so the code is a reliable executable
|
||||
specification.
|
||||
|
||||
| Format | Java (LlamaMod) | Python (kit) |
|
||||
|---|---|---|
|
||||
| WAD container | [`format/Wad.java`](../../src/main/java/com/eliasmueller/llamamod/format/Wad.java) | [`tools/wadtool.py`](../../tools/wadtool.py) |
|
||||
| TRAK / STPC models, collision | [`format/Trak.java`](../../src/main/java/com/eliasmueller/llamamod/format/Trak.java), [`Model.java`](../../src/main/java/com/eliasmueller/llamamod/format/Model.java), [`Collision.java`](../../src/main/java/com/eliasmueller/llamamod/format/Collision.java), [`Stpc.java`](../../src/main/java/com/eliasmueller/llamamod/format/Stpc.java) | [`tools/geomlib.py`](../../tools/geomlib.py) |
|
||||
| MAP | [`format/MapChunk.java`](../../src/main/java/com/eliasmueller/llamamod/format/MapChunk.java) | [`tools/mapchunk.py`](../../tools/mapchunk.py) |
|
||||
| TEXT | [`format/TextChunk.java`](../../src/main/java/com/eliasmueller/llamamod/format/TextChunk.java) | [`tools/textchunk.py`](../../tools/textchunk.py) |
|
||||
| whole level | [`format/Level.java`](../../src/main/java/com/eliasmueller/llamamod/format/Level.java) | – |
|
||||
| strat code | [`format/strat/`](../../src/main/java/com/eliasmueller/llamamod/format/strat/), [`format/stpcmerge/`](../../src/main/java/com/eliasmueller/llamamod/format/stpcmerge/) | [`tools/strats.py`](../../tools/strats.py), [`tools/stpcwalk.py`](../../tools/stpcwalk.py) |
|
||||
| COR pictures | [`format/CorImage.java`](../../src/main/java/com/eliasmueller/llamamod/format/CorImage.java), [`CorEncoder.java`](../../src/main/java/com/eliasmueller/llamamod/format/CorEncoder.java) | [`tools/cor2png.py`](../../tools/cor2png.py), [`tools/png2cor.py`](../../tools/png2cor.py) |
|
||||
| KSV saves | [`save/Ksv.java`](../../src/main/java/com/eliasmueller/llamamod/save/Ksv.java) | – |
|
||||
| ASF music | – | [`tools/asf2wav.py`](../../tools/asf2wav.py) (decode only) |
|
||||
@@ -0,0 +1,73 @@
|
||||
# Music and sound
|
||||
|
||||
Sound is the least explored part of the game. This page separates what is verified from what is
|
||||
only a guess. Custom music or sound effects are **not supported yet** by any LlamaMod tool.
|
||||
|
||||
## Music
|
||||
|
||||
| Fact | Status |
|
||||
|---|---|
|
||||
| Music tracks are files `music/<NAME>.asf` on the CD (33 files) | certain |
|
||||
| A level does not reference music files directly: **environment zones** carry a music id, and the start zone's id plays when the level begins | certain |
|
||||
| Music ids map to names through a table in `groove.exe` at 0x578960 (20 bytes per entry, name pointer at +8), played by `FUN_00546b70`; id 0 stops the music | certain |
|
||||
| ASF here is Argonaut's own format (not Microsoft ASF): 4-bit ADPCM, 44.1 kHz stereo, 17-byte blocks, decoded by `asfcodec.adl` in the game folder | certain (decoder reimplemented) |
|
||||
| A second zone field (u16 at +0x56) is probably the ambience setting | likely |
|
||||
|
||||
The id table, as used by the editor's zone inspector
|
||||
([`EnvMath.MUSIC`](../../src/main/java/com/eliasmueller/llamamod/editor/tools/environment/EnvMath.java)):
|
||||
|
||||
| Id | Track | Id | Track | Id | Track | Id | Track |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 0 | (stop) | 10 | GAMEOVER | 20 | SNKJUNG | 30 | TUNE1 |
|
||||
| 1 | VILLAGE | 11 | TALLY | 21 | FROG | 31 | CHACHA |
|
||||
| 2 | JUNGLE1 | 12 | TURRACE | 22 | KRONK | 32 | JJUNGLE2 |
|
||||
| 3 | RIVER | 13 | PACHASNK | 23 | KIDLLAMA | 33 | TUNE9 |
|
||||
| 4 | JUNGLE2 | 14 | MOUNT2 | 24 | TUNE8 | 34 | TUNE6 |
|
||||
| 5 | MOUNTAIN | 15 | BALLOON | 25 | ROLLER | 35 | VICTORY |
|
||||
| 6 | CITY | 16 | JUNGSPID | 26 | BUNNY | 36 | PUZZLE2 |
|
||||
| 7 | CATACOMB | 17 | JAGCHASE | 27 | BLOCK | 37 | PLATFORM |
|
||||
| 8 | LAB | 18 | RIVER | 28 | TUNE5 | 38 | (none) |
|
||||
| 9 | TITLE | 19 | RIVRACE | 29 | CITYPUZZ | 39 | ENDGAME |
|
||||
|
||||
Four names (GAMEOVER, TALLY, PUZZLE2, PLATFORM) have no `.asf` file on the German CD; what the
|
||||
game plays for them (probably nothing) was not checked.
|
||||
|
||||
**Changing the music of a level** works today: pick another id for the start zone (or any zone)
|
||||
in the editor's *Environment* panel ([manual: Environment](../manual/12-environment.md),
|
||||
[tutorial: Edit an original level](../manual/tutorial-original-level.md)).
|
||||
|
||||
**Listening to the tracks:** [`tools/asf2wav.py`](../../tools/asf2wav.py) `in.asf out.wav`
|
||||
decodes a track exactly like the game's codec (filter 0: `s = s1 + (n << shift)`, filter 1:
|
||||
`s = 2·s1 − s2 + (n << shift)`, 16-bit wrap-around instead of clipping – FFmpeg's decoder clips and
|
||||
drifts).
|
||||
|
||||
**New music – open:** there is no encoder yet. Writing one (the inverse of `asf2wav.py`) and
|
||||
replacing a track file through a mod would be the natural next step; the header fields after the
|
||||
block count (`rate`, `flags` with bit 1 = stereo) are read by the decoder, the others are *unknown*.
|
||||
|
||||
## Sound effects
|
||||
|
||||
| Fact | Status |
|
||||
|---|---|
|
||||
| Each level has its own sample bank, chunk **SMPC**: `u32 n`, n × `{u32 "cvg " tag, u32 length, data}` | certain ([`SoundMerge.java`](../../src/main/java/com/eliasmueller/llamamod/format/stpcmerge/SoundMerge.java)) |
|
||||
| Strats play samples by index (`soundPlay(sound:N)`, opcodes 0xd5 / 0x99 / 0x131 play, stop, is-playing); at most **128** samples per level, ids with bit 0x80 are the front-end sounds | certain |
|
||||
| The samples are PlayStation-style VAG ADPCM, decoded by `Vagcodec.adl` | likely (tag and codec name; the sample header was not decoded) |
|
||||
| **AMPC** holds ambient sound data (`ambi` / `VABp` blocks) | likely, not decoded |
|
||||
| `FESOUND.WAD` is the front-end sound bank (not a chunk file) | likely |
|
||||
|
||||
Samples can be **moved between levels**: the editor's cross-world import and the arena copy the
|
||||
samples an imported object needs into the target level and renumber the `sound:N` operands
|
||||
([notes/editor-cross-import.md](../../notes/editor-cross-import.md)). Changing which sample an
|
||||
object plays is a constant edit in its script ([Strats](../strats/custom-types.md)). Exporting or
|
||||
replacing a sample's audio is not possible yet.
|
||||
|
||||
## Speech and dialogue
|
||||
|
||||
* **LGPC** holds the dialogue texts of a level in all languages plus a speech slot per line.
|
||||
* A speech slot `#<hex>` is an index into **SRPC**, whose entries are offset/size pairs into the
|
||||
global speech file `music/<language>.cvs` in the game folder (the German version ships
|
||||
`english.cvs`).
|
||||
* Strats show texts with opcodes 0xf0 / 0xfa (`topSay text:N`). Imported objects bring their
|
||||
texts and speech references along.
|
||||
|
||||
The CVS file format itself was not analysed.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Engine facts and limits
|
||||
|
||||
Numbers to design levels with, and the things that crash the game. Measured in the game with the
|
||||
test harness unless noted.
|
||||
|
||||
## Scale and movement
|
||||
|
||||
One unit = one grid cell. Measured with the jump lab ([`levels/sprunglabor.py`](../../levels/sprunglabor.py))
|
||||
and a coin measuring track ([notes/levelbau.md](../../notes/levelbau.md#findings-from-the-game-tests-lamatest-xvfb)):
|
||||
|
||||
| Quantity | Value |
|
||||
|---|---|
|
||||
| llama height | about 0.6 |
|
||||
| walking speed | about 1.1 units/s (≈ 1 s to accelerate) |
|
||||
| highest edge a jump clears | 0.35 (0.5 is too high) |
|
||||
| long jump with a run-up | about 1.3 units (from standing: much shorter, a 0.5 gap fails) |
|
||||
| comfortable parkour | gaps of 0.3 with +0.2 height, platforms ≥ 1.5 long |
|
||||
| step-up without jumping | none – any edge with wall collision blocks; use slopes or walls without collision |
|
||||
| frame rate of the logic | 30 frames per second (script speeds are per frame) |
|
||||
|
||||
Controls are **camera-relative**; the start camera sits behind the llama and moves aside if
|
||||
something solid is right behind it – keep the area behind start points clear.
|
||||
|
||||
## Drawing
|
||||
|
||||
| Fact | Value |
|
||||
|---|---|
|
||||
| draw distance | = **fog end** of the active environment zone; originals 7–16 cells at detail *Maximum* (roller coaster up to 23), about half at *Minimum* |
|
||||
| objects | drawn only within about **12.5 units of the llama** (they keep running); their activity radius also follows the fog end |
|
||||
| hidden areas | visibility regions: a cell is drawn only if the camera region's mask contains its region |
|
||||
| texture coordinates | none per vertex – every triangle shows half of a texture rectangle |
|
||||
| lights | light only object models; tiles use baked vertex colours |
|
||||
|
||||
The *Draw distance* mod scales all fog values of all levels
|
||||
([notes/mod-fog.md](../../notes/mod-fog.md)).
|
||||
|
||||
## Memory and counts
|
||||
|
||||
| Limit | Value | Source |
|
||||
|---|---|---|
|
||||
| level memory | **16 MB** for STPC, TEXT, TRAK, MAP, LGPC and the strat pool; originals use up to ~7 MB | `FUN_0041eea0` |
|
||||
| strat heap | 65533 words (≈ 256 KB) for strat slots (0x154 bytes each), collision blocks and variables | `FUN_0041ae20` |
|
||||
| strat slots | `poolExtra + 30` – must cover placed **and** spawned objects | MAP |
|
||||
| vertices per tile model | 8000 (transform buffer) | |
|
||||
| collision polygon | within ±8 units of its tile's origin | i16 fixed point |
|
||||
| texture pages | 127 | signed page byte |
|
||||
| texture entries | 65535 | u16 face field |
|
||||
| sound samples | 128 per level | signed char id |
|
||||
| file sizes that worked | TRAK up to 1 MB, 5800 tiles, WAD up to ~7 MB (originals); the test world: 628 models, 2474 tiles, 6.3 MB | measured |
|
||||
|
||||
## Things that crash the game
|
||||
|
||||
| Cause | Symptom |
|
||||
|---|---|
|
||||
| a tile outside the W × H grid | memory corruption, crash on load or later |
|
||||
| vertex-colour block not exactly one entry per vertex | objects read shifted, crash |
|
||||
| a strat type with collision but type flags 0 (or too few collision-bone words) | page fault in the heap allocator `FUN_0041ae20` |
|
||||
| exit target (7, 7, 7) in world 9 | crash after saving (world 10 has no name) |
|
||||
| a selectable chapter without a WAD | "Couldnt load wad" on the world map |
|
||||
| a waypoint-dependent type without its boxes | not a crash: the game removes the object ("removing strat") |
|
||||
| world-9 chapters 2–31 (unpatched exe) | progress, language or options overwritten ([TestLevel](testlevel.md)) |
|
||||
|
||||
## Coordinates, numbers, angles
|
||||
|
||||
* Game space: x east, y up, z = grid row; strats use 20.12 fixed point (4096 = 1.0).
|
||||
* MAP angles: 4096 = 360°; strat angles: 0x1000000 = 360°.
|
||||
* Objects on a flat original floor tile stand at `tile.y + 0.25`.
|
||||
@@ -0,0 +1,99 @@
|
||||
# Game files
|
||||
|
||||
What is on the CD and on the hard disk, how files are named, and the small formats around the
|
||||
levels. The level files themselves are described in [WAD container](wad.md).
|
||||
|
||||
## Two folders: HD and CD
|
||||
|
||||
The installer copies a small **HD** part to the hard disk; everything big stays on the **CD**. The
|
||||
game looks up each file in `HDPath\dir\file`, then in `CDPath\dir\file`, then on every CD drive
|
||||
(`FUN_00415690`). A plain folder in `CDPath` is enough – no real CD drive is needed. LlamaMod relies
|
||||
on this: it points `CDPath` at its own `cd-active` folder, where mod outputs replace original files
|
||||
([notes/llamamod-app.md](../../notes/llamamod-app.md#registry-cd-path-and-the-exe-patch)).
|
||||
|
||||
| Folder | Contents |
|
||||
|---|---|
|
||||
| HD (`Setup/<lang>/Hd` on the CD) | `groove.exe`, `aal.dll`, `aleax2.3dl`, the audio codecs `asfcodec.adl` and `Vagcodec.adl`, `music/english.cvs` (speech), save games `king0.ksv`…`king2.ksv` |
|
||||
| CD (`Setup/<lang>/Cd`) | `wads/` level files, `music/*.asf` music, `movies/*.dat` videos, `*.cor` pictures, `test9.tle`, empty folders `gallery/`, `in/`, `out/` |
|
||||
|
||||
Registry key (all values): `HKEY_LOCAL_MACHINE\Software\Disney Interactive\Emperors New Groove\1.0`
|
||||
with `CDPath`, `HDPath`, screen size, volumes and devices. LlamaMod's base patch of `groove.exe`
|
||||
redirects it to `HKEY_CURRENT_USER`, so no admin rights are needed.
|
||||
|
||||
Development used the **German CD**. Other language versions are expected to use the same formats
|
||||
but were not tested.
|
||||
|
||||
## Level naming scheme
|
||||
|
||||
Levels are named **`t<tribe>l<world>m<chapter>`** (tribe is always 1 in the game):
|
||||
|
||||
| World | Name | Files | Notes |
|
||||
|---|---|---|---|
|
||||
| 1 | Village | t1l1m001–004 | `t1l1m001` is the default base level for new levels |
|
||||
| 2 | Jungle at Night | t1l2m001–003 | t1l2m003 is the llama chase (player strat `LlamaChase`) |
|
||||
| 3 | River | t1l3m001–004 | player strat `KuscoAndPachaTogetherOnATree` |
|
||||
| 4 | Jungle by Day | t1l4m001–003 | t1l4m002 has the Alligator (used by the arena) |
|
||||
| 5 | Mountain | t1l5m001–003 | |
|
||||
| 6 | City | t1l6m001–003 | |
|
||||
| 7 | Catacombs | t1l7m001–005 | m001/3/5 are roller-coaster rides (player strat `RollerCoaster`) |
|
||||
| 8 | Lab | t1l8m001–006 | |
|
||||
| 9 | TestLevel | none on the CD | 49 chapter slots, see [TestLevel](testlevel.md) |
|
||||
|
||||
Other WADs: `t0i0m000` (front end), `t0i0m998` / `t0i0m999` (outro and game-over backdrops),
|
||||
`t6l6m001/002` (memory-card/options screens – *likely*), `FESOUND.WAD` (front-end sound bank, not
|
||||
a chunk file). The chapter counts the world map uses are a table in `groove.exe` (0x572420):
|
||||
4, 3, 4, 3, 3, 3, 5, 5, 49 – world 8 has one more file than its table entry (*unknown* why).
|
||||
|
||||
## Pictures (COR)
|
||||
|
||||
| File | Use |
|
||||
|---|---|
|
||||
| `T1<w><c>.COR` | loading picture of world *w*, chapter *c* (`T191.COR` … `T1949.COR` for TestLevel) |
|
||||
| `<w><c><n>.cor` | concept art shown with a chapter (*likely*) |
|
||||
| `tally<n>.cor`, `gameover.cor`, `loading.cor`, logos | screens; `gameovertest.cor` is unused |
|
||||
|
||||
Format (*certain*, [`tools/cor2png.py`](../../tools/cor2png.py)): 8-byte signature
|
||||
`17 98 AF 89 FE 42 D1 12`, u32 version 1, u32 unpacked size, u32 packed size, then LZ data:
|
||||
a byte `n < 0x80` = *n* literal bytes follow; `1LLLoooo oooooooo` = copy L+3 bytes from *o* bytes
|
||||
back. The result is 640×480 RGB (a few greyscale ones). [`tools/png2cor.py`](../../tools/png2cor.py)
|
||||
and [`CorEncoder.java`](../../src/main/java/com/eliasmueller/llamamod/format/CorEncoder.java) write
|
||||
it; LlamaMod uses that for the loading pictures of new levels.
|
||||
|
||||
## Movies and demos
|
||||
|
||||
* `movies/<w><c><n>intro.dat` / `…outro.dat` and the logo movies: MPEG-1 video 320×240, 29.97 fps,
|
||||
MP2 audio, played through DirectShow. The Intro skip mod patches the calls that start them
|
||||
([notes/mod-introskip.md](../../notes/mod-introskip.md)).
|
||||
* `wads/*.DEM`: recorded attract-mode demos, u32 frame count + 8 bytes of input per frame
|
||||
(*likely*; not used by LlamaMod).
|
||||
|
||||
## Save games (KSV)
|
||||
|
||||
`king0.ksv`…`king2.ksv` in the game folder, 256 bytes each. Decoded from the load/save functions
|
||||
`FUN_0041c640` / `FUN_0041c820`; code: [`save/Ksv.java`](../../src/main/java/com/eliasmueller/llamamod/save/Ksv.java).
|
||||
|
||||
| Offset | Meaning |
|
||||
|---|---|
|
||||
| 0x00 | used flag (0 = empty) |
|
||||
| 0x01 / 0x02 | lives / energy |
|
||||
| 0x03–0x06 | world-map cursor: tribe, world, chapter, map |
|
||||
| 0x07 | number of gold coins |
|
||||
| 0x08–0x3D | u8 [9][6] chapter flags (bit 0 collectible found, bit 1 all coins = gold coin) |
|
||||
| 0x3E–0x41 | last played tribe, world, chapter, map |
|
||||
| 0x42 / 0x43 | highest unlocked world / chapter |
|
||||
| 0x44–0xAF | u16 [9][6] coins per chapter |
|
||||
| 0xB0–0xCF | unknown |
|
||||
| 0xD0–0xD9 | options (effects and music volume, speaker mode, …) |
|
||||
|
||||
To start a TestLevel chapter without a mod, set the cursor, the last played and the highest
|
||||
unlocked world/chapter to 9 and the chapter. LlamaMod does this with `llamamod --unlock <slot> 9 <chapter> --cursor`
|
||||
and with the *Save games* tab.
|
||||
|
||||
## Hidden debug features
|
||||
|
||||
Strings and code in `groove.exe` point to leftovers of the developers' tools. They were seen in the
|
||||
binary but **not** verified or used by LlamaMod: F6 writes a screenshot `capture<n>.tga`, the
|
||||
command-line switch `-display_diagnostics` writes a `<PC name>.dispdiag` file, a log
|
||||
`c:\kingdom_dbg.txt`, and an unused slide-show player that reads the empty `in`/`out` folders.
|
||||
The *Built-in cheats* mod uses the game's own cheat codes
|
||||
([notes/mod-cheats.md](../../notes/mod-cheats.md)).
|
||||
@@ -0,0 +1,155 @@
|
||||
# Geometry and collision
|
||||
|
||||
The world is **not** one big mesh and there is no BSP. It is a **grid of tiles**, inherited from
|
||||
Croc: TRAK holds a set of small models (the level's construction kit), and MAP places them on a
|
||||
W × H grid, one cell = one unit. Every byte described here is reproduced exactly by
|
||||
[`format/Trak.java`](../../src/main/java/com/eliasmueller/llamamod/format/Trak.java),
|
||||
[`format/MapChunk.java`](../../src/main/java/com/eliasmueller/llamamod/format/MapChunk.java) and
|
||||
[`tools/geomlib.py`](../../tools/geomlib.py) for all original levels.
|
||||
|
||||
Full detail with all loader addresses: [notes/geometry.md](../../notes/geometry.md).
|
||||
|
||||

|
||||
|
||||
## Coordinate spaces
|
||||
|
||||
| Space | Used by | Axes |
|
||||
|---|---|---|
|
||||
| **Game space** | strats, collision, MAP fixed-point tile positions, lights | x east, **y up**, z = grid row (positive); 20.12 fixed point (4096 = 1.0); right-handed as the game shows it |
|
||||
| **Export space** | TRAK/STPC vertices, MAP float tile positions | same, but **z negated** (grid at z ∈ (−H, 0]); drawn by a Direct3D (left-handed) camera |
|
||||
| **File space** | OBJ/glTF of every LlamaMod and kit tool | = game space (y up, right-handed): Blender shows a level the same way round as the game |
|
||||
|
||||
Converting export ↔ file space negates z and reverses the triangle corner order. Angles:
|
||||
4096 = 360° in MAP records; the strat VM uses 0x1000000 = 360°. A tile is turned only about y
|
||||
(0, 1024, 2048, 3072 in practice). Rotation 0 faces +z, 90° faces +x.
|
||||
|
||||
## TRAK: tile models (*certain*)
|
||||
|
||||
`u32 n`, then **all n headers** (0x84 bytes each), then the data of every model in order.
|
||||
Typical levels have 100–650 models with up to 422 vertices / 798 faces each.
|
||||
|
||||
| Header field | Meaning |
|
||||
|---|---|
|
||||
| 0x00 | bounding-box centre (3 × f32) |
|
||||
| 0x0c | 8 box corners (used for frustum culling) |
|
||||
| 0x6c / 0x6e | vertex count / face count |
|
||||
| 0x78 / 0x7a / 0x7c | collision polygon counts in three groups (the engine always uses the sum; meaning of the groups *unknown*) |
|
||||
| pointers | 0 in the file, set on load |
|
||||
|
||||
Data per model:
|
||||
|
||||
* **Vertex**, 24 bytes: `f32 x, y, z, nx, ny, nz` (lighting comes from the MAP vertex colours, not
|
||||
from the normals).
|
||||
* **Face**, 28 bytes, triangles only: `u16 flags, u16 v0, v1, v2, u16 texture, u16 0, f32 plane
|
||||
nx, ny, nz, d`.
|
||||
* **Collision polygon**, 32 bytes: `u8 type, u8 material`, then 5 planes `{i8 nx, ny, nz, u8 0,
|
||||
i16 d}`. Plane 0 is the surface, planes 1–4 the edges (normals point inwards). Type bit 0 =
|
||||
quad, otherwise triangle. Normals are ×127, d is 20.12 fixed point – so a collision polygon
|
||||
must lie within **±8 units of its tile's origin**.
|
||||
|
||||
### Face flags
|
||||
|
||||
| Bit | Meaning |
|
||||
|---|---|
|
||||
| 0x0008 | start of a new render batch (texture page or blend mode changes) – must be set on the first face of each batch, at the latest every 256 faces |
|
||||
| 0x0010, 0x0020, 0x0800 | which corners of the texture rectangle the triangle uses (below) |
|
||||
| 0x0400 | double-sided (no backface culling) |
|
||||
| 0x4000 | ignore vertex colours |
|
||||
| 0x0001, 0x0002 | special sorting/transparency modes (*likely*) |
|
||||
| others | present in the data, not read by the PC renderer (*unknown*, probably PlayStation leftovers) |
|
||||
|
||||
### No free texture coordinates
|
||||
|
||||
The engine has **no per-vertex UVs**. Every triangle shows one half of a texture rectangle from the
|
||||
TEXT table: with flag 0x800 the face is the first or (with 0x10) second half of a quad, without it
|
||||
a triangle covering the lower-left half; 0x20 mirrors horizontally. 96 % of world faces are quad
|
||||
halves. This is why the level builder and editor split surfaces into 1 × 1 pieces and why a narrow
|
||||
wall shows a squashed texture. See [Textures](textures.md).
|
||||
|
||||
## MAP: the tile grid (*certain*)
|
||||
|
||||
MAP starts with the grid and the tiles; objects follow (see [Objects](objects.md)). The blocks that
|
||||
concern geometry:
|
||||
|
||||
| Block | Format |
|
||||
|---|---|
|
||||
| header | `u32 nTiles, u32 W, u32 H` |
|
||||
| tiles (floats) | nTiles × `{f32 x, y, z, u32 rotY, u32 model, u32 hasAlt}` in export space; x, z = cell centre |
|
||||
| regions | 32 × `{u32 visibility mask, i32 value}`, then W·H × u32 region number per cell |
|
||||
| tiles (fixed point) | nTiles × `{rx, ry, rz, x, y, z}` 20.12 in game space – used for drawing and collision; must match the float copy exactly |
|
||||
| model index | nTiles × u32 (= the float block's `model`) |
|
||||
| tile swaps (WFPC 0x10000) | one `{cellX, y, cellZ, modelA, modelB}` per tile with `hasAlt ≠ 0`: doors and bridges that change their model (strat opcodes 0x11a / 0x11b) |
|
||||
| vertex colours | per tile: one u32 RGBA per vertex of its model (0x80 = 1.0, the game doubles and clamps); more sets for animated colours; then the same for the swap model |
|
||||
|
||||
Rules that crash the game or corrupt the level when broken:
|
||||
|
||||
* every tile must lie inside the grid (`0 ≤ x < W`, `0 ≤ row < H`) – outside, the cell list
|
||||
writes into foreign memory;
|
||||
* the vertex-colour block must have **exactly** one entry per vertex of the tile's model – else
|
||||
everything after it (the objects) is read shifted;
|
||||
* the float and fixed-point copies and the model index must agree;
|
||||
* tile swaps: one entry per tile with `hasAlt ≠ 0`, in tile order.
|
||||
|
||||
The cell of a tile is `floor(x) + floor(−z) · W` (export-space floats).
|
||||
|
||||
## Collision (*certain*)
|
||||
|
||||
* Only **the tiles listed in the cell the player stands in** are tested (`FUN_004036d0` floors and
|
||||
ceilings, `FUN_00403ad0` walls). A wall polygon that sticks into the neighbouring cell has no
|
||||
effect there – builders enter a polygon into every cell it covers, as a collision-only tile if
|
||||
needed.
|
||||
* Collision is in game space, relative to the tile's fixed-point position.
|
||||
* There is **no step-up logic**: a 0.12-unit edge with a wall polygon already blocks the llama.
|
||||
Low plinths need walls without collision; stairs need an invisible slope.
|
||||
|
||||
### Collision materials
|
||||
|
||||
The material byte ends up in the player's ground state and is read by strats (`floorMaterial`).
|
||||
|
||||
| Material | Meaning |
|
||||
|---|---|
|
||||
| 0 | normal ground |
|
||||
| **16** | **exit floor**: the LevelDoor ends the level only when the llama stands on material 16 in the door's cell; also used under the village house doors (zone teleports) |
|
||||
| 17, 18 | ignored when WFPC flag 0x8000000 is set (*likely*: special surfaces) |
|
||||
| **30** (0x1e) | death plane / water (*likely*): falling onto it empties the energy and the llama respawns at the last checkpoint or the start (Model 0 of many levels is such an invisible plane at y = −2.75) |
|
||||
| others | special surfaces, meaning *unknown* – the debug view colours them |
|
||||
|
||||

|
||||
|
||||
## Visibility regions (no BSP)
|
||||
|
||||
Drawing (`FUN_0042bf40`) walks the grid cells within the draw distance around the camera and draws
|
||||
a cell's tiles **only if the cell's region bit is set in the mask of the camera's region**. Up to 32
|
||||
regions; mask 0 or a camera outside the grid means "everything". New geometry must therefore get a
|
||||
region that the surrounding regions can see, or it stays invisible – the builders put everything
|
||||
into region 0 with mask `0xffffffff`. Objects in hidden cells are skipped too (unless strat flag
|
||||
0x800). The editor's *Visibility regions* tool edits this
|
||||
([manual chapter 13](../manual/13-visibility.md)).
|
||||
|
||||
## STPC: object models and code
|
||||
|
||||
STPC starts with the **object models** (same model format as TRAK, header 0x8c bytes, optionally
|
||||
skinned with a bone table), then the **animation sets** (per-frame bone matrices and compressed
|
||||
vertex deltas – the compression is only partly understood), then the **strat code units**
|
||||
([Strats](../strats/README.md)). The engine never parses the code; MAP records and bytecode refer
|
||||
into STPC by absolute byte offsets, so STPC can only be extended at the end or merged with
|
||||
relocation ([notes/editor-cross-import.md](../../notes/editor-cross-import.md)).
|
||||
|
||||
## WFPC flags
|
||||
|
||||
WFPC is a u32 of feature flags. Some change the data layout and must stay as they are: 0x10,
|
||||
0x100, 0x10000 (tile swaps), 0x100000, 0x200000, 0x4000000, 0x10000000. Others: 0x800 renderer
|
||||
fog flag, 0x80000 read by a strat opcode, 0x8000000 makes collision materials 17/18 ignored.
|
||||
|
||||
## Writing new geometry – checklist
|
||||
|
||||
1. TRAK: correct counts, bounding box and face planes recomputed, texture index below the
|
||||
number of texture entries, batch bit 0x8, ≤ 8000 vertices per model.
|
||||
2. Collision polygons within ±8 units of the tile origin, entered into every cell they cover.
|
||||
3. MAP: tiles inside the grid, float/fixed/model blocks consistent, vertex colours exactly per
|
||||
vertex, tile-swap block matching `hasAlt`, a visible region.
|
||||
4. Rewrite the WAD's total length.
|
||||
|
||||
The editor and both level builders do all of this; the details of the builder's approach
|
||||
(splitting into tiles per cell and height band, merging identical tiles, baking sun, sky and
|
||||
ambient occlusion into vertex colours) are in [notes/levelbau.md](../../notes/levelbau.md).
|
||||
@@ -0,0 +1,141 @@
|
||||
# Objects, waypoints, zones and lights
|
||||
|
||||
Everything that is not static geometry lives in the second half of the MAP chunk: object
|
||||
instances ("strats"), their parameters, waypoint chains and environment zones. Lights are in their
|
||||
own chunk, LGHT. Full byte layouts: [notes/objects.md](../../notes/objects.md).
|
||||
|
||||
## Strats: objects are programs
|
||||
|
||||
An object – coin, enemy, door, camera, the player – is an instance of a **strat**: a script in the
|
||||
level's STPC code. A MAP record says *where* and *which code*; the code does the rest. There is no
|
||||
type id: **the type is the code's byte offset in STPC**. Type names come from the code itself
|
||||
(a `debugName "Coin"` instruction at the entry point). How the code works: [Strats](../strats/README.md).
|
||||
|
||||
About 300 types are placed in the original levels, about 200 more are only spawned by other
|
||||
strats (splinters, effects, heads). The full list of which type appears in which level, with
|
||||
instance counts, is [notes/strat_types_table.md](../../notes/strat_types_table.md)
|
||||
(`tools/strat_table.py`).
|
||||
|
||||
## The MAP object blocks
|
||||
|
||||
After the geometry blocks ([Geometry](geometry.md#map-the-tile-grid-certain)):
|
||||
|
||||
| Block | Format |
|
||||
|---|---|
|
||||
| parameter pool | `u32 n, n × u32` – all object parameters of the level, referenced by index |
|
||||
| environment zones | `u32 n, n × 90 bytes` (below) |
|
||||
| waypoints | `u32 n, n × 34 bytes {u32 next, u32, u16 rx, ry, rz, s32 x, y, z, u32, u32}`; `next ≠ 0` = the following entry belongs to the same chain |
|
||||
| strats | `u32 nStrats, u32 poolExtra`, nStrats × 58-byte records |
|
||||
| optional tail | paths (WFPC 0x200000, unused by the CD levels), a buffer size (WFPC 0x10), a u16 |
|
||||
|
||||
### Strat record (58 bytes, *certain* unless noted)
|
||||
|
||||
| Offset | Field |
|
||||
|---|---|
|
||||
| 0 | rotation x, y, z (u16 each, 4096 = 360°; x/z almost always 0) |
|
||||
| 6 | position x, y, z (s32, 20.12 fixed point, game space) |
|
||||
| 18 | **code offset in STPC = the type** |
|
||||
| 22 / 26 | parameter count / index into the parameter pool (−1 if none) |
|
||||
| 30 | number of strat variables (fixed per type) |
|
||||
| 34 | 16-byte slots for event handlers (fixed per type, *likely*) |
|
||||
| 38 | type flags: collision and physics setup (fixed per type) |
|
||||
| 42 | extra memory words (collision bones; fixed per type) |
|
||||
| 46 | first waypoint index or −1 |
|
||||
| 54 | flags; bit 1 = not created at level start |
|
||||
|
||||
`poolExtra` is the number of strat slots beyond 30: the engine allocates `poolExtra + 30` slots,
|
||||
which must cover all placed objects **plus everything spawned at run time**.
|
||||
|
||||
The type fields (variables, slots, flags, extra) come from the compiler and must be copied from an
|
||||
original instance of the same type; a type that uses collision but has type flags 0 crashes the
|
||||
game. Builders and the editor do this automatically.
|
||||
|
||||
### Parameters
|
||||
|
||||
The *n* parameters of an instance are pushed onto the new strat's stack and become its
|
||||
**variables v0 … v(n−1)**. Values are 20.12 fixed point by convention (0x1000 = 1.0), booleans
|
||||
0 / 0x1000, counts and world/chapter numbers `n << 12`. The editor knows the meaning of the
|
||||
parameters of many types (schemas in
|
||||
[`editor/tools/objects/Schemas.java`](../../src/main/java/com/eliasmueller/llamamod/editor/tools/objects/Schemas.java),
|
||||
findings in [notes/editor-object-schemas.md](../../notes/editor-object-schemas.md)); the others are
|
||||
shown raw with the values used in the original levels.
|
||||
|
||||
### Waypoints
|
||||
|
||||
A waypoint chain is a list of positions used for patrol paths, moving platforms, camera paths –
|
||||
and for some types (guards, box walkers) as a **pair of boxes** the object must stand in, or the
|
||||
game removes it ("removing strat"). Guards also wait for an **event bit** (global variable 95) that
|
||||
another object sets ([notes/editor-object-schemas.md](../../notes/editor-object-schemas.md#event-bits-global-95)).
|
||||
|
||||
The **player's waypoint chain is the start camera**: the camera takes the first node within 2.5
|
||||
cells of the llama. A good start has one node about 2 cells behind the llama and 0.5 above it, and
|
||||
nothing solid right behind the llama (the camera moves aside).
|
||||
|
||||
## Special objects
|
||||
|
||||
| Role | Type | Notes |
|
||||
|---|---|---|
|
||||
| player start | `Llama` | exactly one per level (`RollerCoaster`, `KuscoAndPachaTogetherOnATree`, `LlamaChase` in special levels) |
|
||||
| checkpoint | `CheckPointPost` (spawns `CheckPointHead`), `CheckPointBox` | the respawn point after a fall |
|
||||
| exit | `LevelDoor` | invisible trigger, see below; variants `VillageEndDoor`, `EndGameDoor` |
|
||||
| cameras | `CameraSide`, `CutSceneCameraDirectional`, `CutSceneTrigger` | camera zones and cut scenes |
|
||||
| always present | `CheatTeleport`, `Wompy`, `CutSceneTrigger` | |
|
||||
|
||||
### The exit (LevelDoor) – *certain*
|
||||
|
||||
The LevelDoor has no model. Every frame it checks:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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)).
|
||||
|
||||
## Environment zones (90 bytes, *certain* for the fields below)
|
||||
|
||||
A zone sets fog, colours, music and sky, and optionally teleports the player.
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| cell x, y, z | the trigger cell |
|
||||
| target x, y, z, angle | teleport target and facing |
|
||||
| flags (+0x20) | flag 8 = **start zone** (exactly one per level: the level begins with its settings); flag 4 = do not move the player |
|
||||
| sky model | STPC offset of a sky model (0 = none) |
|
||||
| fog mode | 0 none, 1 darkness with a light around the player, 2 table fog, 3 vertex fog |
|
||||
| fog start / end | two pairs (detail Minimum and Maximum); **fog end = far clip = draw distance** |
|
||||
| colours | ambient colour for object lighting, fog/backdrop colour |
|
||||
| music id | 1–39 (see [Music](audio.md#music)), 0 stops the music |
|
||||
|
||||
Teleports are triggered from strat code (opcode 0x100 looks up the zone of the calling object's
|
||||
cell). In the original levels the trigger cells hold exit floors (material 16) or door objects –
|
||||
this is how the village house doors work. The editor edits zones in the
|
||||
*Environment* panel ([manual chapter 12](../manual/12-environment.md)).
|
||||
|
||||
## Lights
|
||||
|
||||
LGHT: `u32 n`, then per light a type, a colour (×2/255) and direction or position (game space).
|
||||
Lights only light **object models** (ambient + all directional lights + the nearest point lights,
|
||||
at most 6). Tiles use baked vertex colours, so changing a light does not change the ground – bake
|
||||
the vertex colours again. Point lights are full up to radius *a* and fade to 0 at *b*; one byte
|
||||
makes them flicker.
|
||||
|
||||
## Tools
|
||||
|
||||
| Task | Tool |
|
||||
|---|---|
|
||||
| list types, instances, parameters of a level | [`tools/strats.py`](../../tools/strats.py) `level.wad [--all]`, [`tools/map_dump.py`](../../tools/map_dump.py) `--strats --tiles --lights` |
|
||||
| type × level table | [`tools/strat_table.py`](../../tools/strat_table.py) |
|
||||
| place objects from a script | [`tools/levelbuild.py`](../../tools/levelbuild.py) `L.strat(...)`, Java [`LevelBuilder`](../../src/main/java/com/eliasmueller/llamamod/levelbuild/LevelBuilder.java) |
|
||||
| place, edit, check objects visually | the level editor ([manual chapter 9](../manual/09-objects.md), [10](../manual/10-waypoints.md), [11](../manual/11-cameras-triggers.md)) |
|
||||
@@ -0,0 +1,51 @@
|
||||
# TestLevel (world 9)
|
||||
|
||||
The game has a hidden ninth world, **TestLevel**, with 49 chapter slots – but the retail CD ships
|
||||
no data for it. It is the natural home for custom levels: originals stay untouched, and the world
|
||||
map can reach it once a save game unlocks it. Every LlamaMod level mod, the Test World and the
|
||||
Alligator Arena are installed as `wads/T1L9M0nn.wad` with a loading picture `T19n.COR`.
|
||||
|
||||
Code: [`save/TestLevelChapters.java`](../../src/main/java/com/eliasmueller/llamamod/save/TestLevelChapters.java),
|
||||
lab notes: [notes/testlevel-chapters.md](../../notes/testlevel-chapters.md).
|
||||
|
||||
## Use chapters 1 and 32–49
|
||||
|
||||
The game keeps per-chapter coin counters and flag bytes in arrays that only have rows for worlds
|
||||
0–8 (index `world · 6 + chapter`). For world 9, chapters 2–31 land on **other game variables**:
|
||||
|
||||
| Chapter | What its coin counter / flag byte overwrites |
|
||||
|---|---|
|
||||
| 1 | nothing (free) |
|
||||
| 5–8 | the last played tribe/world/chapter/map |
|
||||
| 6–9 (flags) | the **language index** |
|
||||
| 9 | the **furthest progress** – the world map breaks |
|
||||
| 10–31 | options, HUD state and tables used by the original levels |
|
||||
| **32–49** | tables of tribes 2 and 3, which the game never uses – free |
|
||||
|
||||
So custom levels use **chapter 1 or 32–49**. The editor only offers these, new projects take the
|
||||
first free chapter from 32, and old projects on other chapters are moved automatically.
|
||||
|
||||
LlamaMod additionally relocates both arrays to a 128-entry buffer in unused space of `groove.exe`
|
||||
(base exe patch, revision 2), so even picking an empty chapter on the world map is harmless with
|
||||
LlamaMod's executable. Mods still keep to 1 and 32–49 so they also work with an unpatched exe.
|
||||
|
||||
## Making world 9 reachable
|
||||
|
||||
* **Empty chapters must exist.** Finishing chapter *n* unlocks chapter *n* + 1, and the world map
|
||||
crashes ("Couldnt load wad") when a selectable chapter has no file. LlamaMod links every missing
|
||||
`T1L9M002…049.wad` to an installed world-9 WAD and `T192…T1949.COR` to `T191.COR`.
|
||||
* **Never exit to (7, 7, 7).** "World finished" in world 9 unlocks a world 10 that has no name;
|
||||
the game crashes after saving. Exits in TestLevel point to (1, 9, chapter).
|
||||
* **Get there:** set a save game's map cursor to world 9 (`llamamod --unlock <slot> 9 <chapter>
|
||||
--cursor`, the launcher's *Save games* tab), or use the *Boot to level* mod / the editor's
|
||||
play-test, which start the game directly in the level.
|
||||
|
||||
## Tally totals
|
||||
|
||||
The tally screen after the exit shows "coins collected *x* / *total*". *Total* is not counted by
|
||||
the game – it is the **LNFO** chunk (`u32 coins, u32 secrets`). A new level built on the village
|
||||
would show the village's 55. LlamaMod recounts it for new levels (Coin = 1, Coin5 = 5, Secret
|
||||
objects; the arena adds its dynamic coins).
|
||||
|
||||
Cosmetic leftovers: the world map shows no background for world 9 and a garbage coin target, and
|
||||
the loading screen says "TestLevel Kapitel *n*".
|
||||
@@ -0,0 +1,53 @@
|
||||
# Textures (TEXT chunk)
|
||||
|
||||
Each level carries its own textures. Parse and rebuild are byte-identical for all level WADs
|
||||
(including the RLE encoder). Code: [`format/TextChunk.java`](../../src/main/java/com/eliasmueller/llamamod/format/TextChunk.java),
|
||||
[`tools/textchunk.py`](../../tools/textchunk.py). Lab notes: [notes/textures.md](../../notes/textures.md),
|
||||
editor side: [notes/editor-tools-textures.md](../../notes/editor-tools-textures.md).
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
u32 nPal 0 in all WADs (8-bit palettes are unused)
|
||||
u32 nPages 13–22 per level, all 256×256
|
||||
nPages × { u32 flags, u32 w, u32 h, data }
|
||||
flags & 0x80 : u32 length + RLE16 (all original pages)
|
||||
other ≠ 0 : w·h raw u16
|
||||
u32 nTex ~180–1260 texture entries
|
||||
nTex × { u16 flags, u8 page, u8 clut (0xff), u8 x0, x1, y0, y1 } (edges inclusive)
|
||||
u32 nAnim texture animations
|
||||
nAnim × { u16 texIndex, u8 type, payload } type 8 = flip book: list of texture indices
|
||||
```
|
||||
|
||||
RLE16: a sequence of s16 *n*; *n* > 0 → *n* literal pixels follow, *n* < 0 → the next pixel
|
||||
repeated −*n* times.
|
||||
|
||||
## Pixels
|
||||
|
||||
16 bit in **PlayStation order**: bits 0–4 red, 5–9 green, 10–14 blue, bit 15 = STP
|
||||
(semi-transparency). `0x0000` is fully transparent. Export to PNG uses alpha 0 = `0x0000`,
|
||||
alpha 128 = STP 0, alpha 255 = STP 1. The page flags (low 7 bits) select the blend class:
|
||||
1/6 opaque, 4/7 alpha blend, 5 another blend mode, 2/3 special converters (*likely* the
|
||||
PlayStation semi-transparency modes; details open).
|
||||
|
||||
## Texture entries
|
||||
|
||||
A face never has its own UVs: it names a **texture entry**, a rectangle on a page, and shows half
|
||||
of it ([Geometry](geometry.md#no-free-texture-coordinates)). Entry flag bit 0 = flat colour (the
|
||||
rectangle bytes are R, G, B instead) – the level builder appends such entries for plain colours.
|
||||
Other entry flag bits (blend mode, size class) are only partly understood.
|
||||
|
||||
Limits: at most **127 pages** per level (signed page byte) and 65535 entries (u16 face field).
|
||||
|
||||
## Changing textures
|
||||
|
||||
* **Replace pixels, keep the table.** Exporting pages to PNG and importing them back changes no
|
||||
index anywhere: `tools/text_export.py export level.wad dir` / `import level.wad dir new.wad`.
|
||||
* **Add pages and entries.** New pages and entries are appended; faces then use the new indices.
|
||||
The editor's texture manager does this, including colour reduction, painting onto faces and
|
||||
flip-book animations ([manual chapter 8](../manual/08-textures.md)).
|
||||
* **Same name, same picture.** Texture *indices* differ between base levels. The builders resolve
|
||||
texture names (`grass`, `stone`, …) by picture fingerprint on each base and copy missing
|
||||
village pictures over ([notes/levelbau.md](../../notes/levelbau.md)).
|
||||
|
||||

|
||||
@@ -0,0 +1,63 @@
|
||||
# WAD container
|
||||
|
||||
A level is one file `wads/t1l<world>m<chapter>.wad`. It is a flat list of chunks:
|
||||
|
||||
```
|
||||
u32 total length
|
||||
repeat: u32 tag (four ASCII letters stored reversed: "OFNI" in the file = INFO)
|
||||
u32 length
|
||||
length bytes of chunk data
|
||||
```
|
||||
|
||||
There are no checksums. When a chunk changes size, only the chunk length and the total length
|
||||
must be rewritten ([`format/Wad.java`](../../src/main/java/com/eliasmueller/llamamod/format/Wad.java),
|
||||
[`tools/wadtool.py`](../../tools/wadtool.py) – `wadtool.py list|extract|replace`).
|
||||
|
||||
## Chunks
|
||||
|
||||
In file order. "Loader" is the function in `groove.exe` that reads the chunk (dispatcher at
|
||||
0x558960).
|
||||
|
||||
| Chunk | Contents | Documented in |
|
||||
|---|---|---|
|
||||
| INFO, VERS | u32 1 each, ignored | – |
|
||||
| WFPC | u32 feature flags of the level; they switch optional blocks in MAP and STPC on and off – never change them | [Geometry](geometry.md#wfpc-flags) |
|
||||
| TEXT | texture pages, texture rectangles, texture animations | [Textures](textures.md) |
|
||||
| FONT | 256 × 4 u16 glyph rectangles | – |
|
||||
| SPRT | sprite list (u32, more with WFPC 0x100000) | – |
|
||||
| NAME | u32 + level name (not shown anywhere in the game) | – |
|
||||
| SMPC | sound samples of the level (`cvg ` blocks) | [Music and sound](audio.md) |
|
||||
| AMPC | ambient sound data (`ambi`/`VABp`) | [Music and sound](audio.md) |
|
||||
| SRPC | references into the speech file `music/<lang>.cvs` | [Music and sound](audio.md) |
|
||||
| **TRAK** | world geometry: the tile models (render triangles + collision polygons) | [Geometry](geometry.md) |
|
||||
| **STPC** | object models, skeletal animations and the **strat code** (object behaviour) | [Geometry](geometry.md#stpc-object-models-and-code), [Strats](../strats/README.md) |
|
||||
| **MAP** | the tile grid, vertex colours, visibility regions, objects, waypoints, environment zones | [Geometry](geometry.md), [Objects](objects.md) |
|
||||
| LGHT | lights (directional and point lights for object models) | [Objects](objects.md#lights) |
|
||||
| LNFO | u32 coins, u32 secrets: the totals shown on the tally screen | [TestLevel](testlevel.md#tally-totals) |
|
||||
| LGPC | dialogue texts in all languages + speech slots | – |
|
||||
| END | end marker | – |
|
||||
|
||||
Not every level has every chunk. The three chunks that matter for level building are **TRAK**,
|
||||
**STPC** and **MAP**; a new level takes everything else (textures, sounds, sky, fog, music) from an
|
||||
original **base level**.
|
||||
|
||||
## The base-level idea
|
||||
|
||||
An object type exists in a level only if its code is in that level's STPC. MAP refers to a type
|
||||
by its **byte offset into STPC** – there is no global type id. So every custom level starts as a
|
||||
copy of an original level (default: Village 1, `t1l1m001`) and replaces geometry and objects:
|
||||
|
||||
* Python [`tools/levelbuild.py`](../../tools/levelbuild.py) and Java
|
||||
[`levelbuild/LevelBuilder.java`](../../src/main/java/com/eliasmueller/llamamod/levelbuild/LevelBuilder.java)
|
||||
generate TRAK and MAP from a script and keep STPC, TEXT and sound of the base.
|
||||
* The level editor stores only the difference to the base level and rebuilds the WAD from the
|
||||
user's own CD ([notes/editor-format.md](../../notes/editor-format.md)).
|
||||
* Object types of *other* levels can be merged into the base's STPC together with their models,
|
||||
animations, textures and sounds – the editor's *All worlds* import and the arena use this
|
||||
([notes/editor-cross-import.md](../../notes/editor-cross-import.md)).
|
||||
|
||||
## Level memory
|
||||
|
||||
The game loads STPC, TEXT (compressed pages + 20 bytes per texture entry), TRAK, MAP, LGPC and the
|
||||
strat pool into one **16 MB** block (`FUN_0041eea0`); original levels use up to about 7 MB. See
|
||||
[Engine facts and limits](engine-limits.md).
|
||||
Reference in new issue
Block a user