J. Rogers, SE Ohio
1. Overview & Architecture
The
zil2gdl compiler translates parsed Zork Implementation Language (ZIL) abstract syntax trees (ASTs) and world models into self-contained, deterministic GDL game packages (game.json + optional zil_runtime.py native extension).Rather than emulating the Z-machine bytecode VM,
zil2gdl lowers ZIL's object-tree, flag system, verb syntax tables, and action routines into GDL's declarative zones, entities, precondition-effect interactions, and native extension escape hatches.Compilation Pipeline
text
sources/<game>/*.zil
│
▼ (Existing build_viewer.py parser + Routine AST lifter)
Intermediate World IR
│
├──► Structural Pass ──► topology, zones, entity_schemas, setup
├──► Grammar Pass ──► interactions (verb/object preconditions & effects)
└──► Routine Pass ──► Declarative DSL expressions OR zil_runtime.py
│
▼
games/<game>/
├── game.json (Pure declarative GDL specification)
└── zil_runtime.py (Generated native extension for complex routines)
Use code with caution.
2. Structural Mapping (ZIL World → GDL Topology)
Infocom games are fundamentally containment trees where rooms, containers, actors, and inventory items are all ZIL objects with a
LOC (parent) and bit-flags.A. Topology & Zones
- Topology Type:
"zones"(graph-linked zones rather than a Cartesian grid). - Rooms as Zones: Every
<ROOM>(or post-1985<OBJECT ... (LOC ROOMS)>) compiles into a GDL zone:json"WEST_OF_HOUSE": { "type": "set", "visible": false, "attributes": { "title": "West of House", "description": "You are standing in an open field west of a white house...", "exits": { "NORTH": { "target": "NORTH_OF_HOUSE", "condition": null }, "EAST": { "target": "KITCHEN", "condition": "eq(entity('FRONT_DOOR').flags.OPENBIT, true)" } }, "flags": { "RLANDBIT": true, "ONBIT": true, "TOUCHBIT": false } } }Use code with caution. - Special Global Zones:
inventory: Player's carried items (LOC = WINNER/ADVENTURER).limbo: Unspawned or removed entities (LOC = <>).global_objects: Pseudo-objects andLOCAL-GLOBALSaccessible across multiple rooms.
B. Entity Schemas & Object Containment
- Entities: Every ZIL
<OBJECT>compiles into a GDL entity spawned into its initialLOCzone duringsetup. - Containment: ZIL nested containment (e.g., a leaflet inside a mailbox inside
WEST_OF_HOUSE) maps cleanly by giving every container/supporter entity its own dynamic child zone (zone('MAILBOX_CONTENTS')) gated byOPENBITorCONTBITvisibility rules. - Properties & Flags:
- ZIL
FLAGS(TAKEBIT,OPENBIT,LIGHTBIT,WEAPONBIT,NDESCBIT) map to boolean properties underentity.flags.<FLAG_NAME>. - ZIL properties (
STRENGTH,VALUE,CAPACITY,SIZE,TEXT) map to typed key-value fields on the entity.
- ZIL
3. Grammar & Interaction Compilation (
<SYNTAX> → interactions)ZIL separates parsing from execution via
<SYNTAX> rules (VERB OBJECT PREP OBJECT = V-ROUTINE), pre-actions (PRE-ROUTINE), and object/room handlers (ACTION).A. Verb-to-Interaction Lowering
Each
<SYNTAX> production compiles into a parameterized GDL interaction:- Navigation (
V-WALK): Compiles into directional movement interactions (move_north,move_east, etc.) checking the current room'sexits[dir]target and condition. - Standard Manipulation (
TAKE,DROP,OPEN,CLOSE,EXAMINE,PUT-IN): Compiles into generic GDL interactions utilizingrequires_choiceor target entity selection:json"take_object": { "conditions": [ "eq(entity.zone, state.current_room)", "eq(entity.flags.TAKEBIT, true)", "not(eq(entity.flags.TRYTAKEBIT, true))" ], "effects": [ "move_to_zone(entity, zone('inventory'))", "set(entity.flags.TOUCHBIT, true)" ] }Use code with caution.
B. ZIL Dispatch Precedence (The Hook Chain)
In ZIL, a turn evaluates handlers in strict priority order. The compiler preserves this dispatch chain via ordered interaction conditions or a dispatcher wrapper:
- Room
M-BEGhook (intercepts command before execution) - Pre-Action (
PRE-V-FOO) (e.g., checking light availability beforeV-READ) - Indirect Object
ACTION(PRSI) - Direct Object
ACTION(PRSO) - Default Verb Routine (
V-FOO) - Room
M-END& Clock Interrupts (CLOCKER/QUEUEdaemons)
4. Routine Lifting Strategy (Hybrid Declarative + Native)
ZIL routines (
<ROUTINE ...>) are Lisp-like imperative functions using COND, FSET?, IN?, MOVE, TELL, and RTRUE/RFALSE. zil2gdl uses a two-tier compilation strategy:Tier 1: Pure Declarative DSL Lifting (Simple Routines)
Routines that strictly test flags/locations and mutate properties or move objects compile directly into GDL
conditions and effects JSON arrays:<FSET? ,MAILBOX ,OPENBIT>→"eq(entity('MAILBOX').flags.OPENBIT, true)"<FSET ,MAILBOX ,OPENBIT>→"set(entity('MAILBOX').flags.OPENBIT, true)"<MOVE ,LAMP ,WINNER>→"move_to_zone(entity('LAMP'), zone('inventory'))"<IN? ,SWORD ,WINNER>→"eq(entity('SWORD').zone, zone('inventory'))"
Tier 2: Native Extension Generation (
zil_runtime.py)Complex routines—such as Zork's thief AI (
THIEF-VS-ADVENTURER), combat damage tables, parser disambiguation, or multi-branch TELL text formatting—compile into clean, deterministic Python functions inside games/<game>/zil_runtime.py registered under the native.zil_* namespace:python
def register(registry):
registry.add("native.zil_eval_routine", eval_routine)
registry.add("native.zil_tick_daemons", tick_daemons)
Use code with caution.
- Deterministic RNG: All ZIL
<PROB N>and<RANDOM N>calls bind directly to GDL's seeded RNG state so--seed Nproduces identical Infocom playthroughs. - Output Buffer: ZIL
<TELL ...>statements append strings tostate.transcript_buffer, which GDL's console/GrokThink presentation layer renders after each turn.
5. Daemon & Fuse Scheduler (
CLOCKER → game_flow)Infocom games rely on background timers (
ENABLE / QUEUE / INT) for lamp batteries, troll recovery, and thief wandering:- Maintain a
daemonslist instate_schemastoring{ "routine": "I-LANTERN", "ticks_remaining": 330, "enabled": true }. - At the end of every player turn (
game_flow.post_turn), triggertick_daemonsto decrement active fuses and fire expired routines intostate.transcript_buffer.
6. Presentation & GrokThink Integration
Because GDL separates rules from presentation, compiled Infocom games automatically inherit multiple frontends:
- GDL Console Renderer: Renders
state.transcript_buffer, current room exits, and legal contextual actions as clickable choices or text input. - Live Map Sync: Exposes
state.current_roomandvisitedflags directly to your Infocom Source Explorer force-directed graph or GrokThink canvas widgets. - AI Agent Player: Exposes the compiled Infocom world via
gdl/api.py(create/submit/state), allowing an Ollama or MCP node in GrokThink to play Zork with deterministic replay and inspectable state transitions.
7. Verification via Differential Testing
Using your existing
dfrotz runner as a ground-truth oracle:- Run a recorded command script (
saved_games/zork1_walkthrough.txt) through bothdfrotz(with a fixed random seed) andpython3 game.py games/zork1. - Diff the resulting room locations, inventory contents, object flags, and score at every step to automatically validate compiler fidelity across all 30 games in your archive.
No comments:
Post a Comment