Files
llamamod/notes/editor-playtest.md
T

9.1 KiB
Raw Blame History

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

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).
  • -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.