Skip to content
HN On Hacker News ↗

GitHub - katiahayati/lucasartsifier: Static analysis for Sierra adventure games: finds softlocks by abstract interpretation of decompiled SCI scripts, derives and verifies guards, recompiles them into the game

▲ 161 points 97 comments by wkfauna 6d ago HN discussion ↗

Pangram verdict · v3.3

We believe that this text is a mix of AI and human-written content.

67 %

AI likelihood · overall

Mixed
31% human-written 69% AI-generated
SEGMENTS · HUMAN 1 of 4
SEGMENTS · AI 1 of 4
WORD COUNT 1,565
PEAK AI % 90% · §4
Analyzed
Aug 19
backend: pangram/v3.3
Segments scanned
4 windows
avg 391 words each
Distribution
31 / 69%
human / AI fraction
Verdict
Mixed
Pangram v3.3

Article text · 1,565 words · 4 segments analyzed

Human AI-generated
§1 Human · 30%

Sierra softlock analyzer Static analysis for 30-year-old adventure games. This tool decompiles a Sierra SCI game, abstract-interprets the decompiled scripts into a graph of guarded room transitions, item movements and plot-flag writes, finds the softlocks — states where the game still accepts input but victory has quietly become impossible — and derives, verifies, compiles and installs guards that prevent them. Nothing about any title is declared: the start room, the victory room, the death signal and the debug flags are all discovered from the game's own code. Sierra games, unlike LucasArts ones, let you get stuck. Forget the sunscreen in Los Angeles, board the cruise ship, and you die days later on a raft with no way back. This finds these traps automatically and blocks the crossing that causes them — at the last moment you can still comply. Four games analyzed and play-tested — Leisure Suit Larry 2 (SCI0, 1988), King's Quest IV (SCI0, 1988), King's Quest VI (SCI1.1, 1992), and Laura Bow 2 (SCI1.1, 1992) — same engine, no game-specific analysis code.

§2 Mixed · 62%

Demo King's Quest IV, patched — the whale, the night clock and seven stranded items, all guarded: The thirty-second version Abridged from a real run on Leisure Suit Larry 2 (python3 -m pipeline <game>): [2] ANALYZE anchors: start rm11, victory [86] (discovered) death signal: global101 == 1001, debug globals: [14, 100] (derived) 101 rooms, 27 strongly-connected components, 40 gating registers softlocks: 15 items + 1 disjunctive group(s) - Sunscreen ...

§3 Mixed · 32%

[3] DERIVE rm38 -> rm131: (and (gEgo has: 11) (gEgo has: 12) (gEgo has: 14) (gEgo has: 15)) rm57 -> rm58: (and (gEgo has: 21) (gEgo has: 24) (gEgo has: 25) (gEgo has: 26)) rm79 -> rm80: (or (gEgo has: 30) (gEgo has: 31)) rm131 -> rm138: (not (gEgo has: 13)) rm63: delete `(gEgo put: 21 -1)` (Hair_Rejuvenator) verifying against the guarded model... fixed 15 + 1 group(s); NEW softlocks introduced: none [4] PATCH compiled 117/118 scripts script.000 Main 10790 bytes script.057 rm57 2938 bytes ...

§4 AI · 90%

Done. 10 patch files in build/patch The analyzer discovered the ship boarding as a one-way crossing, derived which items must cross with you, re-verified the guarded model to prove the guards introduce no new softlocks, and recompiled the touched scripts into Sierra's own loose-patch format. Note rm131 -> rm138: (not (gEgo has: 13)). Guards carry negative literals too: the Spinach Dip is fatal to be holding in rm138, so the fix is to refuse the crossing while you still have it — placed where you can still throw it overboard, because demanding you drop something you can no longer drop is a wall, which this project treats as worse than the bug. The pipeline refuses to emit anything if the guards fail verification, or if a script it edited will not compile. A patched game plays normally — the patch mechanism is how Sierra shipped its own bug fixes, and the originals are never modified (delete the patch files to revert). You can set the guard behavior in-game: Full prevents every dangerous action; Lite prevents it once, then allows it with a warning; Off turns the guards off. What counts as a softlock? (or: Caveat Player) Some deaths are deliberately left in — the ones you can still avoid from where you are. The analysis distinguishes unwinnable states from avoidable deaths by reachability, not by death conditions. In Leisure Suit Larry 2, walking onto the KGB beach without the full disguise kills you. Some pieces of the disguise exist only on the cruise ship, so the analyzer refuses to let you leave the ship without them. But the rest is obtainable on the island — from the very place the death occurs — so that death stays in: it is how Sierra games hint at what you need to do. As Al Lowe says, "Save Early, Save Often!" Status Four games done, spanning the engine's two major eras (SCI0 1988 → SCI1.1 1992), with nothing declared per title — start room, victory room, death signal and debug flags are all derived from each game's own code. game engine status notes Leisure Suit Larry 2 (1988) SCI0 done & tested the Spinach Dip: fatal to carry, so the guard is a negative literal, placed while you can still ditch it King's Quest IV (1988) SCI0 done & tested the real-time night clock; the whale — random events guarded by arming them only when survivable King's Quest VI (1992) SCI1.1 done & tested the two ending paths massively complicate analysis; guarding the start of the wedding (a timer) until necessary items are in hand Laura Bow 2 (1992) SCI1.1 done & tested the act structure: the plot clock is a register, act breaks are one-way, demands ride the act-flip interceptor King's Quest V (1990) SCI1-middle in progress (kq5 branch) the village market: matching payments to merchants so everyone can be paid — detection becomes a matching problem How it works, briefly Decompile the game binary to a typed control-flow AST (JSON IR). Abstract-interpret that AST, composing path conditions into a game graph: guarded movement edges, item acquisitions, item losses, register writes. Room art (PIC/VIEW) and obstacle polygons are read too, since some gates are geometric and exist nowhere in the script. Condense the graph into strongly-connected components — regions you can wander freely. Only the one-way edges between them can strand you, which is what makes the problem finite. Find strandings: an item obtainable before a crossing, unavailable after, still needed beyond. Derive a guard from the winning region — the condition under which the goal is still reachable — and place it at the last point where the player can still comply. Item-wasting dead ends are neutralized separately, with a "Just kidding!" message that prevents you from wasting the needed item, and no score penalty. Recompile and emit. The patched game is now playable normally (e.g. in ScummVM or DOSBox). Longer version in docs/HOW-IT-WORKS.md; per-file map in docs/ARCHITECTURE.md; current KQ6 status in docs/KQ6-STATUS.md; LB2's derivation log in docs/LB2-ORACLE.md. The toolchain Steps 1 and 6 stand on two excellent existing projects, driven headless: Decompilation is sci-tools (sluicebox, MIT). We maintain a fork whose json-ir branch adds a second emitter beside the .sc source output: the typed control-flow AST as JSON, which is what the analysis consumes. The decompilation logic itself is untouched. Compilation is SCICompanion's script compiler (Philip Fortier, GPL-2.0+), which we ported to build and run headless on Linux — tools/scicompile/ is a small CLI plus a compatibility layer that replaces the MFC/Windows surface, calling the real parser, class browser, resource map and code generator (GenerateScriptResource). The vendor tree is cloned at build time and never edited; a handful of files are patched as a build step for MSVC-only constructs, with every change documented in tools/scicompile/BUILD_NOTES.md. Each guarded script the pipeline emits is compiled by the same code paths SCICompanion uses in its IDE, then wrapped in Sierra's loose-patch header. Install The analysis is Python 3 with no third-party packages at all — src/ imports only the standard library. What needs installing is the two external toolchains it drives: the decompiler (C#) and the SCI compiler (C++), both built here from source. Prerequisites sudo apt install python3 git cmake g++ make dotnet-sdk-8.0 # Debian/Ubuntu what why verified against Python 3.12 the analysis and the tests (src/) 3.12.3 .NET SDK 8 builds sci-tools, which decompiles the game 8.0.129 cmake ≥ 3.16, a C++14 compiler, make builds scicompile, which recompiles the patched scripts cmake 3.28.3, g++ 13.3 git both vendored trees are cloned at build time, not bundled 2.43 Verified from scratch in a clean ubuntu:24.04 container: the packages above, the two builds below, a full pipeline run and the game-independent tests — see the log recipe in docs/HOW-IT-WORKS.md. One-time build git clone https://github.com/katiahayati/lucasartsifier && cd lucasartsifier # 1. the decompiler. Clones our sci-tools fork into vendor/, builds it, and decompiles # GAME into build/ir -- both a .sc source tree and the typed-AST JSON IR. tools/sci-tools-fork/build.sh /path/to/game # 2. the compiler: SCICompanion's, ported headless. Its source is cloned and never modified; # the port lives beside it in tools/scicompile/{compat,patched}. git clone --depth 1 https://github.com/icefallgames/SCICompanion vendor/SCICompanion cmake -S tools/scicompile -B tools/scicompile/build cmake --build tools/scicompile/build -j Step 1 alone is enough to analyze a game (--report); step 2 is what turns the derived guards into patch files. vendor/ is gitignored — no third-party source and no game data is redistributed here. Run it You supply your own copy of a game; none is included. The commands run from src/: cd src python3 -m pipeline /path/to/game # decompile -> analyze -> derive -> patch python3 -m pipeline /path/to/game --report # analyze only, write nothing python3 -m pipeline /path/to/game --skip-decompile # reuse the IR under build/ir Output lands in build/patch/ as loose patch files: cp build/patch/script.* /copy/of/game/ # install rm /copy/of/game/script.0* # revert Loose script.NNN files override the mapped resource, so RESOURCE.MAP and the volumes are never modified and the patch reverts by deleting files. Point it at a copy of the game, never at your only one. Running the tests python3 tools/run_tests.py # the whole suite (~21 min with every model cold) docs/TESTING.md has the rest: why some checks are RED on purpose, the three regression nets and the different questions they answer, how to measure a change against the full output surface before committing it, and how to drive a patched build under ScummVM with nobody at the keyboard.