223 lines
18 KiB
Markdown
223 lines
18 KiB
Markdown
# Debug view: collision & invisible objects in game (`tools/debugviz.py`)
|
||
|
||
As of: 2026-11-06. Shows, in **all 34 level WADs**, the collision geometry (including invisible
|
||
walls/floors, death planes) and invisible logic objects (cameras, triggers, exits,
|
||
checkpoints, force/box zones, environment zones …) directly in the game – as a coloured wireframe,
|
||
invisible surfaces additionally filled semi-transparently. Game mechanics stay unchanged.
|
||
|
||
## Usage
|
||
|
||
```
|
||
lama-mod debug an # enable debug WADs (rebuilds the overlay, thereby also activating other mods)
|
||
lama-mod debug aus # back to the original levels (the testlevel mod stays as it was)
|
||
lama-mod debug # status
|
||
```
|
||
Takes effect the next time a level is loaded (best to restart the game). Do not toggle
|
||
while a level is loading. Mod folder: `~/.wine-lama/mods/debug/wads/*.wad` (same names as
|
||
on the CD, e.g. `t1l1m001.wad`); the file `~/.wine-lama/mods/debug/.aus` = disabled.
|
||
Order in the overlay: alphabetical, later ones win → `testlevel` (T1L9*) always stays,
|
||
debug WADs only replace the original levels. `lama-mod an|aus|status` work as
|
||
before (`an` skips mod folders containing `.aus`). Backup of the old script:
|
||
`deb-build/lama-mod.bak-vor-debug`.
|
||
|
||
Rebuild (e.g. after changing colours):
|
||
```
|
||
python3 tools/debugviz.py --all ~/.wine-lama/mods/debug/wads --report /tmp/dv.json
|
||
python3 tools/debugviz.py IN.wad OUT.wad [--no-fill] [--no-markers] [--no-coll] [--opaque]
|
||
[--width 0.007] [--offset 0.012]
|
||
```
|
||
Run time ≈ 10–40 s per level.
|
||
|
||
## Colour legend
|
||
|
||
All colours are RGB (0–255). **Lines** = wireframe (opaque), **fill** = semi-transparent
|
||
(alpha 96/255, engine blend mode 1).
|
||
|
||
### Collision surfaces (frame around every collision polygon of the world tiles)
|
||
Edge colour by orientation, as long as the material is 0 (normal):
|
||
|
||
| Colour | RGB | Meaning |
|
||
|---|---|---|
|
||
| **Green** (lines) | 40,255,40 | walkable floor (normal pointing steeply up, ny ≥ 0.7) |
|
||
| **Yellow-green** (lines) | 200,255,0 | slope (0.2 ≤ ny < 0.7) |
|
||
| **Blue** (lines) | 0,150,255 | wall (−0.2 < ny < 0.2) |
|
||
| **Orange** (lines) | 255,130,0 | ceiling / overhang (ny ≤ −0.2) |
|
||
| **Red, filled semi-transparently** | 255,0,0 | **invisible collision**: no render surface within ±0.5 units along the normal (= invisible wall/floor/ceiling). The edge colour still shows orientation or material. |
|
||
|
||
Collision with a special material (byte 1 of the collision polygon ≠ 0): edges in the material colour,
|
||
for invisible polygons the fill is also in the material colour (instead of red):
|
||
|
||
| Material | Colour | RGB | Meaning |
|
||
|---|---|---|---|
|
||
| 30 (0x1e) | **Magenta**, lines only (no fill) | 255,0,255 | **no floor / fall and death plane**: the player falls through (FUN_004036d0 sets floor = none). Usually model 0 at y ≈ −2.75 in every grid cell below the islands → magenta grid = fall/death zone. |
|
||
| 16 | **Violet**, always filled | 190,60,255 | exit floor: LevelDoor only triggers when the player stands on material 16 in the door cell |
|
||
| 17 (0x11) | **Yellow** | 255,255,0 | selective collision: only affects objects with runtime flag 0x8000000 (strat+0xe8) |
|
||
| 18 (0x12) | **Light orange/peach** | 255,200,120 | selective collision: does NOT affect objects with flag 0x8000000 |
|
||
| 1 | Turquoise | 0,200,200 | special material, meaning unknown (evaluated by strat scripts) |
|
||
| 2 | White | 255,255,255 | ditto |
|
||
| 3 | Pink (rose) | 255,120,170 | ditto (common in world 5, mountain) |
|
||
| 5 | Mint | 120,255,200 | ditto (river, roller coaster) |
|
||
| 6 | Lavender | 160,120,255 | ditto |
|
||
| 7 | Light orange | 255,170,80 | ditto |
|
||
| 8 | Dark green | 0,130,60 | ditto |
|
||
| 9 | Light blue | 150,200,255 | ditto |
|
||
| 10 | Olive | 150,150,0 | ditto |
|
||
| 11 | Brown | 150,90,40 | ditto |
|
||
| 13 | Salmon | 255,130,110 | ditto |
|
||
| 15 | Light yellow | 255,255,150 | ditto |
|
||
| 20 | Petrol | 0,120,140 | ditto |
|
||
| 26 | Dark violet | 110,40,160 | ditto |
|
||
| 27 | Silver | 190,190,190 | ditto (chase t1l2m003) |
|
||
| 28 | Hot pink | 255,0,140 | ditto |
|
||
| 33 | Dark red | 150,0,0 | ditto |
|
||
| 255 | Almost black | 20,20,20 | ditto |
|
||
| other | Light grey | 230,230,230 | (does not occur in the originals) |
|
||
|
||
### Object markers (invisible strats, MAP strat entries)
|
||
Marker cube (10 cm) on a thin pole 0.35 above the object origin; short horizontal
|
||
line = view direction (cameras, checkpoints).
|
||
|
||
| Colour | RGB | Category | Types |
|
||
|---|---|---|---|
|
||
| **White**, arrow flat on the ground | 255,255,255 | player start (direction) | Llama, KuscoAndPachaTogetherOnATree (river), RollerCoaster, LlamaChase |
|
||
| **Violet** + wire cube 1×1×1 around the grid cell | 190,60,255 | exit (trigger cell) | LevelDoor, VillageEndDoor, EndGameDoor, TeleportDoor |
|
||
| **Green-turquoise** | 0,255,140 | checkpoint | CheckPointPost, CheckPointBox |
|
||
| **Cyan** | 0,255,255 | camera | Camera* (CameraSide, CameraLift, CameraForceInsideBox …), CutSceneCamera*, RiverWaterfallCamera |
|
||
| **Yellow** | 255,255,0 | trigger | CutSceneTrigger*, *Trigger*, Say* (voice lines), ChangeToBunny/Turtle, RiverStartSign |
|
||
| **Orange** | 255,90,0 | force/special zones | Force*, FallOffNoHurt, ObjectDontDropInBox, RiverCurrentBox, RiverRebound, WaterReboundPoints*, SuckingWPs, ObjectSuckPoints, RiverTrackChange, TrackSwitch, RiverWhirlpool, BackgroundRotationChanger |
|
||
| **Gold** | 255,200,0 | secret zone | Secret |
|
||
| **Grey** | 170,170,170 | other invisible control objects | CheatTeleport, Wompy*, Spawner, Helper, coin lines/circles, Rain, DrippingWater, WaterfallMist, MusicBox, FlockOfBirds, Bats, Scorpions*, AppearingPlatforms* … |
|
||
| **Dark grey**, small dot (6 cm) | 90,90,90 | all remaining objects (origin) | coins, enemies, doors … – so that really nothing is missing |
|
||
| **Pink** wire cube per grid cell | 255,100,180 | environment/music/fog zone | MAP block 4 (90-byte entries, cell x/y/z) |
|
||
|
||
**Zone boxes:** if a marker object has exactly 2 waypoints and the object lies in the rectangle
|
||
between them, it is a box area (Secret, CameraSide/Lift, ObjectDontDropInBox,
|
||
FallOffNoHurt, RiverCurrentBox …): wire cuboid in the category colour from min(y) to max(y)+1.2
|
||
(the height is estimated – the code probably only checks x/z), for areas ≤ 64 units² with a
|
||
semi-transparent floor (alpha 70).
|
||
**Waypoint chains** of other marker objects: small cubes (5 cm) in a darkened
|
||
category colour (60 %), for force zones with connecting lines (e.g. WaterReboundPoints paths,
|
||
CutSceneTrigger camera moves only as points).
|
||
|
||
## How it is built (decisions)
|
||
|
||
* **Collision overlay appended to the TRAK models** (not as separate tiles): per
|
||
collision polygon a frame of 2·n vertices (outer corners + corners offset inwards by the line
|
||
width, computed from the edge planes), 2·n triangles; the fill reuses the inner
|
||
corners. Everything offset by 0.012 along the surface normal (no z-fighting). This way
|
||
tile transform, zones/visibility and tile swaps (alt models, WFPC 0x10000) are
|
||
automatically correct. Collision is converted back from game space (geomlib.coll_decode/
|
||
coll_polygon, z mirrored).
|
||
* Face flags of the overlay triangles: 0x800 | 0x400 (double-sided) | 0x4000 (ignore vertex
|
||
colours) | 0x8 at every texture change and at the latest every 250 faces (render batch).
|
||
* **Colours** = appended flat-colour textures (TEXT table, page −1): opaque flags 0x105,
|
||
semi-transparent **0x1105** (bits 12–14 = 1 → table 0x573eb8: blend mode 1 =
|
||
SRCALPHA/INVSRCALPHA, alpha from the 4th byte (v1) of the entry; checked in game).
|
||
Semi-transparent triangles go through the sort buffer (FUN_0041ed90, 512 KB/frame ≈ 5000
|
||
triangles; when it is full they are simply dropped – no crash). That is why fills exist only
|
||
for invisible collision, material 16 and small zone floors.
|
||
* **Vertex colour block** in MAP: extended per tile (and per colour set/frame, also for alt models) by
|
||
nV(overlay) entries 0x00808080 (byte 3 = 0, does not change the frame count).
|
||
* **Invisibility detection**: render triangles of all tiles in world coordinates, bucketed by
|
||
cell; for the centre + 4 points (70 % towards the corners) of every collision polygon a
|
||
ray along the normal (±0.5) is tested against triangles with similar orientation (|cos| > 0.4);
|
||
< 60 % hits = invisible. Majority vote per model over up to 6 instances.
|
||
Strat models (STPC: doors, platforms, characters) do NOT count as render surfaces – collision
|
||
behind moving objects may therefore appear red.
|
||
(The first version with 0.06/0.15 distance marked entire house walls and cliff walls red, because the
|
||
collision hull lies 0.2–0.4 in front of the visible wall – too much noise.)
|
||
* **Markers** as new tiles without collision, one model per grid cell (lines split into pieces ≤ 1
|
||
and placed in the cell of their midpoint) → automatically get the zone of the
|
||
cell. New tiles are appended at the end (indices of the original tiles stay).
|
||
* **Game mechanics unchanged**: no collision polygons added/changed, strats,
|
||
waypoints, parameter pool, zones and the rest of MAP byte-identical (checked after writing:
|
||
strats/nodes/rest/original tiles identical).
|
||
* Size: WADs grow by 0.1–4 MB (max. 8.9 MB, t1l6m003). Level memory (16 MB arena,
|
||
FUN_0041ef00) holds TRAK+STPC+MAP – enough for all (load test).
|
||
|
||
## Limits / known issues
|
||
* Collision of the **object models** (STPC: moving platforms, doors, crates …) is not
|
||
shown – they move; only their origin as a dark grey dot.
|
||
* Zone heights of the box areas are estimated; whether the engine checks y is open.
|
||
* Meaning of most special materials unknown (only colour + count, see table below);
|
||
material 30 = no floor, 16 = exit, 17/18 = selective are certain from the code.
|
||
* Environment zones (MAP block 4): cell certain, meaning (fog/music/camera) only moderately certain;
|
||
the default entry (position 1,1,1) is not drawn.
|
||
* If the camera is close to a marker/line, the engine's near-plane clip sometimes cuts the
|
||
triangles into large shreds (only briefly visible).
|
||
* Performance: no noticeable drop under Wine/llvmpipe (Xvfb); uncritical on a real GPU
|
||
(≤ 2000 extra vertices per tile model, on average ~1–2× as many triangles as the original).
|
||
* Test level T1L9 (test world) has no debug version (the testlevel mod takes precedence).
|
||
|
||
## Statistics per level (build of 2026-11-06)
|
||
|
||
Marker categories are the internal keys of `debugviz.py`: ausgang = exit, geheimnis = secret,
|
||
kamera = camera, kraft = force zone, objekt = other object, sonst = other control object,
|
||
umgebung = environment zone.
|
||
|
||
| Level | World | WAD old→new (KB) | Coll. polygons | of which invisible | Special materials (polygon count) | Markers |
|
||
|---|---|---|---|---|---|---|
|
||
| t1l1m001 | Village | 6158→8235 | 3474 | 215 | 2:138, 6:4, 8:35, 9:13, 16:8, 17:5, 18:84, 28:19, 30:1 | ausgang 1, checkpoint 6, geheimnis 2, kamera 6, kraft 6, objekt 95, sonst 4, start 1, trigger 1, umgebung 12 |
|
||
| t1l1m002 | Village | 5471→7573 | 3909 | 130 | 6:4, 8:14, 16:9, 18:65, 28:23, 30:1 | ausgang 1, checkpoint 5, geheimnis 1, kamera 8, kraft 5, objekt 89, sonst 9, start 1, trigger 1, umgebung 15 |
|
||
| t1l1m003 | Village | 5891→8348 | 3893 | 79 | 2:15, 6:4, 8:8, 16:5, 17:3, 18:458, 20:6, 28:23, 30:1 | ausgang 1, kamera 3, kraft 6, objekt 45, sonst 3, start 1, trigger 2, umgebung 4 |
|
||
| t1l1m004 | Village | 5866→7006 | 2169 | 60 | 2:15, 8:8, 16:16, 17:3, 18:90, 30:1 | ausgang 2, geheimnis 1, kamera 2, kraft 1, objekt 31, sonst 2, start 1, trigger 1, umgebung 5 |
|
||
| t1l2m001 | Jungle night | 5094→7491 | 3933 | 460 | 1:26, 2:111, 6:156, 9:34, 16:22, 17:24, 18:284, 20:80, 28:28, 30:138, 33:1 | ausgang 1, checkpoint 2, geheimnis 1, kamera 3, kraft 6, objekt 39, sonst 8, start 1, trigger 1, umgebung 8 |
|
||
| t1l2m002 | Jungle night | 5329→7561 | 3715 | 389 | 1:216, 2:57, 6:154, 9:24, 16:34, 17:23, 18:319, 28:28, 30:50 | ausgang 1, checkpoint 4, geheimnis 1, kamera 3, kraft 7, objekt 105, sonst 2, start 1, trigger 1, umgebung 18 |
|
||
| t1l2m003 | Jungle night (chase) | 2837→5142 | 4844 | 630 | 5:3, 9:6, 10:6, 11:37, 16:26, 27:206, 30:46 | kamera 2, kraft 1, objekt 33, sonst 4, start 1, trigger 1 |
|
||
| t1l3m001 | River | 4099→8220 | 8371 | 1000 | 5:26, 16:8, 18:23 | ausgang 2, geheimnis 1, kamera 13, kraft 4, objekt 140, sonst 13, start 1, trigger 5 |
|
||
| t1l3m002 | River | 3799→8249 | 8824 | 1097 | 5:40, 16:5, 18:23, 30:1 | ausgang 2, geheimnis 1, kamera 19, kraft 44, objekt 115, sonst 39, start 1, trigger 6 |
|
||
| t1l3m003 | River | 4497→8537 | 8387 | 868 | 5:56, 16:5 | ausgang 4, geheimnis 1, kamera 15, kraft 7, objekt 52, sonst 15, start 1, trigger 3 |
|
||
| t1l3m004 | River | 2674→3754 | 2201 | 228 | 5:18, 16:5 | ausgang 2, kamera 5, kraft 4, objekt 31, sonst 7, start 1, trigger 2 |
|
||
| t1l4m001 | Jungle day | 5469→7380 | 3214 | 230 | 1:148, 6:105, 10:33, 16:18, 17:33, 18:158, 20:25, 28:24, 30:10 | ausgang 1, checkpoint 9, geheimnis 1, kamera 5, kraft 12, objekt 120, sonst 3, start 1, trigger 1, umgebung 10 |
|
||
| t1l4m002 | Jungle day | 5807→8045 | 3820 | 157 | 1:186, 6:123, 10:80, 16:33, 17:55, 18:309, 20:6, 28:98, 30:22 | ausgang 1, checkpoint 7, geheimnis 2, kamera 1, kraft 14, objekt 135, sonst 3, start 1, trigger 1, umgebung 14 |
|
||
| t1l4m003 | Jungle day | 6320→8886 | 4805 | 135 | 1:174, 2:52, 6:10, 10:33, 16:36, 17:110, 18:890, 20:186, 28:27, 30:33, 255:8 | ausgang 1, checkpoint 3, geheimnis 2, kamera 6, kraft 4, objekt 99, sonst 3, start 1, trigger 1, umgebung 16 |
|
||
| t1l5m001 | Mountain | 6090→8613 | 4419 | 188 | 3:356, 8:96, 16:24, 17:35, 18:539, 20:66, 28:6, 30:87 | ausgang 1, checkpoint 3, geheimnis 2, kamera 5, kraft 2, objekt 83, sonst 7, start 1, trigger 1, umgebung 15 |
|
||
| t1l5m002 | Mountain | 5949→8558 | 5242 | 220 | 2:20, 3:396, 8:23, 9:15, 16:19, 17:33, 18:342, 20:57, 30:120 | ausgang 1, checkpoint 3, geheimnis 4, kamera 10, kraft 3, objekt 67, sonst 6, start 1, trigger 1, umgebung 7 |
|
||
| t1l5m003 | Mountain | 4989→6796 | 3399 | 98 | 3:232, 8:223, 16:17, 18:20, 20:6, 30:78 | ausgang 1, checkpoint 5, geheimnis 1, kamera 4, kraft 1, objekt 71, sonst 6, start 1, trigger 1, umgebung 2 |
|
||
| t1l6m001 | City | 6210→8227 | 2857 | 169 | 6:29, 16:18, 17:140, 18:110, 30:38 | ausgang 1, checkpoint 4, geheimnis 1, kamera 5, kraft 3, objekt 83, sonst 3, start 1, trigger 5, umgebung 20 |
|
||
| t1l6m002 | City | 5155→7060 | 1771 | 106 | 6:56, 16:9, 17:20, 18:39, 30:17 | ausgang 1, checkpoint 10, geheimnis 1, kamera 7, kraft 9, objekt 127, sonst 16, start 1, trigger 5, umgebung 46 |
|
||
| t1l6m003 | City | 6693→8924 | 2462 | 148 | 1:39, 16:13, 17:80, 18:154, 28:3, 30:10 | ausgang 1, checkpoint 11, geheimnis 6, kamera 11, kraft 6, objekt 107, sonst 15, start 1, trigger 3, umgebung 59 |
|
||
| t1l7m001 | Catacombs (roller coaster) | 3054→6705 | 4043 | 20 | 5:54, 11:18, 13:18, 16:9, 30:18 | kamera 1, objekt 2, sonst 36, start 1, trigger 1, umgebung 14 |
|
||
| t1l7m002 | Catacombs | 5280→7722 | 1795 | 111 | 2:1, 11:3, 16:44, 30:93 | ausgang 3, checkpoint 12, geheimnis 5, kamera 6, kraft 4, objekt 186, sonst 12, start 1, trigger 1, umgebung 23 |
|
||
| t1l7m003 | Catacombs (roller coaster) | 2496→5140 | 1999 | 11 | 5:54, 11:90, 13:36, 15:18, 16:9, 30:9 | kamera 1, objekt 11, sonst 41, start 1, trigger 1 |
|
||
| t1l7m004 | Catacombs | 5299→6978 | 1892 | 61 | 2:7, 16:32, 30:70 | ausgang 1, checkpoint 3, geheimnis 3, kamera 4, kraft 7, objekt 93, sonst 10, start 1, trigger 2, umgebung 15 |
|
||
| t1l7m005 | Catacombs (roller coaster) | 2390→4782 | 1885 | 9 | 5:54, 11:36, 13:36, 16:9, 30:9 | kamera 1, objekt 1, sonst 51, start 1, trigger 1, umgebung 9 |
|
||
| t1l8m001 | Lab | 6632→8824 | 3396 | 185 | 1:40, 8:9, 16:16, 17:8, 18:181, 30:56 | ausgang 1, checkpoint 2, geheimnis 2, kamera 8, kraft 2, objekt 99, sonst 3, start 1, trigger 2, umgebung 19 |
|
||
| t1l8m002 | Lab | 5427→7112 | 2204 | 76 | 16:1, 17:44, 18:496, 30:42 | ausgang 1, geheimnis 1, kamera 5, kraft 2, objekt 74, sonst 2, start 1, trigger 3, umgebung 1 |
|
||
| t1l8m003 | Lab | 4656→5546 | 1398 | 34 | 16:9, 17:10, 18:196, 30:21 | ausgang 1, checkpoint 2, geheimnis 1, kamera 2, kraft 2, objekt 36, sonst 2, start 1, trigger 3, umgebung 6 |
|
||
| t1l8m004 | Lab | 5817→8407 | 2993 | 124 | 9:10, 16:66, 17:1, 30:57 | ausgang 1, checkpoint 7, geheimnis 3, kamera 12, kraft 6, objekt 164, sonst 3, start 1, trigger 7, umgebung 46 |
|
||
| t1l8m005 | Lab | 5375→8660 | 4041 | 179 | 7:10, 16:78, 17:1, 30:82 | checkpoint 7, geheimnis 1, kamera 14, kraft 5, objekt 113, sonst 15, start 1, trigger 2, umgebung 47 |
|
||
| t1l8m006 | Lab | 5423→5948 | 738 | 44 | 2:13, 8:24, 16:3, 17:5, 18:27, 26:4 | ausgang 1, objekt 15, start 1, trigger 17 |
|
||
| t6l6m001 | Special (options/map) | 1331→1482 | 333 | 0 | | kamera 1, objekt 3 |
|
||
| t6l6m002 | Special (options/map) | 3605→3640 | 64 | 0 | | kamera 1, objekt 2 |
|
||
|
||
(Collision polygons counted per TRAK model, not per tile instance.)
|
||
|
||
## Tests (2026-11-06)
|
||
|
||
**Offline (all 33 debug WADs, `$JOB/tmp/dv/offcheck.py`):** TRAK parses and rebuilds
|
||
byte-identically; ≤ 8000 vertices per model (max. 2012, t1l4m003); render batches ≤ 256 faces; all
|
||
texture indices < table size; vertex colour block per tile = nV(model) (all frames);
|
||
MAP fully parsed with `mapchunk` (0 bytes left over) and rebuilt byte-identically; strats,
|
||
waypoints, parameter pool, environment zones, zone table/grid, alt block and original tiles
|
||
unchanged; original models (vertices, faces, **collision**) unchanged byte for byte; new
|
||
marker models without collision; all other chunks (STPC, sound, LGPC …) identical;
|
||
tiles within the grid. TRAK+STPC+MAP+LGPC ≤ 7.6 MB (16 MB level arena).
|
||
|
||
**Loaded in game** (lamatest, via slot world 9 as T1L9M001.wad, ~40–90 s played/rotated,
|
||
no crash, no hang, no errors in the Wine log): t1l1m001 (several times, visual check),
|
||
t1l1m003, t1l1m004, t1l2m001, t1l2m002, t1l3m001–004 (the river also works normally in slot 9:
|
||
the boat moves), t1l4m001–003, t1l5m001–003, t1l6m001–003 = 19 levels.
|
||
The build tested was the one right before the final one: identical geometry, only the colour values
|
||
of the special materials were switched afterwards to fixed colours per material (+8–16 bytes
|
||
of texture table). The preview images also come from that build – special material colours
|
||
in them (e.g. turquoise fill in the mountain image) do not yet fully match the legend above.
|
||
**Only checked offline:** t1l1m002 and t1l2m003 (the load attempt previously failed at lamatest's
|
||
menu detection, not at the level), t1l7m001–005 (catacombs/roller coaster),
|
||
t1l8m001–006 (lab), t6l6m001/002. A test via the real save game slot was not done.
|
||
|
||
lamatest change on the side: save game selection with more attempts and 0.3 s long
|
||
key presses (under CPU load short presses got lost).
|
||
|
||
Images: `notes/img/debug/00_montage.jpg` (6 worlds), `01_…06_*.jpg` (1280×720).
|