# Test World (`testworld`, LEVELS) Package `mods/testworld`: `TestWorldMod` (writes `wads/T1L9M001.wad` + loading picture `T191.COR`), `TestWorld` (the level script), `Gallery` (all-worlds model gallery), `Layout` (row packing), `JumpLab` (separate jump-measuring level, not used by the mod). No options in the launcher; the script options below are for tests and tools (`ViewerDemo testworld`, `TestWorld.buildWad(…, opts)`). | Script option | Default | Meaning | |---|---|---| | `gallery` | `all` | `none`, or comma-separated original type names (`TipoVillage,Spider`) | | `start` | `plaza` | `plaza`, `rowN` (middle of the aisle in front of gallery row N), `pathN` (its west end), `parkour`, `pk1`..`pk5`, `goal`, `goal2` | | `door` | `1,9,1` | exit parameters (tribe, world, chapter of the next level) | ## Layout (2026-11-07 rework) Island (x 11..43.5, z 9.5..≈34) + strip under the parkour end. Start plaza x 15..21, z 12..19; the parkour along z = 15.5 to the goal platform with the exit is unchanged. The animal enclosure and viewing podium are gone. South of the plaza the **gallery**: rows along +x (x 16.6..43), one block of rows per world (1..8, then the front end), figures before objects, ordered by catalogue category (characters, player models, enemies, animals, other figures; collectibles, checkpoints, exits, mechanisms …). Every item stands on a 0.08 high stone pedestal sized to its model (min 0.3 × 0.35, gap 0.15); in front of every row a 0.75 wide sand aisle. Row posts at x 15.05 in the world colour; the first row of a world carries one small block per world number (front end: white, none). The island grows southwards with the gallery (12 rows, z 20.5..≈33). ![gallery from the editor viewer](img/mods/testworld_top.jpg) ## Gallery: every model of the game, without AI `Gallery.build(wads)` scans the level WADs (t1l1m001…t1l8m006, t6l6m001/2) in order. For each strat type of a level's MAP (first level that places it wins, names deduplicated): 1. model = the first `address model; setModel` of the type's code (`StratModels.modelOf`, follows spawn stubs); otherwise the first one in any unit of its import closure (types that show their model through sub-strats: RisingColumns, FlockOfBirds, spawners …) – not for cameras, triggers and cut scenes. Types with their own model are handled first, so they win step 3. 2. skinned models also get an animation set: the first one the type's code addresses that has the model's bone count (bone matrices, no vertex deltas), else any such set of the level. 3. "look" = SHA-256 of the model bytes with every face texture replaced by its pixels, plus the animation bytes; a type that looks exactly like an earlier one is dropped (TipoLab = TipoCatacombs …). 4. A **display strat** is compiled with `StratCompiler` and appended as its own unit to the *source* level's STPC: ``` strat GxTipoVillage { setModel(model:17) animPlay(anim:60) // skinned models only loop { if (!self.animPlaying) { animPlay(anim:60) } yield } } ``` No collision, physics, waypoints, events, texts or sounds – it cannot move, attack, talk, trigger anything or delete itself. Type fields in MAP: 1 variable, 0 slots, type flags 0, no parameters, no waypoints (nothing for the engine's "no waypoints → remove" checks to find). 5. `mergeInto(base)` imports all display entries with `StpcMerge.merge` exactly like the editor's cross-world import: models, animation sets and textures come along (identical ones shared), the tiny units are appended, the base's own code is relocated (MAP strat codes remapped), TEXT gets the new pages/entries. One unplaced prototype record per display type is added to the base MAP so `LevelBuilder` knows the types (`l.strat("GxTipoVillage", …)`); the builder replaces all records. Result (German CD): **253 entries**, merge +5.95 MB (227 models, 96 animation sets, 93 texture pages → 111 of 127, 3126 texture entries, 0 sounds/texts); STPC 10.1 MB, WAD 14.6 MB. Scan + merge ≈ 10 s (mod apply). | World | figures | objects | total | |---|---|---|---| | 1 Village | 23 | 27 | 50 | | 2 Jungle | 11 | 7 | 18 | | 3 River | 30 | 6 | 36 | | 4 Jungle day | 13 | 9 | 22 | | 5 Mountain | 7 | 11 | 18 | | 6 City | 13 | 17 | 30 | | 7 Catacombs | 7 | 19 | 26 | | 8 Lab / end | 30 | 20 | 50 | | front end | 3 | 0 | 3 | Not shown (81 of 334 type names): 52 without a model of their own (cameras, cut-scene/dialogue triggers, force/current boxes, sound/particle effects, LevelDoor (its door is level geometry), Secret, CheatTeleport, AppearingPlatforms*, MouthPlatform …), 28 that look exactly like an earlier entry, 1 skinned model without a usable animation (EndGameGuardsBottle). `TestWorld.notShown()` lists them with the reason. ## Engine facts found on the way * **Strats are only drawn near the player.** Objects farther than ≈12.5 units from the llama are not rendered (they still exist) – walking along the gallery reveals the rows; a free camera (mod `freecam`) far away from the llama shows empty pedestals. * Strat heap: `FUN_0041ae20` allocates in u16-sized word blocks from a heap initialised with `(short)0x800000 − 3` = 65533 words (≈256 KB) for the strat pool (0x154 bytes × (poolExtra+30)), collision blocks and per-strat variables – plenty for the gallery (≈280 strats). ## Verified in game (apptest :26, 2026-11-07, host load ≈30) Mods `testworld` (+ `freecam` with `freeze=false`, noclip F9 for the walk): level loads (no crash), all rows walked twice (254- and final 253-entry build), figures stand still and play their idle animation in place (screenshot diff: only limbs change), no dialogue, cut scene or attack, the llama walks through them. The game stayed responsive (no freeze); frame rate not measurable at that host load. Over the south edge the llama falls into the sea and respawns at the plaza checkpoint as before. ![village and jungle figures](img/mods/testworld_village.jpg) ![Condor, guards and townsfolk](img/mods/testworld_condor.jpg) ![lab and end-game figures](img/mods/testworld_river.jpg) ## Tests * `GalleryTest`: every entry is placed once, has a non-empty model (skinned = figure, posed with an animation), its unit only uses `debugName address setModel animPlay pushProp not jz jmpYield end`, type flags 0 / no parameters / no waypoints, merged code decodes without errors; rows do not overlap; `gallery=none` imports nothing, `gallery=a,b,x` places a and b and reports x. * `TestWorldGoldenTest`: SHA-256 of the test world (default and `start=parkour`) pins the current output (no Python counterpart any more), 253 gallery objects; jump lab and loading picture still compared with the Python tools. * `TestWorldExampleTest` / `examples/testwelt.llamalevel` keep the **original** test world (enclosure, base-level gallery, `TestWorldBrushes` = port of levels/testwelt.py): the brush project's build is compared with the level builder's WAD of the same script instead of the mod's output.