Scripts

August 17, 2026 · View on GitHub

Catalogue of the repository's developer, CI, and packaging scripts. Kept current the same way docs/README.md is: when you add, move, or remove a script under scripts/, update its row here in the same commit.

The Invoked by column tells you whether a script is load-bearing or a one-off: CI / Makefile / pre-commit scripts are wired into the project and must keep working; manual scripts are run by hand (usually documented in docs/); spike scripts are one-shot investigations kept as reproducible evidence for a plan doc, not maintained tooling.

Formatting & lint (ci/)

ScriptWhat it doesInvoked by
ci/clang-format-select.shSingle source of truth for the pinned clang-format major version (CLANG_FORMAT_MAJOR). Sourced, not executed.the three format scripts + pre-commit
ci/check-format.shVerify tracked C/C++ files already match the clang-format policy; non-zero on drift.CI (format.yml), make format-check
ci/check-docs-links.shVerify doc references resolve: markdown links and #anchors via lychee, plus docs/<name>.md paths named by bare path in source comments, tests and CMake (invisible to a link checker). --external checks URLs. lychee's settings live in lychee.toml, shared with local runs.CI (docs-links.yml), make docs-links
ci/check-docs-symbols.pyVerify a doc's claim about where code lives: for `Foo()` in [FILE] the symbol must be defined there, and the report names the file it is actually in. Paths resolving is not the same as the prose being right.CI (docs-links.yml), make docs-symbols
ci/apply-format.shApply the clang-format policy to tracked C/C++ files (respects .clang-format-ignore).manual, pre-commit fix step
ci/filter-format-files.pyFilter a NUL-delimited git file list through .clang-format-ignore (one shared exclusion list).the format shell scripts
ci/run-clang-tidy.shRun clang-tidy for memory-safety / UB checks over the project's own sources (needs compile_commands.json).manual (see .clang-tidy)

Build & run (dev/)

ScriptWhat it doesInvoked by
dev/build-and-run.shBuild and run lba2cc from any working directory; resolves repo root and game data.make run
dev/repo_root.shPrint the absolute repository root (directory with the top-level CMakeLists.txt).Makefile
dev/check-tooling.shProbe the external tools the repo expects, tier by tier; non-zero only when the clone cannot build. Reads every version floor from the file that pins it.make check-tooling, TOOLING.md
dev/build-android.shBuild the engine for Android (arm64-v8a default, armeabi-v7a via --abi).manual (ANDROID.md)
dev/build-sdl3-android.shCross-build SDL3 for Android and install it to a known prefix (prerequisite for build-android.sh).manual (ANDROID.md)
dev/check-16k-align.shVerify an Android APK is safe on 16 KB memory-page devices (segment alignment + uncompressed .so).CI (android), ANDROID.md

Driving a running engine (dev/)

Clients and sweeps for the --listen command socket, which needs a -DLBA2_CONTROL_SERVER=ON build; see CONTROL.md. The sweeps import lba2ctl and are restart-tolerant where they need to be, because walking many cubes in one process faults and a sweep that stops at the first fault covers almost nothing.

ScriptWhat it doesInvoked by
dev/lba2ctl.pySpeak the line protocol: a REPL for looking around, a Control class for scripting a probe loop. Its docstring carries the traps that cost the most to rediscover.manual, and imported by the sweeps below
dev/probe_bench.pyMeasure one probe over the socket against the process-per-probe it replaces.manual
dev/probe_zones.pyParse zonelist into structured zones, including which cube gates wait on a door collision. Imported by the two sweeps below.manual
dev/probe_gate_scan.pySweep every cube for transition gates that are enabled, separating ones switched off from ones waiting on a door collision: zonelist reports the two identically and they want opposite fixes.manual
dev/probe_cube_overlap.pySweep every cube for camera zones overlapping in all three axes, where list order alone decides which shot wins.manual
dev/probe_input_matrix.pyA/B whether injected input moves the hero, across several saves.manual

Release dry-runs (dev/)

Local wrappers that build a release binary and then delegate to the matching packaging/ bundler, so the local artifact layout can't drift from CI's.

ScriptWhat it doesInvoked by
dev/build-linux-tarball.shDry-run the Linux static-binary tarball: build, then call bundle-linux-tarball.sh.manual
dev/build-macos-release.shDry-run the macOS DMG: build (host arch by default), then call bundle-macos.sh.manual
dev/build-windows-release.shDry-run the Windows ZIP: build (MSYS2 native or Linux cross), then call bundle-windows.sh.manual
dev/verify-release.shPost-release smoke test: download the published Linux artifacts, run each in a clean container, and check the version, AppImage self-update channel and AppStream metainfo they carry.manual (RELEASING.md)

Packaging (packaging/)

One bundler per platform; the CI release workflows are glue around them. The *-readme.txt.in files are the user-facing README templates the bundlers expand into each artifact.

ScriptWhat it doesInvoked by
packaging/bundle-linux-tarball.shBundle a built Linux binary into a portable .tar.gz.CI, build-linux-tarball.sh
packaging/bundle-macos.shBundle a built .app into a macOS DMG.CI, build-macos-release.sh
packaging/bundle-windows.shBundle a built lba2cc.exe into a portable ZIP.CI, build-windows-release.sh
packaging/bundle-android.shBundle a built native .so into a debug-signed APK.CI (android)
packaging/make-appimage.shBuild a Linux AppImage (installs deps, packs the SDL3 runtime).CI (linux appimage)

Regression baselines & savegame corpus

ScriptWhat it doesInvoked by
dev/regen_projrec_baselines.shRegenerate projrec baseline hashes for the save corpus and attract-mode demo (tagged by render width).manual (CONTROL.md)
dev/run-savegame-corpus.shBuild and run the savegame corpus harness from any working directory.make (savegame corpus target)
save_probe.pyOffline probe of .lba save headers, LZSS bodies, fixed-offset fields, and a 32-vs-64 ABI forward-simulator.run-savegame-corpus.sh, SAVEGAME.md
save_probe_lz_selftest.pyGolden ExpandLZ vectors mirroring tests/SYSTEM/test_lz.cpp.make save-probe-lz-selftest
dev/dist_check.shSweep every retail distribution: one row per install, five assertions, non-zero exit if any fails. Needs real installs, so local only.manual (TESTING.md)
dev/png_hash.pyPlain pixel hash of a PNG capture, which is enough because --fixed-dt makes UI captures exactly reproducible.dist_check.sh, the control-harness UI tests

Data-format & asset tools (dev/)

Decoders and inspectors for the Adeline on-disk formats. Mostly one-off tools kept for reproducibility; cited from the format and effects docs.

ScriptWhat it doesInvoked by
dev/hqr_inspect.pyList / extract / decompress HQR archive entries (LZSS + LZMIT). Also a shared library imported by the art and LBA1 tools below.ENGINE_FILE_FORMATS.md, imported
dev/impact_disasm.pyDisassemble and round-trip IMPACT effect bytecode (RESS_IMPACT=47).IMPACT_SCRIPTS.md
dev/flow_dump.pyDecode FLOW particle-emitter definitions (RESS_FLOW=45).IMPACT_SCRIPTS.md
dev/pof_dump.pyDecode POF 2D wireframe shapes (RESS_POF=46).IMPACT_SCRIPTS.md
dev/iso_bin.pyRead the ISO9660 filesystem out of a raw Mode1/2352 (or cooked 2048) CD image on the fly.DISC_IMAGE_SOURCE.md
dev/disc_extract.pyExtract a playable game-data folder from a rip, a CD drive (--from-drive) or a ripped soundtrack, naming the CD tracks the way the engine asks for them (--selftest for the cue and TOC rules).GAME_DATA.md
dev/acf_decode.pyDecode Adeline ACF/XCF cinematic frames (Time Commando tile codec).spike
dev/acf_inspect.pyParse the ACF/XCF cinematic container chunk layout.spike
dev/extract_lba2_gog_media.pyExtract FMV / VOX / music from a GOG LBA2.GOG BIN image into the install dir.GAME_DATA.md
dev/fingerprint_distro.pyIdentify which release a game directory or disc image holds, from the payload rather than the config: RESS.HQR names the master, SCENE/TEXT the pressing.VERSIONS.md
dev/art_catalog_screen.pyDump every SCREEN.HQR bitmap to PNG (widescreen art inventory; output local-only).manual (widescreen)
dev/art_treatment_preview.pyPreview widescreen art treatments (letterbox / palette-fill / edge-clone / mirror-tile) as PNGs.manual (widescreen)

LBA1 feasibility spikes (dev/)

Read-only investigations backing LBA1_PORT_PLAN.md; each confirms one axis of hosting LBA1 content on this engine.

ScriptWhat it doesInvoked by
dev/lba1_body_probe.pyTranscode an LBA1 body to the lba2cc body format.spike (LBA1_PORT_PLAN §6)
dev/lba1_body_render.pyRender a decoded LBA1 body to PNG with a stdlib software rasteriser.spike (LBA1_PORT_PLAN §6)
dev/lba1_script_remap.pyRemap LBA1 Life-script opcodes onto the LBA2 VM; flag-width divergence report.spike (LBA1_PORT_PLAN §6)
dev/lba1_bkg_repack.pyRe-index LBA1's three background HQRs into LBA2's single merged container.spike (LBA1_PORT_PLAN §6.5)
dev/lba1_voc_probe.pyConfirm LBA1 VOC audio plays through lba2cc's existing sample path.spike (LBA1_PORT_PLAN §6.6)

Git hooks (git-hooks/)

ScriptWhat it doesInvoked by
git-hooks/pre-commitOpt-in clang-format check on staged C/C++ files. Enable with git config core.hooksPath scripts/git-hooks.git (opt-in)

The test and packaging drivers that these scripts feed into, or that stand alongside them, live next to the code they exercise:

PathWhat it doesSee
../run_tests_docker.shBuild and run the full ASM↔C++ equivalence suite inside a Linux x86_64 Docker container.TESTING.md
../tests/automation/run.shRun the CLI control-harness / headless behaviour suite (test_*.sh + lib.sh).CONTROL.md
../tests/savegame/corpus/Savegame corpus harness (Python): manifest build, native-fallback check, baseline harness.corpus README.md
../tests/SNAPSHOT/Polyrec render / compare / bisect helpers.POLYREC.md