Files
llamamod/notes/mod-testworld.md
T

6.9 KiB
Raw Blame History

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

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 Condor, guards and townsfolk lab and end-game figures

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.