# 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). ![The test world from above in the editor: every square is one grid cell](../img/testworld-top.jpg) ## 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 | ![Debug view of Village 3: collision frames, invisible walls in red, objects as markers](../img/debug-view.jpg) ## 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).