tool paths configurable by environment (LLAMAMOD_CD, LAMA_WINEPREFIX, LAMA_MOD, APPTEST_ROOT, APPTEST_ISO; tools/lamapaths.py), music tracks without a file on the cd marked in the editor and the tutorial uses kronk, levelbuild.py accepts the english texture names

This commit is contained in:
MineTec committed 2026-11-07 16:34:17 +01:00
1 parent a0d961357e
commit 6d449257a7
21 files changed
+153 -44

No files matched your search

+7 -4
View File
@@ -7,7 +7,7 @@ daemon (null sink only) and Wine prefix, so several agents can test in parallel:
<root>/<NAME>/data LLAMAMOD_DATA (config.json, original/ (hard links), game/, cd-active/, wine/)
<root>/<NAME>/harness state.json, pulse/ (socket, rt, home), logs, xvfb.log
root = $APPTEST_ROOT or /home/elias/.claude/jobs/90c6eed7/tmp
root = $APPTEST_ROOT, else $CLAUDE_JOB_DIR/tmp (AI agent sessions), else ~/.cache/llamamod-apptest
Commands (NAME first; --display only for 'start'/'run', remembered afterwards):
apptest.py setup NAME install from the ISO (shared hard-linked template)
@@ -27,7 +27,7 @@ Commands (NAME first; --display only for 'start'/'run', remembered afterwards):
--no-retry turns that off
Options of every command: --root DIR, --jar FILE (default: newest build/libs/*-all.jar),
--iso FILE (default ~/Downloads/GROOVE_DK.iso). Python: `from apptest import Session`.
--iso FILE (default $APPTEST_ISO or ~/Downloads/GROOVE_DK.iso). Python: `from apptest import Session`.
Environment: APPTEST_CP=<extra classpath> + APPTEST_JAVA_OPTS="-Dllamamod.extraMods=<class>" test
mods that are not registered yet (e.g. APPTEST_CP=build/classes/java/test).
@@ -46,8 +46,11 @@ import time
HOME = os.path.expanduser("~")
PROJECT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DEFAULT_ROOT = os.environ.get("APPTEST_ROOT", "/home/elias/.claude/jobs/90c6eed7/tmp")
DEFAULT_ISO = os.path.join(HOME, "Downloads", "GROOVE_DK.iso")
DEFAULT_ROOT = os.path.expanduser(
os.environ.get("APPTEST_ROOT")
or (os.path.join(os.environ["CLAUDE_JOB_DIR"], "tmp") if os.environ.get("CLAUDE_JOB_DIR") else "")
or os.path.join(HOME, ".cache", "llamamod-apptest"))
DEFAULT_ISO = os.path.expanduser(os.environ.get("APPTEST_ISO") or os.path.join(HOME, "Downloads", "GROOVE_DK.iso"))
TEMPLATE = "_llamamod-template"
SCREEN = (1920, 1080)
FULL = "%dx%d+0+0" % SCREEN
+4 -1
View File
@@ -40,7 +40,9 @@ import geomlib as G # noqa: E402
import mapchunk # noqa: E402
import strats as S # noqa: E402
CD_WADS = os.path.expanduser('~/.wine-lama/iso-dk/Setup/Eng/Cd/wads')
import lamapaths # noqa: E402
CD_WADS = lamapaths.CD_WADS
# ------------------------------------------------------------------ colours
KIND_COL = {
@@ -706,6 +708,7 @@ def main():
opt = ap.parse_args()
infos = []
if opt.all:
lamapaths.need(opt.src, 'CD wads folder', 'pass --src or set LLAMAMOD_CD')
os.makedirs(opt.all, exist_ok=True)
files = sorted(glob.glob(os.path.join(opt.src, 't[1-6]l*.wad')))
if opt.only:
+6 -3
View File
@@ -1,8 +1,11 @@
import sys, glob, os, struct
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import geomlib as G, mapchunk
# usage: debugviz_check.py <folder with debug WADs> [<folder with original CD WADs>]
SRC = (sys.argv[2] if len(sys.argv) > 2 else os.path.expanduser('~/.wine-lama/iso-dk/Setup/Eng/Cd/wads')) + '/'
import geomlib as G, mapchunk, lamapaths
USAGE = 'usage: debugviz_check.py <folder with debug WADs> [<folder with original CD WADs, default $LLAMAMOD_CD/wads>]'
if len(sys.argv) < 2 or sys.argv[1] in ('-h', '--help'):
sys.exit(USAGE)
SRC = lamapaths.need(sys.argv[2] if len(sys.argv) > 2 else lamapaths.CD_WADS, 'CD wads folder',
'pass it as 2nd argument or set LLAMAMOD_CD') + '/'
bad = 0
for f in sorted(glob.glob(sys.argv[1] + '/*.wad')):
n = os.path.basename(f); err = []
+1 -1
View File
@@ -15,7 +15,7 @@
# "lama-mod debug an|aus" toggles the "debug" mod independently of the others).
# Order: alphabetical, later mods win (testlevel > debug).
set -e
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-lama}"
export WINEPREFIX="${WINEPREFIX:-${LAMA_WINEPREFIX:-$HOME/.wine-lama}}"
MODS="$WINEPREFIX/mods"
OVL="$WINEPREFIX/cd-overlay"
DBG="$MODS/debug"
+31
View File
@@ -0,0 +1,31 @@
"""Where the Python tools look for the original game files (all overridable by environment variables).
LLAMAMOD_CD folder with the CD contents (wads/, music/, *.cor ...),
default $LAMA_WINEPREFIX/iso-dk/Setup/Eng/Cd
LAMA_WINEPREFIX Wine prefix of a hand-made game setup (lamatest.py, levelbuild.py --install),
default ~/.wine-lama
LAMA_MOD the lama-mod script, default ~/.local/bin/lama-mod, else tools/lama-mod
"""
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
WINEPREFIX = os.path.expanduser(os.environ.get('LAMA_WINEPREFIX') or '~/.wine-lama')
CD = os.path.expanduser(os.environ.get('LLAMAMOD_CD') or os.path.join(WINEPREFIX, 'iso-dk', 'Setup', 'Eng', 'Cd'))
CD_WADS = os.path.join(CD, 'wads')
TESTLEVEL_WAD = os.path.join(WINEPREFIX, 'mods', 'testlevel', 'wads', 'T1L9M001.wad')
def lama_mod():
p = os.environ.get('LAMA_MOD')
if p:
return os.path.expanduser(p)
p = os.path.expanduser('~/.local/bin/lama-mod')
return p if os.path.exists(p) else os.path.join(HERE, 'lama-mod')
def need(path, what, hint):
"""Exit with a clear message if PATH does not exist."""
if not os.path.exists(path):
sys.exit('%s not found: %s (%s)' % (what, path, hint))
return path
+10 -5
View File
@@ -27,7 +27,8 @@ TestLevel (world 9, chapter 1) via save slot 3 and takes screenshots.
--keep leave the tested WAD installed (otherwise the previously
installed T1L9M001.wad is restored - not .orig!)
State/logs: $LAMATEST_DIR (default /tmp/lamatest-$USER).
State/logs: $LAMATEST_DIR (default /tmp/lamatest-$USER). Game: $LAMA_WINEPREFIX (default ~/.wine-lama),
see lamapaths.py.
Report lines (report.txt, "ergebnis: ...") and `status` output stay German: helper
scripts grep for them. OCR expectations are German game text.
Docs: ../notes/testharness.md
@@ -43,13 +44,16 @@ import time
HOME = os.path.expanduser("~")
DISPLAY = ":5"
SINK = "lama_stumm"
PREFIX = os.path.join(HOME, ".wine-lama")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import lamapaths # noqa: E402
PREFIX = lamapaths.WINEPREFIX
GAMEDIR = os.path.join(PREFIX, "drive_c", "Program Files", "Disney Interactive",
"Ein Koenigreich fuer ein Lama")
GAMEEXE = r"C:\Program Files\Disney Interactive\Ein Koenigreich fuer ein Lama\groove.exe"
MODWAD = os.path.join(PREFIX, "mods", "testlevel", "wads", "T1L9M001.wad")
OVERLAY = os.path.join(PREFIX, "cd-overlay")
LAMA_MOD = os.path.join(HOME, ".local", "bin", "lama-mod")
LAMA_MOD = lamapaths.lama_mod()
STATE = os.environ.get("LAMATEST_DIR", "/tmp/lamatest-%s" % os.environ.get("USER", "x"))
WINELOG = os.path.join(STATE, "wine.log")
XVFBLOG = os.path.join(STATE, "xvfb.log")
@@ -190,10 +194,11 @@ def check_slot3():
# ---------------------------------------------------------------- commands
def cmd_start(timeout=60):
lamapaths.need(GAMEDIR, "game folder", "set LAMA_WINEPREFIX")
os.makedirs(STATE, exist_ok=True)
f = foreign_groove()
if f:
die("Elias' own game instance is running (pid %s) - test aborted" % f)
die("the user's own game instance is running (pid %s) - test aborted" % f)
if game_alive():
log("game already running on %s" % DISPLAY)
return
@@ -756,7 +761,7 @@ def cmd_run(wad, outdir, n=8, seq=None, do_tour=None, keep=False):
rep = ["wad: %s" % os.path.abspath(wad)]
status = 0
if foreign_groove():
die("Elias' own game instance is running - aborted")
die("the user's own game instance is running - aborted")
# remember the previously installed WAD -> put it back afterwards (not .orig!)
prev = os.path.join(STATE, "vorher_T1L9M001.wad")
os.makedirs(STATE, exist_ok=True)
+31 -6
View File
@@ -13,8 +13,9 @@ ambient occlusion) and object placement (strats by type name, parameters from th
Coordinates (as in the game / for strats): x east, y up, z = grid row
(positive). 1 unit = 1 grid cell. The base level's grid is 96x96 (x,z in 0..96).
Angles in degrees (0..360) about the y axis.
Texture names (TEX/FLAT below) are German keys and part of the level script API; TEX indices are
village indices, other bases (--base) map a name to the same picture or reject it.
Texture names (TEX/FLAT below) are German keys and part of the level script API; the English names of
the Java builder work too (ALIASES). TEX indices are village indices, other bases (--base) map a name
to the same picture or reject it.
Docs: ../notes/levelbau.md
"""
import argparse
@@ -35,9 +36,11 @@ import mapchunk as M # noqa: E402
import stpcwalk # noqa: E402
import strats as S # noqa: E402
WADS = os.path.expanduser('~/.wine-lama/iso-dk/Setup/Eng/Cd/wads')
import lamapaths # noqa: E402
WADS = lamapaths.CD_WADS
BASE = os.path.join(WADS, 't1l1m001.wad')
MODWAD = os.path.expanduser('~/.wine-lama/mods/testlevel/wads/T1L9M001.wad')
MODWAD = lamapaths.TESTLEVEL_WAD
FX = 4096
# texture names for the village (t1l1m001). Contact sheet: notes/img/texturen_dorf.png
@@ -61,6 +64,23 @@ FLAT = {
'rot': (190, 50, 40), 'gelb': (230, 190, 60), 'blau': (60, 90, 190), 'schwarz': (20, 20, 20),
'gruen': (70, 140, 50), 'grau': (120, 120, 120),
}
# English names of the Java builder (levelbuild/LevelBuilder.java) -> the German keys above
ALIASES = {
'grass': 'gras', 'grass2': 'gras2', 'grass_light': 'gras_hell', 'grass_striped': 'gras_streifen',
'grass_dark': 'gras_dunkel', 'grass_patch': 'gras_fleck', 'flowers': 'blumen', 'flowers2': 'blumen2',
'flowers3': 'blumen3', 'earth': 'erde', 'earth2': 'erde2', 'earth3': 'erde3', 'grass_edge': 'graskante',
'grass_edge2': 'graskante2', 'grass_edge_sand': 'graskante_sand', 'stone': 'stein', 'stone2': 'stein2',
'stone_blue': 'stein_blau', 'stone_edge': 'stein_kante', 'stone_green': 'stein_gruen',
'stone_green2': 'stein_gruen2', 'stone_moss': 'stein_moos', 'slab': 'platte', 'slab2': 'platte2',
'slab_edge': 'platte_kante', 'wood_dark': 'holz_dunkel', 'bamboo': 'bambus', 'bamboo2': 'bambus2',
'wood_grain': 'holz_maser', 'wood_grain2': 'holz_maser2', 'crate': 'kiste', 'bark': 'rinde',
'wood_brown': 'holz_braun', 'wood_brown2': 'holz_braun2', 'beam': 'holzbalken', 'wood_rings': 'holz_ringe',
'wood_edge': 'holzkante', 'inca': 'inka', 'pattern_purple': 'muster_lila', 'pattern_purple2': 'muster_lila2',
'pattern_purple3': 'muster_lila3', 'mushroom_red': 'pilz_rot', 'mushroom_red2': 'pilz_rot2',
'stone_stairs': 'treppe_stein',
'sea': 'meer', 'sea_deep': 'meer_tief', 'white': 'weiss', 'red': 'rot', 'yellow': 'gelb', 'blue': 'blau',
'black': 'schwarz', 'green': 'gruen', 'grey': 'grau',
}
# strat types present in the village code but not placed in the village:
# take type fields (nvars, slots, flags, extra) + parameters from another level
FOREIGN = {'RubyIdol': 't1l1m002.wad'}
@@ -200,6 +220,7 @@ class Level:
key = 'rgb%02x%02x%02x' % t
FLAT.setdefault(key, t)
t = key
t = ALIASES.get(t, t)
if t in TEX:
names = self._base_names()
if t not in names:
@@ -425,7 +446,7 @@ class Level:
V.append(tuple(float(c) * scale + o for c, o in zip(p[1:4], offset)))
elif p[0] == 'usemtl':
m = p[1]
cur = int(m[4:]) if m.startswith('tex_') else (m if (m in TEX or m in FLAT) else tex)
cur = int(m[4:]) if m.startswith('tex_') else (m if (m in TEX or m in FLAT or m in ALIASES) else tex)
elif p[0] == 'f':
ids = [int(t.split('/')[0]) for t in p[1:]]
pts = [V[i - 1 if i > 0 else len(V) + i] for i in ids]
@@ -980,6 +1001,8 @@ def main():
ap.add_argument('--textures', metavar='PNG')
ap.add_argument('--set', action='append', default=[], help='variable for the level script, e.g. --set teil=a')
a = ap.parse_args()
if a.types or a.textures or a.level:
lamapaths.need(a.base, 'base level', 'pass --base or set LLAMAMOD_CD')
if a.types:
L = Level(a.base)
for n in L.types():
@@ -990,9 +1013,10 @@ def main():
if a.textures:
import subprocess
names = {v: k for k, v in TEX.items()}
english = {g: e for e, g in ALIASES.items()}
subprocess.run([sys.executable, os.path.join(HERE, 'texsheet.py'), a.base, a.textures], check=True)
for i in sorted(names):
print('%4d %s' % (i, names[i]))
print('%4d %-16s %s' % (i, names[i], english.get(names[i], '')))
return
if not (a.level and a.out):
ap.error('specify LEVEL.py and OUT.wad')
@@ -1006,6 +1030,7 @@ def main():
mod.report(L)
if a.install:
import shutil
lamapaths.need(os.path.dirname(MODWAD), 'TestLevel mod folder', 'set LAMA_WINEPREFIX')
if not os.path.exists(MODWAD + '.orig'):
shutil.copy2(MODWAD, MODWAD + '.orig')
shutil.copyfile(a.out, MODWAD)