Files
llamamod/notes/editor-playtest.md
T

130 lines
9.4 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.
# Level editor – play-test straight into the level (`editor.tools.playtest`, `mods.boottolevel`)
Before: Play installed the level and started the game at its main menu; the player had to
choose *Spiel starten* → slot 3 → Enter on the map. Now **Play (F5)** lands in the level a few
seconds after the game window opens, and **Escape** quits back to the editor.
![game booted into TestLevel chapter 2](img/editor/play_boot.jpg)
## Editor side (`PlaytestEditorPlugin`, `PlaytestRunner`, `FloorFinder`)
| Action | Key | |
|---|---|---|
| Play (replaces the built-in `level.play`) | F5 | build → install level mod → apply user mods + boot mod → save slot → start |
| Play from camera position | Shift+F5 (toolbar) | the llama starts on the ground in the middle of the view, facing the camera direction |
| Play boots straight into the level | Level menu (check) | off = old behaviour (main menu, map cursor) |
| Escape quits the play-test | Level menu (check) | boot mod option `quitOnEscape` |
* Run: `PlayTest.build` (built-in, background) → the project snapshot is written to the
**temporary** `<data>/levels/.playtest/<stem>.llamalevel` (mod id `playtest-<stem>`; with
"Keep play-tested level in LlamaMod" on: `<data>/levels/<stem>.llamalevel` as before) → the user's **active** mods (others on the same WAD and Test
World on chapter 1 turned off) + the level mod become `enabledMods`; `boot-to-level` is applied
**in addition** (not stored in `enabledMods`) with world/chapter of the project, slot
(Preferences, default 3), `logos=true`, `quitOnEscape`, `start` → save slot unlocked up to the
level → `GameLauncher.launch`.
* **Restore**: a daemon thread polls `GameLauncher.isRunning()` every 2 s; when the game has
ended it re-applies the user's **previous** active mods and enabled list (stored in the
session: `mods`, `enabled`, `bootOptions`), restores the boot mod's own options and deletes
`levels/.playtest/` plus `playtest-*` entries in `enabledMods`/`modOptions`
(`LevelProjects.clearPlaytest`). With "keep" the level stays enabled/active (old behaviour).
The session file `<data>/editor/playtest-session.json` lets the next editor start finish it;
without a session, the next editor start deletes leftover copies if no game runs
(`clearStale`). The non-boot Play (menu, map cursor) uses the same install/restore path now.
* **Level → Add to LlamaMod…**: writes the snapshot to `<data>/levels/` (`PlayTest.installTarget`:
the same project's file is reused, file names compared case-insensitively) and sets the mod's
`chapter` option to the project's TestLevel chapter.
* The launcher hides `playtest-*` mods unless enabled/active (then: "(play-test)" + blue badge
"temporary play-test", no Remove button).
* **Play from camera position**: ground point = depth-buffer hit at the viewport centre
(export space → game x, y, −z), heading = camera yaw (`atan2(f.x, −f.z)`, 0 = +z).
`FloorFinder` then looks for **collision**: highest floor polygon (|n.y| ≥ 0.5) at or below
the hit + 0.6 among scene tiles and brush-generated tiles, searching up to 0.75 cells around
(rings of 8); material 30 (death floor) is rejected. Needed because render geometry often has
no collision (village: the hill edge east of the start renders, but only a death floor at
y 2.25 lies below – the llama fell to a game over). Since 2026-11-07 `FloorFinder` is a thin layer
over the objects tools' `Ground` (`Ground.ofCells(scene, …)` built from the query cell(s) only + `cellBelow`: the cell's own tiles only, death
floors included so a death floor is still reported/used as last resort, no swap models or neighbours).
* `-Dllamamod.editor.noLaunch=true` skips the start (tests start the game with apptest).
## Exe mod `boot-to-level` (GAMEPLAY, `modifiesExecutable`)
Options: `world` 1–9, `chapter` 1–49, `slot` 1–3, `logos` (skip logo movies/title, same patch
as intro-skip – only applied if the bytes are still original, so both mods can be on),
`quitOnEscape`, `start` ("x, y, z[, yaw]" in cells; empty = the level's own start). Order 90
(after level mods). Registered through `META-INF/services/com.eliasmueller.llamamod.mods.Mod`
(ServiceLoader; `ModRegistry` was not edited).
### Engine facts used
| | |
|---|---|
| 0x5846E4 | game state: 1 ?, 2 main menu, 3 in game, 4 attract demo, 5 options?, 6 save slots, 12 world map, 0x11 quit |
| 0x578F28 | frame handler per state (returns 1 = state changed → main loop runs the enter handler) |
| 0x578F7C | enter handler per state; state 3 = FUN_0054ae20: intro movie, loading screen `T1%d%d.COR`, FUN_00419510(tribe, world, chapter, 0) loads the level |
| 0x579050 | per-state update/render (3 = FUN_0054b590, the game frame) |
| 0x584EB4/B8/BC/C0 | tribe, world, chapter, map of the level to load; 0x585588/0x584C24/0x584C20/0x58557C = map cursor |
| 0x584700 | next state for fade transitions; 0x584638 = sub state (7 = enter level) |
| FUN_0041c0e0 | reads `king0..2.ksv` into 0x585274 + slot·0x100 |
| FUN_0041cc70 | loads slot 0x584C28 into the game variables (new game in it if empty) |
| 0x546CC0, 0x546990([0x6D94B4]), 0x5464D0 | music/sound shutdown done before every state switch (copied from the demo switch at 0x549E5D) |
| 0x5FCF08 bit 0x8 | "pause" pressed this frame (Escape) |
| main loop (WinMain) | exits through its normal cleanup (FUN_00545030, DestroyWindow, CoUninitialize) when the state is 0x11 – the main menu's *Beenden* |
### Patches
* **Boot**: `jmp` at 0x549C50 (state-2 frame handler, `A1 2C 37 58 00` = `mov eax,[0x58372c]`).
The cave runs once (flag in an `allocateData` block): pushad, read slots, `[0x584C28]=slot−1`,
load slot, set tribe 1/world/chapter/map 0 (+ map cursor), `[0x5855B0]=0`, sub state 7, music
shutdown, state 3, popad, `mov eax,1; ret` → the main loop enters state 3 exactly like the
map's Enter would. Later main-menu frames (after quitting to the menu) run the original code.
* **Escape quits** (`quitOnEscape`): `jmp` at 0x549F40 (state-3 frame handler, same 5 bytes):
`test byte [0x5FCF08],8; jz original; mov [0x5846E4],0x11; mov eax,1; ret`.
* **Logos**: 0x41D56E `68 28 25` → `53 EB 62` (see mod-introskip.md).
* **Start override** (data, not exe): the level's WAD (as written by earlier mods) is imported,
the Llama moved/turned and rebuilt (`StartOverride`; an unchanged rebuild is byte-identical).
Its waypoint chain moves and turns along only if it is an editor/levelbuild camera chain
(all nodes but the first on the llama); original levels use the llama's chain as a
level-wide list, which stays.
## Verified in game (apptest, display :9, Wine 6, 2026-11-06)
| Test | Result |
|---|---|
| `boot-to-level` world 1 chapter 2, original data | Village 2 fly-by 5 s after the window, gameplay at ~20 s, walking works |
| Editor Play on `examples/testwelt.llamalevel` (TestLevel 2) | game shows the Testwelt plaza with HUD 4 s after the window opens (`play_boot.jpg`) |
| Editor Play from camera position, Village 1, start 2.3 cells east | llama stands at the new spot (`play_from_here.jpg`); the original village fly-by is skipped (its cut-scene camera is tied to the original start) |
| Same with the view on the hill edge (no collision) | before FloorFinder: llama fell to a game over; now the editor finds the nearest floor or refuses |
| Escape in gameplay (village and TestLevel) | groove.exe gone after 1.3 s, no crash, no Wine error dialog, the next start works |
| Restore after the game | active mods back to the user's selection, exe has no mod patches |
Images: `img/editor/play_boot.jpg`, `play_from_here.jpg`, `play_editor.jpg`,
`play_windows_editor.jpg`.
## Windows package under Wine
`./gradlew packageWindows` → `LlamaMod-windows-x64.zip` (45 MB). Unpacked into an own win64
prefix (Wine 6.0.3), started as `JDK_JAVA_OPTIONS="-XX:ActiveProcessorCount=4
-Dllamamod.data=Z:\…\data" wine runtime/bin/javaw.exe -jar LlamaMod.exe --editor t1l1m001`
(the launch4j exe itself hangs in its `javaw -version` check under Wine 6, see llamamod-app.md):
the editor window opens (within about two minutes on the first start, software GL) and renders the village with all phase-3 plugins (Play from
camera position, Textures tab, paint tool) – `img/editor/play_windows_editor.jpg`. The game was
not started from the Windows build (the private Wine prefix has no game install); real Windows
hardware is untested.
## Tests
`PlaytestLifecycleTest` (temporary copy listed as `playtest-…` → restore removes it and puts
the enabled list back; keep → stays; stale copies cleared; Remove → backups, refused while the
game runs; regression: `My-level`/`my-level` get distinct ids), `BootToLevelModTest` (hooks at both frame handlers, logo patch, exe re-parses; Escape hook only
when asked; start parsing; original village: only the llama moves, file size unchanged;
editor-style chain moves and turns with the llama), `RestoreCheck` (manual), `EdTour` +
`PlayTourSteps` (scripted editor runs on an X display: camera placement, play-from-here).
## Temporary play-test copies – verified in game (apptest lvlrm, game on :5, editor on :31, 2026-11-07)
EdTour `run:level.play` on `/tmp/…/mytest.llamalevel` (real launch, private pulse): during the
game `levels/.playtest/mytest.llamalevel`, enabled `[playtest-mytest]`, active
`[playtest-mytest, boot-to-level]`, Testwelt plaza in game (`img/mods/levels_playtest_temporary.jpg`);
Escape → game gone → `.playtest` deleted, enabled `[]` and active `[level-old-village-test]` as
before the test, session file removed. Launcher badge: `img/mods/levels_playtest_badge.jpg`.