Files
llamamod/notes/mod-testworld.md
T

117 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.