From f7e674d9cbf03e845375404f6b6886ae11648f35 Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 21 Jul 2026 10:08:37 -0500 Subject: [PATCH] tools/mkdist.py: tester-distribution zip builder Packages the player bats (zip root), BOTH exes (build\ pod fallback + build-glass\ preferred -- the bats auto-select glass = clickable cockpit / trigger config / bindings.txt keymap), the runtime DLLs next to each exe (OpenAL32.dll -- the first clean-extract boot test failed without it), and the git-TRACKED content/ files only (the working tree carries dev junk that must not ship). Output: dist/BT411--.zip (dist/ gitignored). Verified: clean-extract boot test -- glass exe boots the GLASS profile and simulates; pod exe boots as fallback. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/mkdist.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tools/mkdist.py diff --git a/tools/mkdist.py b/tools/mkdist.py new file mode 100644 index 0000000..528447d --- /dev/null +++ b/tools/mkdist.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""mkdist.py -- build the tester-distribution zip. + +Layout (matches what the player bats expect): + BT411--/ + play_solo.bat join.bat join_lan.bat (from players/) + build/Release/btl4.exe (pod build -- fallback) + build-glass/Release/btl4.exe (preferred: clickable cockpit) + content/... (git-TRACKED files only -- + the working tree carries dev + junk that must not ship) + +The bats prefer build-glass when present and set BT_PLATFORM=glass with it, +so testers boot the glass experience (PadRIO clicks, trigger config, +bindings.txt keymap) with the pod build as fallback. + +Usage: python tools/mkdist.py [outdir] (default outdir = dist/) +""" +import os +import subprocess +import sys +import time +import zipfile + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +def main(): + os.chdir(REPO) + outdir = sys.argv[1] if len(sys.argv) > 1 else os.path.join(REPO, "dist") + os.makedirs(outdir, exist_ok=True) + + githash = subprocess.check_output( + ["git", "rev-parse", "--short", "HEAD"]).decode().strip() + stamp = time.strftime("%Y-%m-%d") + name = "BT411-%s-%s" % (stamp, githash) + zpath = os.path.join(outdir, name + ".zip") + + tracked = subprocess.check_output( + ["git", "ls-files", "-z", "content/"]).decode().split("\0") + tracked = [t for t in tracked if t] + + exes = ["build/Release/btl4.exe", "build-glass/Release/btl4.exe"] + bats = ["players/play_solo.bat", "players/join.bat", "players/join_lan.bat"] + for p in exes + bats: + if not os.path.exists(p): + sys.exit("MISSING: %s -- build/refresh it first" % p) + + # Runtime DLLs living next to each exe (OpenAL32.dll etc.) -- the exe + # fails to load without them (caught by the clean-extract boot test). + for exe in list(exes): + d = os.path.dirname(exe) + for f in sorted(os.listdir(d)): + if f.lower().endswith(".dll"): + exes.append(d + "/" + f) + + total = 0 + with zipfile.ZipFile(zpath, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as z: + for p in bats: # bats at the zip ROOT (next to + arc = os.path.basename(p) # content\ + build\, per the guard) + z.write(p, name + "/" + arc) + total += os.path.getsize(p) + for p in exes: + z.write(p, name + "/" + p) + total += os.path.getsize(p) + for p in tracked: + if not os.path.exists(p): + print(" (skipping tracked-but-absent: %s)" % p) + continue + z.write(p, name + "/" + p) + total += os.path.getsize(p) + + zsz = os.path.getsize(zpath) + print("wrote %s" % zpath) + print(" %d files, %.1f MB raw -> %.1f MB zipped" + % (len(bats) + len(exes) + len(tracked), total / 1048576.0, + zsz / 1048576.0)) + +if __name__ == "__main__": + main()