Mastodon Politics, Power, and Science: Specification: ZIL-to-GDL Cross-Compiler (zil2gdl)

Thursday, November 12, 2026

Specification: ZIL-to-GDL Cross-Compiler (zil2gdl)

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 and LOCAL-GLOBALS accessible across multiple rooms.
B. Entity Schemas & Object Containment
  • Entities: Every ZIL <OBJECT> compiles into a GDL entity spawned into its initial LOC zone during setup.
  • 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 by OPENBIT or CONTBIT visibility rules.
  • Properties & Flags:
    • ZIL FLAGS (TAKEBIT, OPENBIT, LIGHTBIT, WEAPONBIT, NDESCBIT) map to boolean properties under entity.flags.<FLAG_NAME>.
    • ZIL properties (STRENGTH, VALUE, CAPACITY, SIZE, TEXT) map to typed key-value fields on the entity.

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's exits[dir] target and condition.
  • Standard Manipulation (TAKE, DROP, OPEN, CLOSE, EXAMINE, PUT-IN): Compiles into generic GDL interactions utilizing requires_choice or 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:
  1. Room M-BEG hook (intercepts command before execution)
  2. Pre-Action (PRE-V-FOO) (e.g., checking light availability before V-READ)
  3. Indirect Object ACTION (PRSI)
  4. Direct Object ACTION (PRSO)
  5. Default Verb Routine (V-FOO)
  6. Room M-END & Clock Interrupts (CLOCKER / QUEUE daemons)

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 N produces identical Infocom playthroughs.
  • Output Buffer: ZIL <TELL ...> statements append strings to state.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 daemons list in state_schema storing { "routine": "I-LANTERN", "ticks_remaining": 330, "enabled": true }.
  • At the end of every player turn (game_flow.post_turn), trigger tick_daemons to decrement active fuses and fire expired routines into state.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_room and visited flags 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:
  1. Run a recorded command script (saved_games/zork1_walkthrough.txt) through both dfrotz (with a fixed random seed) and python3 game.py games/zork1.
  2. 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

Player Portal in Mega Mappers

Codex Engine: Player Portal, Auth, Rules-as-MCP & AI NPCs (Pure Node-Tree Edition) Defining game rulesets (D&D 5e, Pathfinder, Cyber...