117 lines
6.9 KiB
Markdown
117 lines
6.9 KiB
Markdown
# 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: 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.
|
||
|
||

|
||

|
||

|
||
|
||
## 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.
|