Mastodon Politics, Power, and Science: Infocom games.
Showing posts with label Infocom games.. Show all posts
Showing posts with label Infocom games.. Show all posts

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.

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...