Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

AGI Clean-Room Reverse Engineering

Project repository: peterkelly/agi-re

This book is the reverse-engineering evidence record. It collects local-file observations, disassembly, addresses, commands, hypotheses, corrections, and compatibility-test results for the AGI interpreters being examined. Private game files are selected locally and are not part of the repository.

This evidence book is not the clean-room deliverable presented to an implementation team. The separate spec/ mdBook contains the portable behavioral specification. Facts discovered here are promoted there only after they can be expressed as externally observable behavior without relying on Sierra’s DOS implementation details.

The project rules are:

  • Use only local files and locally observed behavior.
  • Do not consult external AGI documentation, source code, or prior AGI-specific knowledge.
  • Document each reverse engineering step as evidence for the clean-room process.
  • Keep implementation-independent behavioral requirements in the separate specification rather than treating this evidence book as the final spec.

Current documentation:

  • The chronological evidence trail is in Clean-Room Executable Notes.
  • User-facing progress updates and their immediate outcomes are preserved in Progress Log.
  • The current readable decompilation of the loader is in SIERRA.COM Loader.
  • Notes on the decrypted interpreter executable, startup, and overlay loading are in Decrypted AGI Executable.
  • Stable symbolic names for routines, tables, and globals are tracked in Symbolic Labels, so later interpreter versions can be compared without relying on matching offsets.
  • Version-specific differences between local interpreter/game inputs are tracked in Versions.
  • Notes on directory and volume resource files are in Resource Files.
  • A higher-level implementation model of the observed runtime data types and operation families is in Runtime Model.
  • The growing executable compatibility suite and current graphics-render commands are described in Compatibility Testing.

Current Status

This page is a short handoff note for resuming the clean-room reverse engineering work.

Current Focus

The final deliverable is the separate mdBook under spec/: a self-contained description of externally observable AGI behavior suitable for an independent implementation team. The existing docs/ book remains the reverse-engineering evidence record and may contain original implementation details. New findings must continue to be documented in docs/, then promoted to spec/ only as portable behavioral contracts.

The logic bytecode catalog is complete for the current SQ2 interpreter. The standalone specification now also covers picture commands, view/cel decoding, object behavior, parser/input/menu/inventory behavior, sound command-output behavior, and the room/replay/save state machine. All five observed profile 2.936 save blocks now have exhaustive block-relative maps. The profile 3.002.149 Gold Rush save map is also complete for the observed local save envelope. Former opaque block-1 ranges are classified as reserved words, inactive key-map/string records, and alignment bytes; the clean spec defines canonical initialization and byte preservation. The subsystem/version conformance matrix is now present. The exact text-glyph bitmap question is now resolved as a font-profile boundary: core compatibility specifies text cells, bytes, attributes, geometry, and modal behavior, while bitmap-identical text screenshots must declare a font set. The KQ4D/3.002.102 picture, view, and object-update comparison is now complete. Fifty-two role pairs cover the primary full-EGA renderer/object path and match Gold Rush after relocation; the remaining renderer differences are alternate display-mode branches outside the target. The highest-value future work is applying the completed workflow to newly supplied interpreter builds, followed by any concrete renderer edge behavior that still affects the portable full-EGA target.

The top-level 2.936 cycle is now instruction-backed and promoted as a portable phase order. Timer and sound ticks are asynchronous inputs; synchronous work orders pacing, transient input reset, input processing, direction mirroring, pre-motion, logic-0 execution and immediate re-entry, status refresh, transient cleanup, and the alternate-text-mode-gated object update.

Portable conformance bundles are no longer visual-only. In addition to the canonical 160 by 168 EGA frame, a case may carry semantic JSON values for state, input, ordered sound commands, and persistence outcomes. The comparator validates portable JSON types and reports typed differences by semantic path, without requiring DOS memory layouts or another implementation’s object model.

KQ2/2.411 and LSL1/2.440 are now separate promoted profiles. Both end at action 0xa9, use exact-four-loop selection, lack later sound attenuation envelopes, and serialize a 0x05df first save block. KQ2 has point-only pattern commands, always prompts for restart, and always emits both non-PC tone bytes. LSL1 has the later brush patterns, honors the f16 restart bypass, and conditionally suppresses the low tone byte. KQ2 and LSL1 selected-game save dimensions are mapped and confirmed from existing save files.

That same-version check is now complete. PQ1/KQ1 and KQ3/SQ2 have identical action, condition, and subsystem code; their loaded images differ only in game signature bytes. PQ1 and KQ3 save dimensions are mapped as selected-game data. The new tools/match_interpreter_roles.py performs normalized first-pass role relocation for future builds and reports unique, ambiguous, and unmatched candidates for manual source review.

XMAS.230 is now identified as AGI 2.230 and has its own promoted full-EGA profile. It is a source-backed hybrid of the neighboring early builds: its position/composition/parser/save paths follow 2.272, while exit, motion-clear, and sound output follow 2.089. Its unique view format packs cel count and mutable orientation/mirroring state into the loop header; six selected view resources exercise that path. The selected save dimensions are also mapped.

Full KQ4 is now separated from the KQ4D demo. The full game uses AGI 3.002.086, a 178-action range ending at 0xb1, and a distinct promoted profile. It combines the v3 inventory/save/restart/motion changes with the earlier increment-style release gate, 39 key mappings, direct room targets, and v2 input-width actions. Its two source-visible object variants are the 2.936 four-or-more-loop rule and a left-boundary report for an exact-zero X proposal. Full KQ4 save dimensions are mapped as 26 object records, 45 inventory entries, and 250 replay pairs. KQ4D remains the later 3.002.102 demo profile with its own smaller selected-game save dimensions.

KQ1/2.917 remains a promoted full-EGA profile. Its valid action range ends at 0xad; all shared action and condition handlers match 2.936. The sole renderer/object delta in the 52-role pass is exact-four-loop automatic selection, and the selected KQ1 save dimensions are mapped for binary interchange.

The current top-level compatibility runner is tools/compatibility_suite.py. The latest smoke report is build/compatibility-suite/qemu_smoke_002.json, covering local tests, mdBook, opcode-evidence freshness, parser QEMU probes, picture scanner command-resume probes, raw command-looking operands, and relative-line underflow. The latest broad report is build/compatibility-suite/qemu_broad_002.json, which includes the smoke layer plus the eight-picture timed carousel and the 19-case view/object stress carousel. Every selected command returned zero.

The user intentionally deleted build/. Those report paths are historical generated-artifact locations rather than preserved project inputs. The source tools and private games/ inputs regenerate required fixtures and reports; no specification evidence depends on retaining generated disk images or captures.

The latest resource-lifecycle source pass corrected an overly map-like clean model. Picture and view retention is ordered within each family. Discarding a selected retained resource clears that record and every later same-family record; replay event kinds 6 and 7 reproduce the same truncation. Source shows the family-link mutation and shared heap rewind directly, so no QEMU probe was needed. The clean spec excludes continued use of a discarded payload and a portable transition model now has focused regression coverage.

All current local logic resources were also scanned for discard usage. Every profile uses picture or view discard, and KQ2 logic 67 provides a particularly clear LIFO example: views 53,59,51,52,57,60 are later discarded as 60,57,52,51,59,53. This supports the valid-script discipline independently of the executable source that proves the broader truncation effect.

Recent source-first renderer work corrected picture relative-line endpoint semantics: handler 0x665e computes relative deltas in 8-bit coordinate registers, then clamps only above x=0x9f or y=0xa7. A negative underflow therefore reaches the right or bottom edge rather than zero. QEMU batch relative_underflow_001 and the current smoke suite validate this. The object control-acceptance helper 0x56b8 also has tighter local source-model tests for the “other nonzero high nibble” branch and priority-15 scan bypass/event-flag clearing.

Confirmed Motion and Object Findings

  • Persistent object movement has a QEMU-backed 17-case batch in build/object-movement-probes/batches/motion_modes_004.json; the batch matched with 17 matches, 0 mismatches, and 0 errors.
  • 0x51 (move_object_to) and 0x52 (move_object_to_var) can complete in two ways:
    • script logic can reissue the setup action each interpreter cycle while the completion flag is clear;
    • a one-shot setup can complete through the countdown-gated dispatcher 0x067a -> 0x1672 when object byte +0x01 is ready.
  • 0x53 (approach_first_object_until_near) is validated for direct autonomous completion with step 5, countdown byte +0x01 = 1, threshold 35, and object 1 stopping at (50,80) while object 0 is at (80,80).
  • 0x54 (start_random_motion) has a property-style QEMU probe: the object must render exactly at some valid final position. The recorded run ended at (140,112).
  • 0x4e (clear_object_field_22_and_global) has a focused QEMU probe for its visible mode-byte effect: after 0x54 starts random motion, 0x4e clears object byte +0x22, and the object remains at (60,80).
  • Object-object collision helper 0x4719 is documented: moving-object bit 0x0200 skips the test, candidates need active/update flags, equal grouping byte +0x02 is skipped, then horizontal overlap and Y equality/crossing are tested. A follow-up QEMU probe validates that action 0x44 clears this skip bit and restores the default collision stop.
  • Horizon-like placement is QEMU-validated for [0x012d] = 100: with bit 0x0008 clear, an object placed at baseline 80 clamps to 101; action 0x3d exempts the object and action 0x3e restores the clamp.
  • Fixed-priority clearing is QEMU-validated: after 0x36 fixes an object’s priority/control byte to 5, action 0x38 clears bit 0x0004; at baseline 80, the object derives priority 7 and draws over a control-6 background.
  • Control/priority acceptance bits now have focused QEMU probes. 0x58 and 0x59 set/clear bit 0x0002, bypassing or restoring a rectangle-boundary crossing stop. 0x40 and 0x41 set bits 0x0100 and 0x0800, blocking movement over full control-class-2 or control-class-3 pictures for a priority-14 object; 0x42 clears those bits and restores movement.
  • Approach stuck recovery in 0x0b36 is source-backed: when bit 0x4000 reports no movement, the helper chooses a random nonzero direction and stores a retry delay in +0x29.
  • Object frame-cycling is now modeled from static disassembly: 0x4c seeds timer bytes +0x1f/+0x20, bit 0x0020 enables the countdown, and code.object.advance_frame_by_mode interprets modes 0..3 as forward loop, forward-to-last completion, backward completion, and backward loop. QEMU batch frame_timer_001 validates visible mode-1 advancement and the 0x46/0x47 bit gate; batch frame_timer_modes_002 validates visible modes 0, 2, and 3 for actions 0x48, 0x4b, and 0x4a.
  • Object update-list partitioning is now source- and QEMU-backed. Bit 0x0010 selects root 0x16ff versus root 0x1703; actions 0x3a and 0x3b move active objects between those partitions, and 0x3c performs a global list-refresh pass. QEMU batch object_root_partition_004 validates the visible draw-order effect with overlapping view-11 objects.
  • Object bit 0x2000 is source- and QEMU-backed as the suppressor for automatic direction-based group selection in code.object.frame_timer_update. QEMU batch object_bit_2000_002 validates that clearing the bit with 0x2e lets direction 6 select view 4 group 1, while setting it with 0x2d keeps the same object on group 0. Follow-up batch object_bit_2000_004 validates the two/three-group table, sentinel group value 4, countdown-to-1 timing, and the exact +0x01 == 1 gate.
  • The top-level cycle is source-mapped at code.engine.main_cycle (0x0150): a pre-logic motion/boundary pass at 0x0644 runs before logic 0, while code.object.frame_timer_update (0x0563) runs after logic 0 unless byte [0x1757] is nonzero.
  • Rectangle-boundary actions 0x5a and 0x5b now have QEMU-backed movement evidence. Existing rectangle-boundary probes validate 0x5a; batch rect_bounds_clear_001 validates that 0x5b clears the bounds and restores target arrival.

Confirmed Graphics and View Findings

  • Object overlay probes have a QEMU-backed 22-case batch in build/object-overlay-probes/batches/view_cel_selection_002.json; the batch matched with 22 matches, 0 mismatches, and 0 errors.
  • Picture renderer parity is much broader now: real-picture snapshot batches cover pictures 1/45, the broad 8-picture preset, and all 74 valid local SQ2 picture resources. The chunked timed polling carousel also matched all 74 valid local SQ2 picture resources across five engine launches.
  • The active compatibility suite’s broad picture carousel matches the 8-picture preset from one engine process, and the view/object stress carousel matches 19 base-plus-stress view/cel cases.
  • Source-backed picture edge tests now include raw command-looking operands for 0xf0, 0xf2, and 0xf9, command-byte resume for coordinate/list readers, byte-width diagonal-line accumulators, lower-right pattern linear writes, pattern channel masks, seed-fill target-channel behavior, and relative-line underflow for 0xf7.
  • View 11 group/frame selection has been validated beyond the first cel: group 0 frame 1, group 1 frame 0, and group 1 frame 1 all match the local renderer.
  • Existing docs cover view resource layout, selected group/frame pointers, transparent-color behavior, bit-0x80 orientation rewriting, priority/control draw gating, top/right/bottom placement adjustments, and persistent object-table drawing.

Useful Files

  • spec/src/README.md: entry point for the independent behavioral specification.
  • spec/src/resource_containers.md: normative v2/v3 container and expansion contract.
  • spec/src/runtime_state.md: normative portable state and cycle model.
  • spec/src/logic_bytecode.md: complete normative condition/action catalog.
  • spec/src/picture_resources.md: picture commands and exact raster rules.
  • spec/src/view_resources.md: view/cel format, mirroring, and composition.
  • spec/src/object_behavior.md: lifecycle, animation, movement, collision, control acceptance, and draw ordering.
  • spec/src/input_text_and_menus.md: parser, events, text state, inventory, and menus.
  • spec/src/session_and_persistence.md: room transitions, resource replay, save framing, restore, and restart.
  • spec/src/sound.md: normative sound payload, scheduling, and output boundary.
  • docs/src/logic_bytecode.md: main opcode and interpreter bytecode reference.
  • docs/src/logic_opcode_evidence.md: generated opcode evidence matrix with QEMU/source-backed/reserved status for every action and condition row.
  • docs/src/logic_resources.md: logic resource format and decoded script notes.
  • docs/src/graphics_object_pipeline.md: object records, motion, view drawing, and update pipeline.
  • docs/src/sound_and_audio.md: sound resource format, playback scheduling, completion flags, and driver output boundary.
  • docs/src/compatibility_testing.md: QEMU and Python test commands.
  • docs/src/clean_room_executable_notes.md: chronological command/evidence log.
  • docs/src/symbolic_labels.md: symbolic address map for comparing interpreter versions.
  • tools/disassemble_logic.py: local logic bytecode disassembler.
  • tools/game_census.py: read-only multi-game resource/version/layout census for explicit local game directories.
  • tools/resource_reference_audit.py: compares immediate script-visible resource references with the complete readable set for explicit local game directories, including absent and malformed entries.
  • tools/compare_interpreter_tables.py: detects and compares dispatch-table contracts and normalized handler-entry shapes across two explicit local interpreter inputs.
  • tools/v3_object_encoding_probe.py: builds copied unchanged/XOR-encoded inventory-metadata fixtures and can compare them in one QEMU snapshot run.
  • tools/logic_interpreter_probe.py: QEMU logic-interpreter compatibility harness for small bytecode behavior probes.
  • tools/qemu_snapshot.py: shared snapshot runner; fixtures can now request post-launch keystrokes for deterministic prompt/message probes.
  • tools/logic_opcode_evidence.py: generator for the opcode evidence matrix.
  • tools/object_movement_probe.py: QEMU movement/motion compatibility harness.
  • tools/object_overlay_probe.py: QEMU object/view drawing compatibility harness.

Immediate Next Work

  1. Convert the fresh MH1/MH2 static winning-route specifications into deterministic original-interpreter replays. The terminal predicates and formerly missing late-game chains are now mapped; exact movement, report answers, and arcade/control inputs remain.
  2. Continue v3 and other cross-version probes from source-mapped deltas only when the portable specification still has an observable ambiguity.
  3. Keep source-first renderer work going only when disassembly reveals a concrete valid-stream edge. Use QEMU as confirmation or regression coverage, not as the primary discovery method.
  4. Keep tools/compatibility_suite.py current as new local/QEMU evidence is promoted. Re-run --include-qemu-smoke or --include-qemu-broad when the manifest changes.
  5. Continue assigning symbolic labels for helpers, globals, dispatch tables, and overlay entries so later interpreter versions can be compared by role rather than by absolute address.
  6. Apply the cross-version workflow to the additional local interpreter/game inputs when a selected version can add or refine an observable spec variant.

Deferred Or Conditional Work

The remaining open rows in PROGRESS.md are mostly conditional:

  • Most mapped cross-version work is no longer blocked. Replacement MH1/MH2 inputs close their resource gaps, and fresh static route analyses now cover their complete valid late-game scripts. Whole-game replay claims require deterministic input capture rather than additional source files. Other work should be prioritized by the value of an observed version difference to the behavioral spec.
  • Non-EGA display/input paths are outside the current full-EGA compatibility target unless another local interpreter version requires them.
  • Menu arrow navigation and a few UI paths would benefit from direct event injection or a more precise keyboard harness; the source model is currently stronger than the QEMU input path for those details.
  • Analog sound synthesis is outside the interpreter boundary currently being specified; the useful model is the source-backed scheduler and port-output behavior.
  • Out-of-memory, invalid-path, and other error UI cases should be added only if they become necessary for the final compatibility suite.

Use PROGRESS.md as the completion dashboard and spec/src/ as the normative source. Use docs/src/clean_room_executable_notes.md when the exact evidence trail or command history is needed.

Progress Log

This chapter records user-facing progress updates from the assistant, with the concrete action or result behind each update. It complements clean_room_executable_notes.md: the evidence log records commands, observations, offsets, and corrections, while this chapter preserves the narrative trail of what was reported while the work was happening.

Exact progress-update logging starts with the synthetic picture fuzzing work below. Earlier project work is documented in detail in Clean-Room Executable Notes, but the exact wording of every earlier transient assistant update is not fully available in the current thread context, so this log does not invent those verbatim messages.

Prior Work Summary

Before this progress log was created, the project had already:

  • Established the mdBook documentation layout under docs/.
  • Built clean-room notes for the loader, executable, logic interpreter, resource formats, graphics/object pipeline, and symbolic labels.
  • Set up a bootable DOS 6.22 QEMU hard disk image at build/dos622/dos622.img.
  • Added local graphics/rendering helpers under tools/.
  • Built QEMU fixture support for custom LOGIC.0 scripts.
  • Validated original-engine output against local Python rendering for:
    • picture 45;
    • picture 1 with view 11/group 0/frame 0;
    • picture 1 with view 0/group 1/frame 0, including the bit-0x80 row-orientation rewrite.

The detailed command evidence for those milestones is in docs/src/clean_room_executable_notes.md.

Synthetic Picture Fuzzing Framework

User request:

Write a fuzz testing framework that generates a large set of synthetic picture resources, including some with invalid commands/data, and use that to compare the behaviour of the original engine with the python code you’ve written. I want to capture edge cases so we can model the semantics with a high degree of accuracy

Progress updates and outcomes:

  1. Reported: “I’ll build this as a reusable harness, not a one-off pile of samples: synthetic picture payload generation, fixture construction that swaps in a custom picture resource, Python rendering with error capture, and QEMU capture/compare hooks. I’ll start by reading the current fixture/rendering tools so the new code plugs into the existing path cleanly.”

    Action/result: Read tools/qemu_fixture.py, tools/compare_picture_capture.py, tools/agi_graphics.py, and the current test list.

  2. Reported: “I’ve got the shape now. The important design point is to patch PICDIR and LOGDIR together inside a copied fixture, with VOL.3 holding both the custom LOGIC.0 and the synthetic picture payload. That lets the original engine run synthetic pictures without touching the real SQ2 files.”

    Action/result: Read tests/test_qemu_fixture.py, AGENTS.md, and docs/src/SUMMARY.md to align the fixture changes with existing tests and docs.

  3. Reported: “I’m going to extend the fixture builder first: one generic directory-entry patcher, then a synthetic-picture fixture that writes LOGIC.0 and the fuzzed picture into the same VOL.3. That keeps the fuzz runner small and makes the patching testable.”

    Action/result: Updated tools/qemu_fixture.py with patch_dir_entry(), build_synthetic_picture_fixture(), and a synthetic-picture CLI command.

  4. Reported: “The synthetic fixture builder is in place. Next I’m adding the fuzz corpus tool: it will generate deterministic valid and invalid picture payloads, render each with the Python model, write manifests/PPMs, build QEMU fixtures, and compare captures back to a selected fuzz case.”

    Action/result: Added tools/picture_fuzz.py with deterministic base cases, random case generation, corpus manifests, Python render hashing, fixture building, QEMU execution, and capture comparison.

  5. Reported: “The corpus tool is added. I’m adding tests around the new pieces now: directory entry patching, synthetic fixture layout, deterministic corpus generation, and a synthetic scaled-capture comparison path that doesn’t need QEMU.”

    Action/result: Expanded tests/test_qemu_fixture.py for generic directory patching and synthetic fixture layout.

  6. Reported: “The fixture tests are patched. I’m adding a dedicated fuzz-tool test file next, including a scaled fake QEMU screenshot so the compare logic is exercised without booting DOS during unit tests.”

    Action/result: Added tests/test_picture_fuzz.py.

  7. Reported: “Tests are in place. I’m running them now; if the generator exposes any rough edges, I’ll tighten it before doing the QEMU sample run.”

    Action/result: Ran python3 -B -m unittest discover -s tests, which passed 33 tests. Generated an initial 79-case corpus from 15 curated cases plus 64 random cases.

  8. Reported: “The unit tests pass, and a 79-case corpus is generated from 15 curated cases plus 64 deterministic random ones. I’m going to use the new QEMU runner on a tiny visual-point case first, because that validates the end-to-end plumbing without risking an unsafe malformed-resource hang.”

    Action/result: Ran the new QEMU runner on base_003_visual_point; QEMU exited early with a broken pipe because the harness hid the emulator output.

  9. Reported: “The first automated QEMU run exited early, and the runner had hidden QEMU’s output too aggressively. I’m adjusting it to surface early-exit diagnostics so failures are evidence instead of a mystery.”

    Action/result: Updated tools/picture_fuzz.py to detect early QEMU exit before monitor input and to retain QEMU output on nonzero exit.

  10. Reported: “The runner now checks for early QEMU exit before each monitor keystroke and preserves monitor output on nonzero exit. I’m rerunning the same smoke case.”

    Action/result: The rerun still failed early and revealed that another diagnostic path was needed.

  11. Reported: “QEMU is exiting with status 1 before DOS input. I still need the actual QEMU message, so I’m adding one more diagnostic path to read the monitor output when the early-exit check trips.”

    Action/result: Added exception wrapping that includes QEMU monitor output.

  12. Reported: “One more smoke run; now if QEMU bails, the runner should show why.”

    Action/result: QEMU reported Failed to bind socket: Operation not permitted, showing that Python-launched QEMU needed escalation for the VNC socket.

  13. Reported: “The runner is fine; the sandbox blocked QEMU’s VNC bind when launched from Python. I’m rerunning that same framework command with escalation so the harness can actually start the emulator.”

    Action/result: With escalation, base_003_visual_point matched the original engine with 0 mismatches out of 26,880 logical pixels.

  14. Reported: “The first original-engine fuzz comparison matched. I’m going to expand the corpus to a genuinely large deterministic set now, then run a small representative QEMU slice across scanner, line, fill, pattern, and invalid-but-safe cases.”

    Action/result: Generated a 1,039-case corpus from curated cases plus 1,024 deterministic random cases. The generator marked 1,037 cases safe for QEMU.

  15. Reported: “The corpus now has 1,039 synthetic picture resources, including 1,037 marked safe for automated QEMU. I’m running five more original-engine comparisons against curated edge cases.”

    Action/result: Ran QEMU for base_002_unknown_commands; it matched with 0 mismatches.

  16. Reported: “Unknown commands 0xfb..0xfe matched the current Python behavior for that fixture: no visible changes before 0xff. Continuing through the line/clamp and fill/pattern cases.”

    Action/result: Ran QEMU for base_004_clamped_absolute; it produced 312 mismatches, the first fuzz-discovered semantics gap in this pass.

  17. Reported: “Good, the clamped-coordinate case found a real mismatch: 312 logical pixels. That’s exactly the kind of edge this harness is meant to expose, so I’m keeping that as evidence and continuing the sample sweep before drilling in.”

    Action/result: Ran QEMU for the visual/control seed-fill case; it matched with 0 mismatches.

  18. Reported: “The combined visual/control fill case matched, which reinforces the seed-fill channel-priority model. Next I’m checking pseudo-random pattern plotting and the truncated coordinate pair case.”

    Action/result: Ran QEMU for the pseudo-random pattern case and the truncated coordinate-pair case; both matched with 0 mismatches.

  19. Reported: “The pattern and truncated-pair cases matched; the only mismatch in the curated slice is the clamp edge case using coordinate byte 0xef. I’m adding mismatch bounding boxes and sample pixels to the comparison output so we can immediately see what kind of semantic gap a fuzz case found.”

    Action/result: Extended CaptureComparison in tools/picture_fuzz.py with mismatch_bbox and sample mismatch tuples.

  20. Reported: “The comparison output now includes a compact mismatch bbox and sample pixel tuples. I’m rerunning the comparison against the already captured clamp case, no new QEMU boot needed.”

    Action/result: The captured clamp case reported mismatch bbox (0, 0, 149, 155) and sample pixels showing displaced diagonal-line output.

  21. Reported: “The mismatch samples show this is probably not just ‘coordinate clamp’; it is a steep diagonal line edge. The original is drawing a visible line displaced from the Python line for a (159,167) -> (0,0) style segment, so I’m going back to the line helper with this new targeted case.”

    Action/result: Re-disassembled around code.picture.draw_line (0x66e1) and compared captured versus expected colored point sets. Both had 168 colored points ending at (159,167), but the original began near (25,0) while Python began at (0,0).

  22. Reported: “The mismatch is very specific: both outputs draw 168 colored pixels ending at (159,167), but the original starts the top of the line around (25,0) while Python starts at (0,0). I’m checking the fixture bytes to make sure this is a real semantic signal and not a stale DOS-copy issue.”

    Action/result: Inspected VOL.3, PICDIR, and the corpus case metadata. The fixture correctly contained payload f0 02 f6 ef ef 00 00 ff and patched PICDIR[0] to the synthetic picture in VOL.3.

Gold Rush / SQ2 Shared Delta Source Pass

User request:

Update PROGRESS.md to include these items, then proceed.

Progress updates and outcomes:

  1. Reported: “Picking this back up at the documentation point. I’ve already got the source-level findings for the first input/text cluster, so I’m going to fold those into PROGRESS.md, the version notes, symbolic labels, and the clean-room log before moving to the next cluster.”

    Action/result: Re-read PROGRESS.md, docs/src/versions.md, docs/src/symbolic_labels.md, and docs/src/progress_log.md.

  2. Reported: “I’m going to keep this pass source-first as requested. The plan is to use the changed opcode entry addresses as anchors, inspect the corresponding disassembly in both interpreters, and only mark behavior as done when it is backed by those snippets rather than a black-box run.”

    Action/result: Re-read tools/compare_gr_sq2_static.py, the end of docs/src/clean_room_executable_notes.md, and the current GR/SQ2 docs references for changed actions.

  3. Reported: “Tiny tooling snag: importing the logic disassembler directly needs the tools/ path on PYTHONPATH. I’m rerunning with that local path added; no project behavior involved.”

    Action/result: Retried a local table-print helper with tools/ added to sys.path.

  4. Reported: “That script has import-time argument handling, so I’m leaving it alone rather than leaning on it as a library. I’ll print the table addresses without importing it.”

    Action/result: Printed SQ2 and GR action-table entries for the changed shared opcodes using tools.compare_gr_sq2_static.load_table.

  5. Reported: “For the event/key/menu cluster, 0xad, 0xb1, and 0xb5 already have a good source pass in the notes; the missing piece is mostly 0x79, where the static report says the table capacity changed. I’m reading both key-map handlers and their consumers now.”

    Action/result: Compared SQ2 0x4c3d and GR 0x4e98, confirming that GR scans 0x31 key-map slots where SQ2 scans 0x27.

  6. Reported: “0x79 is now clear: the write logic is otherwise the same, but GR scans more four-byte key-map slots before giving up. I’m moving to the room/inventory/save/restart/object-state cluster, where the deltas are small entry-path changes but touch bigger subsystems.”

    Action/result: Began reading handlers for 0x12, 0x7c, 0x7d, 0x80, and 0x84.

  7. Reported: “The first room-switch read landed in a neighboring helper, so I’m tightening the address search around 0x175c/0x19d4 and the remaining changed handlers. The disassembly is linear, so exact address anchors matter here.”

    Action/result: Switched from nearby sed ranges to exact-offset local disassembly for action handlers.

  8. Reported: “One caveat surfaced: linear ndisasm sometimes swallows handler-entry bytes as prior inline data. For those entries I’m switching to exact-offset disassembly, which is the safer way to read handlers that sit beside tables.”

    Action/result: Exact-offset disassembly recovered the correct SQ2 0x12 handler at image 0x175c.

  9. Reported: “There’s a genuinely version-specific Gold Rush quirk in 0x12: its entry calls a tiny local helper that remaps immediate room bytes 0x7e..0x80 to 0x49 before entering the normal room-switch path. I’m checking whether the local GR scripts actually use that range before I describe the risk level.”

    Action/result: Disassembled local GR logic resources and found switch_room_like(#126), #127, and #128, proving the remap is live in this data set.

  10. Reported: “The room remap is not just dead code: the decoded local GR scripts do contain switch_room_like(#126), #127, and #128, and the GR handler redirects those immediates to room #73. I’ll record that as an observed v3/GR-specific compatibility detail rather than a portable rule.”

    Action/result: Added the remap to the version/evidence notes.

  11. Reported: “For inventory/save/restart, most differences are relocated state addresses plus a couple of extra guard/cleanup writes. The save action has an extra XOR pass over the inventory/object chunk before and after the file writes; I’m tracking that separately because it can matter for v3 save-file compatibility.”

    Action/result: Read GR helper 0x07bc, documenting its repeating-key XOR behavior over a caller-supplied byte range.

  12. Reported: “The object/motion deltas are nicely bounded: GR gates part of direction-to-loop selection on flag 0x14, treats exactly-four-loop views differently from SQ2, and lets motion mode 4 fall into the same target-direction path as mode 3. Those are now concrete enough to document as source-backed v3 deltas.”

    Action/result: Read frame-timer and motion-dispatch routines and decoded the GR motion-mode jump table.

  13. Reported: “I have enough source evidence for the planned queue now. I’m switching into documentation/tooling updates: the version ledger gets the portable summary, the evidence log gets the offsets and commands, and the static report notes get sharper names for the same deltas.”

    Action/result: Updated tools/compare_gr_sq2_static.py changed-action notes.

  14. Reported: “The progress tracker is the next file: I’m changing the queue from ‘do these four passes’ to ‘source pass complete; remaining work is targeted behavioral fixtures and more version samples.’”

    Action/result: Started updating PROGRESS.md.

  15. Reported: “The patch missed on a line wrap; nothing changed there yet. I’m applying the tracker update in smaller chunks so it lands cleanly.”

    Action/result: Successfully updated the logic-action comparison, object-runtime comparison, and highest-value remaining-work sections in PROGRESS.md.

  16. Reported: “PROGRESS.md now reflects the planned items and the source pass status. I’m adding the same findings to the Versions chapter so the differences are visible in the mdBook, not only in the tracker.”

    Action/result: Added shared-action and object/motion delta tables to docs/src/versions.md.

  17. Reported: “The logic bytecode chapter gets a compact cross-version note next. This keeps the opcode catalog portable: SQ2 behavior remains the baseline rows, and GR differences are called out as version associations.”

    Action/result: Added a shared-opcode GR/SQ2 note to docs/src/logic_bytecode.md.

  18. Reported: “I’m also touching the runtime model briefly so a future implementer sees the GR motion-mode caveat next to the generic motion lifecycle, not only in the version ledger.”

    Action/result: Added a GR-specific motion note to docs/src/runtime_model.md.

  19. Reported: “Now the symbolic label map: I’m adding GR address associations for the handlers I just used, and tightening the existing object/motion notes so later version comparisons can match by label instead of address.”

    Action/result: Added GR input/text, room, inventory, save, restart, and data-global associations to docs/src/symbolic_labels.md.

  20. Reported: “Next is the evidence log. I’m appending the exact source pass: commands, handler addresses, and the conclusions, with uncertainty separated from observed behavior.”

    Action/result: Added a new clean-room evidence section to docs/src/clean_room_executable_notes.md.

  21. Reported: “The evidence log is updated. I’m adding the user-facing progress updates to the progress log now, since the project instructions explicitly keep that historical trail.”

    Action/result: Added this progress-log section.

  22. Reported: “To isolate whether this is the line algorithm or the coordinate clamp, I’m adding an exact-boundary version of the same test using (0x9f,0xa7) -> (0,0). If that matches, the gap is in out-of-range coordinate handling; if it mismatches the same way, the diagonal line model needs correction.”

    Action/result: Added a new curated base case for exact-edge absolute-line drawing and renumbered subsequent curated base cases.

  23. User requested: “keep a log of every intermediate step that you’re reporting to me. it’s super interesting and i want to keep track of it for historical record” and “including everything you’ve done so far”.

    Action/result: Created this progress log and updated the project rules so future progress updates are logged as part of the clean-room history.

  24. Reported: “The progress log now contains the current fuzzing session from the first update through the exact-edge test I was adding when you asked. I’m wiring it into the book and adding a standing instruction so I keep appending to it as I go.”

    Action/result: Added progress_log.md to docs/src/SUMMARY.md, linked it from docs/src/README.md, and updated AGENTS.md with an instruction to preserve user-facing progress updates.

  25. Reported: “I’ll keep that log live from here on. Next I’m regenerating the corpus after the new exact-edge case and running the exact-edge QEMU comparison to separate coordinate clamping from diagonal line drawing.”

    Action/result: Regenerated the corpus after adding the exact-edge case. The corpus now has 1,040 cases, with 1,038 marked safe for QEMU. The local unit suite still passed with 33 tests, and mdbook build docs succeeded.

  26. Reported: “The regenerated corpus has 1,040 cases now, with 1,038 marked safe for QEMU; tests and mdBook still build cleanly. I’m running the exact-edge fixture through the original engine now.”

    Action/result: QEMU comparison for base_005_exact_edge_absolute produced the same 312 mismatches and mismatch bbox (0, 0, 149, 155) as base_004_clamped_absolute.

  27. Reported: “The exact-boundary case mismatched identically, so the gap is in the diagonal line helper, not out-of-range coordinate clamping. I’m updating the log, then I’ll correct the Python line algorithm against the disassembly and rerun the two line fuzz cases.”

    Action/result: Began revisiting the disassembled line helper at 0x66e1.

  28. Reported: “The likely bug is subtle but promising: the executable keeps the line accumulators in 8-bit registers, so long steep diagonals can wrap before the compare. My Python model used unbounded integers, which is fine for short lines but wrong at the screen-scale edge.”

    Action/result: Simulated the disassembled algorithm with 8-bit accumulator wrap. The wrapped model reproduced the QEMU edge-case shape: at y=0 the exact-edge line lands at x=25, not x=0.

  29. Reported: “A quick simulation of the disassembled 8-bit accumulator reproduces the original-engine edge case exactly: at y=0 the line lands at x=25, not x=0. I’m applying that change to the renderer and adding a regression test so this edge stays pinned.”

    Action/result: Updated PictureRenderer.draw_line() to wrap diagonal line accumulators to 8 bits after addition and subtraction.

  30. Reported: “The renderer now uses byte-width accumulator arithmetic for diagonal lines. I’m adding the focused test before rerunning the corpus and QEMU comparisons.”

    Action/result: Located the existing split absolute-line and relative-line tests in tests/test_graphics_rendering.py.

  31. Reported: “The existing line tests were split into separate absolute and relative cases, so I’m inserting the long-edge regression beside those instead of replacing anything.”

    Action/result: Added test_long_diagonal_uses_byte_width_line_accumulators to tests/test_graphics_rendering.py.

  32. Reported: “The long-edge regression is in. I’m running the unit suite now; if hashes changed in real SQ2 pictures, I’ll treat that as a signal and verify against the existing QEMU captures before updating expected values.”

    Action/result: The local unit suite passed with 34 tests.

  33. Reported: “The unit suite passes with 34 tests, so no existing SQ2 picture hash moved. I’m regenerating the corpus with the corrected renderer metadata and then comparing the two edge-line captures again.”

    Action/result: Regenerated the corpus. The corrected renderer matched the previously captured base_004_clamped_absolute QEMU output with 0 mismatches. The exact-edge recompare failed because the regenerated corpus did not contain the expected base_005_exact_edge_absolute metadata path, so the base-case list needed inspection.

  34. Reported: “The corrected renderer now matches the previous clamped-edge QEMU capture with zero mismatches. The exact-edge recompare failed because regenerating the corpus wiped the captured fixture metadata path before that case existed in the manifest, so I’m checking the base-case list and will rebuild/rerun that exact-edge capture cleanly.”

    Action/result: Verified that base_005_exact_edge_absolute is present in the base-case list and regenerated corpus. The failed compare was caused by running corpus regeneration and capture comparison in parallel.

  35. Reported: “The exact-edge case is present; the failed compare was a race from running corpus regeneration and comparison in parallel. I’m rerunning the exact-edge QEMU comparison now, sequentially.”

    Action/result: Reran base_005_exact_edge_absolute through QEMU. It matched the corrected renderer with 0 mismatches.

  36. Reported: “The exact-edge QEMU case now matches too. I’m rerunning the representative fuzz slice under the corrected renderer using the new case IDs, then I’ll document the framework, the line-accumulator correction, and the current fuzz results.”

    Action/result: Attempted to run three QEMU fuzz cases in parallel. This was rejected as a bad probe: QEMU’s VNC socket was blocked without escalation, and concurrent mtools/QEMU fixture copying caused a DOS-image directory-copy failure for one case.

  37. Reported: “That parallel QEMU slice was the wrong move: QEMU needs the VNC socket and the DOS image copy steps should be serialized. I’m recording that as a failed probe, then I’ll run the remaining cases one at a time with the harness command approved/escalated as needed.”

    Action/result: Updated copy_fixture_to_dos() to surface mmd failures unless the directory already exists.

  38. Reported: “The harness now surfaces mmd failures unless they’re the harmless ‘already exists’ case. I’m running the remaining representative QEMU cases sequentially now.”

    Action/result: The user interrupted the run and then granted full access. No QEMU or fuzz harness process was left running. Inspection showed that FZFILL2 already existed in the DOS image and that mmd returned a nonzero status with no diagnostic text for an existing directory. A probe directory FZNEW99 was created while checking mtools behavior.

  39. Reported: “Full access received. I’ll pick up from the aborted QEMU run, first checking whether any emulator process or partial DOS-image copy was left behind, then I’ll fix the mtools error reporting and continue the serialized fuzz comparisons.”

    Action/result: Verified no QEMU process was running and identified the quiet mmd existing-directory case.

  40. Reported: “No QEMU process is left behind. The interrupted run stopped before launching QEMU; the remaining issue is mtools returning a nonzero mmd status with no useful message, likely because the DOS directory already exists. I’m checking the DOS root and then I’ll make the copy step robust by verifying the directory exists when mmd is quiet.”

    Action/result: Confirmed FZFILL2 already existed and mmd returned status 1 with no output for that directory.

  41. Reported: “Confirmed: FZFILL2 already exists and mmd reports that with an empty error stream. I also created FZNEW99 as a harmless root-directory probe while checking mtools behavior. I’m patching the harness to validate existing directories explicitly instead of trusting the error text.”

    Action/result: Updated copy_fixture_to_dos() so a quiet mmd failure is accepted only if mdir can read the requested directory.

  42. Reported: “The harness now treats a quiet mmd failure as okay only if mdir can read the target directory. I’m logging that, then I’ll run the remaining QEMU cases sequentially with fresh directory names to keep the DOS image tidy-ish.”

    Action/result: Ran base_010_visual_control_fill through QEMU in DOS directory FZFIL3; it matched with 0 mismatches.

  43. Reported: “The visual/control fill fuzz case still matches under the corrected renderer. I’m moving on to the pseudo-random pattern case, also serialized.”

    Action/result: Ran base_012_pattern_random through QEMU in DOS directory FZPAT3; it matched with 0 mismatches.

  44. Reported: “The pseudo-random pattern case matches too. I’m running the safe truncated-coordinate case now; this one checks that an incomplete pair terminates the command without visible garbage.”

    Action/result: Ran base_014_truncated_pair through QEMU in DOS directory FZTRN3; it matched with 0 mismatches.

  45. Reported: “The representative fuzz slice is green now after the line-accumulator fix. I’m updating the spec/docs with the fuzz framework, the QEMU results, and the 8-bit accumulator correction, then I’ll run final verification.”

    Action/result: Updated graphics_object_pipeline.md, compatibility_testing.md, clean_room_executable_notes.md, and AGENTS.md. The docs now describe the fuzz framework, the representative QEMU pass, the failed probes, and the 8-bit line accumulator correction.

  46. Reported: “The evidence log now has the fuzz pass with command-level detail and the failed probes. I’m adding a short AGENTS.md note so future agents know how to use the fuzz harness and why QEMU fuzz runs should stay serialized.”

    Action/result: Added fuzz-harness command notes to AGENTS.md.

  47. Reported: “Docs and AGENTS are updated. I’m running the full local verification now: tests, mdBook build, whitespace check, and a final compare on the edge-line captures.”

    Action/result: python3 -B -m unittest discover -s tests passed with 34 tests; mdbook build docs succeeded; git diff --check succeeded; and base_004_clamped_absolute compared with 0 mismatches.

  48. Reported: “Verification is clean so far. I’m also checking the exact-edge capture and the three representative post-fix cases from their saved outputs, then I’ll summarize the final state.”

    Action/result: Saved captures for base_005_exact_edge_absolute, base_010_visual_control_fill, base_012_pattern_random, and base_014_truncated_pair all compared with 0 mismatches.

  49. Reported: “All saved fuzz captures compare cleanly now. I’m doing one last status/diff overview and updating the progress log entry from “pending” to the actual verification results.”

    Action/result: git status --short showed the expected modified tracked docs plus untracked generated/tool/test files. The saved fuzz captures present under build/picture-fuzz/fixtures/ include the representative scanner, line, fill, pattern, and truncated-data cases.

  50. Reported: “The diff overview only shows tracked-file deltas, so the new fuzz/test/tool files are still listed as untracked in git status; that matches the repo’s current uncommitted state. I’m closing the progress log with the final verification results now.”

    Action/result: This entry completes the current fuzzing pass log.

  51. Reported: “That boundary is important, and I’m going to write it down in the project docs so the fuzz harness treats ‘malformed but bounded’ differently from ‘malformed enough to make the interpreter run off into unrelated memory.’ The former can teach us parser edge cases; the latter belongs in a security notes bucket, not the compatibility spec.”

    Action/result: Updated compatibility_testing.md, clean_room_executable_notes.md, and AGENTS.md to define safe_for_qemu: false cases as out of scope for the behavioral model. Updated tools/picture_fuzz.py run-qemu to refuse those cases before launching QEMU, and added a regression test for that guard. Verified with python3 -B -m unittest tests.test_picture_fuzz, mdbook build docs, and git diff --check; a follow-up full run of python3 -B -m unittest discover -s tests passed with 35 tests.

  52. Reported: “I’ll commit the source, tests, and docs changes, while leaving generated build outputs and installer/disk-image artifacts out of the commit unless the repo is already tracking them. First I’m checking the ignore rules and the exact file list so the commit stays tidy.”

    Action/result: git status --short --untracked-files=all showed a large untracked generated set under build/ plus DOS installer images under 002962_ms_dos_622/. AGENTS.md already documents build/ as disposable generated output, so these artifacts were not staged.

  53. Reported: “The noisy untracked set is almost entirely generated: DOS installer images, the DOS hard disk image, screenshots, rendered PPM/PNG files, and copied SQ2 fixture directories. I’m staging only the project files that define the tooling, tests, and documentation, then I’ll review the staged diff before committing.”

    Action/result: Staged 18 source, documentation, and test files. The staged diff stat reported 3,571 insertions and 11 deletions, with no generated build/ or 002962_ms_dos_622/ artifacts included.

  54. Reported: “The progress log now records what I staged and why. I’m staging that log addition too, then running the same verification set before the commit.”

    Action/result: git diff --cached --check passed, python3 -B -m unittest discover -s tests passed with 35 tests, and mdbook build docs succeeded.

  55. Reported: “Everything staged looks right: 18 files, with generated artifacts still untracked. I’m creating the commit now.”

    Action/result: Created commit 6d55b73 with message Document graphics decoding and fuzz tests.

  56. Reported: “I’ll take the first step from that list: make the picture fuzz harness capable of running a bounded serial batch through QEMU, with machine-readable results and enough summary data to drive the next semantics fixes. I’m going to inspect the current harness shape, then I’ll add the batch path and document the workflow.”

    Action/result: Inspected tools/picture_fuzz.py, tests/test_picture_fuzz.py, the current compatibility-testing docs, and the working tree state. The only untracked directories before this pass were generated local artifacts under build/ and 002962_ms_dos_622/.

  57. Reported: “I’ve got the shape now: batch-qemu will select only safe_for_qemu cases from the manifest, optionally filter by case id/category/max count, run them one at a time, and write a JSON report with each case’s DOS directory, capture path, status, mismatch summary, and elapsed time. That should make QEMU a repeatable oracle instead of a one-case-at-a-time ritual.”

    Action/result: Added the batch-qemu command, batch selection helpers, stable DOS directory naming, JSON report writing, and unit tests with a mocked QEMU run.

  58. Reported: “Got it. I’ll keep pushing through the full sequence rather than stopping after the batch harness: first verify and run the batch oracle, then use whatever it exposes to refine picture semantics, and then move the same QEMU-validation pattern toward view/cel drawing and object behavior.”

    Action/result: python3 -B -m unittest tests.test_picture_fuzz passed with 8 tests. Regenerated a smaller working corpus with python3 -B tools/picture_fuzz.py generate --count 64 --seed 4097 --output build/picture-fuzz/corpus --clean, producing 80 cases, 78 marked safe for QEMU. git diff --check passed.

  59. Reported: “I’ve logged the verification and corpus regeneration. The next command may take a couple of minutes because it boots QEMU once per case and deliberately runs serially.”

    Action/result: Ran a 14-case curated safe QEMU batch covering scanner, line, corner, fill, pattern, and bounded invalid cases. The batch wrote build/picture-fuzz/batches/base_curated_001.json and reported 14 matches, 0 mismatches, and 0 errors.

  60. Reported: “The base report is strong: all 14 curated safe cases matched, including scanner, absolute and relative lines, both corner path modes, control-only fill, visual-plus-control fill, both pattern modes, and a bounded truncated pair. I’ve selected the first 16 safe random cases from the regenerated corpus and am running them as the next batch.”

    Action/result: Selected rand_00000 through rand_00015 from build/picture-fuzz/corpus/manifest.json for a random safe QEMU batch.

  61. Reported: “The random QEMU batch is underway. If it comes back green, that gives us confidence in the current picture renderer across mixed command streams; if not, the report should point directly at the next disassembly target.”

    Action/result: Ran python3 -B tools/picture_fuzz.py batch-qemu --case rand_00000 ... --case rand_00015 --dos-prefix FR --output build/picture-fuzz/batches/random_00000_00015.json --boot-wait 5 --draw-wait 8. The report showed 16 matches, 0 mismatches, and 0 errors.

  62. Reported: “The random report confirms matches across line, corner, pattern, fill, and scanner categories. I’m patching the runner to print per-case progress during long batches, and I’m adding targeted base cases for bounded fill barriers and pattern edge placement so the next QEMU pass is less accidental.”

    Action/result: Added per-case progress printing for batch-qemu and added new curated base cases for visual fill inside/outside a box, pattern lower-right edge clamping, rectangular pattern masks, and multiple pseudo-random pattern seeds.

  63. Reported: “The new cases are in and the batch command will now report progress as it runs. I’m regenerating the corpus, running the focused tests, and then I’ll send the new targeted cases through QEMU.”

    Action/result: python3 -B -m unittest tests.test_picture_fuzz passed with 8 tests. Regenerated the working corpus; it now has 85 cases, 83 marked safe for QEMU. git diff --check passed.

  64. Reported: “The streaming status is already paying off: the first targeted fill-box case matched. The batch is now on the outside-box fill case.”

    Action/result: The targeted batch continued; both fill cases matched.

  65. Reported: “Good, the two bounded fill cases matched. The circular pattern edge case produced a small 5-pixel mismatch, so after the batch finishes I’ll drill into that report and the pattern positioning logic.”

    Action/result: The targeted batch finished with 3 matches, 2 mismatches, and 0 errors. base_018_pattern_edge_circle mismatched by 5 pixels and base_019_pattern_edge_rectangle mismatched by 14 pixels; both mismatches appeared at X 0 on lower screen rows.

  66. Reported: “The mismatch is very precise: the original engine plots column 160 from a lower-right pattern mask as the first pixel of the next scanline, while the Python renderer clipped it away. This is a pattern-specific edge behavior, not a general line-coordinate issue, so I’m going to model it in the pattern plot path and lock it with a test.”

    Action/result: Inspected the targeted batch report and confirmed the out-of-bounds pattern column wraps through the linear picture buffer. Added write_cell() for linear buffer writes, changed pattern plotting to call it directly, added a regression test for lower-right rectangular pattern wrap, and documented the behavior in graphics_object_pipeline.md.

  67. Reported: “The pattern-edge fix is in place and documented. I’m running the rendering tests and regenerating the corpus so the Python hashes reflect the corrected pattern behavior before rerunning the targeted QEMU batch.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzz passed with 32 tests. Regenerated the working corpus; it remained at 85 cases, 83 safe for QEMU. git diff --check passed.

  68. Reported: “The targeted rerun is underway with the corrected renderer. The first fill case still matches, and the run is progressing case by case with the new live output.”

    Action/result: The rerun showed base_016, base_017, and the corrected base_018_pattern_edge_circle all matching. The later cases failed with DOS-image Disk full errors rather than renderer mismatches.

  69. Reported: “Host disk space is fine; the ‘Disk full’ is inside the small DOS hard disk image. I also found the cause: rerunning a fixture copied its old qemu_capture.ppm back into DOS, wasting image space. I’m patching the copy step to exclude generated host captures, then I’ll clear old fuzz directories from the DOS image.”

    Action/result: Updated copy_fixture_to_dos() to skip generated .ppm files when copying a fixture into the DOS image.

  70. Reported: “The DOS image is literally at 0 bytes free, and the root is full of generated fuzz directories (FZ*, FB*, FR*, FT*, FU*). I’m removing those disposable directories from the DOS image, leaving the base DOS install and original SQ2/validation fixture directories alone.”

    Action/result: Removed generated fuzz directories from build/dos622/dos622.img. The DOS image root then showed about 56 MB free.

  71. Reported: “The storage cleanup worked; the rerun is progressing normally and the first targeted case still matches. I’m waiting for the pattern edge cases now.”

    Action/result: Reran the targeted fill/pattern batch as targeted_fill_pattern_003.json. All five cases matched with 0 mismatches and 0 errors.

  72. Reported: “The targeted rerun is green: all five fill/pattern cases now match the original engine. I’m updating the docs with the batch harness, the DOS-image storage lesson, and the newly observed pattern linear-wrap behavior before moving on to view/cel validation.”

    Action/result: Updated AGENTS.md, compatibility_testing.md, clean_room_executable_notes.md, and graphics_object_pipeline.md with batch-QEMU usage, current fuzz coverage, DOS-image cleanup notes, and the pattern X-160 linear-write behavior.

  73. Reported: “The current capture comparator can already compose a rendered view over a picture, but it only reports a mismatch count. I’m going to upgrade it to report bounding boxes/samples and add a view_batch.py tool that builds picture-view fixtures, runs them through QEMU serially, and emits JSON reports like the picture fuzz runner.”

    Action/result: Extended picture-capture comparison results with mismatch boxes and samples. Added tools/view_batch.py with base view/object cases, fixture generation, serial QEMU execution, and JSON report writing.

  74. Reported: “The view batch tool is in place. I’m adding a small unit test around its case loading/report path before running it against QEMU, so the batch machinery itself has coverage independent of the emulator.”

    Action/result: Added tests/test_view_batch.py covering base case coverage, JSON case loading, DOS directory naming, and report summary generation.

  75. Reported: “The view-batch unit tests are added. I’m running the full local suite and mdBook now before using QEMU again.”

    Action/result: python3 -B -m unittest discover -s tests passed with 43 tests, mdbook build docs succeeded, and git diff --check passed.

  76. Reported: “The verification result is logged. Starting the six-case view batch now: normal cel, mirrored cel, left/top clipping, and a low-priority object case.”

    Action/result: Ran python3 -B tools/view_batch.py --dos-prefix VB --output build/view-batch/batches/view_base_001.json --boot-wait 5 --draw-wait 8. Five cases matched; view_011_top_clip mismatched in rows 0 through 4.

  77. Reported: “The cached search found an exact match: the original output equals drawing the cel at left=18, baseline_y=4 when the fixture requested left=20, baseline_y=2. In other words, when the computed top is -2, the overlay path shifts left by that negative top and clamps the top to row 0. I’m encoding that as a provisional add_to_pic/overlay placement rule and will rerun the view batch.”

    Action/result: Updated object-frame composition to adjust left and baseline_y when the computed top is negative. Added a regression test and documented the QEMU-observed top-edge adjustment.

  78. Reported: “The top-edge adjustment is modeled and tested locally. I’m running the relevant tests, then I’ll rerun the view batch to verify the QEMU mismatch is gone.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering tests.test_view_batch passed with 29 tests, and git diff --check passed.

  79. Reported: “The test result is logged. Starting the QEMU rerun for the view batch with the corrected top-edge model.”

    Action/result: Reran tools/view_batch.py as build/view-batch/batches/view_base_002.json. All six cases matched with 0 mismatches and 0 errors.

  80. Reported: “The corrected view batch is fully green: all six cases match, including the top-edge placement case. I’m documenting the view-batch harness and the observed object top-edge rule, then I’ll do the full verification pass.”

    Action/result: Updated compatibility_testing.md and clean_room_executable_notes.md with view-batch commands, coverage, results, and the top-edge object placement rule.

  81. Reported: “That’s a great optimization target. I’m going to pause the broader reverse-engineering loop and investigate QEMU host-directory options for DOS specifically; the likely candidate is QEMU’s virtual FAT block device, which should appear to DOS as another drive. If that works, the next harness shape is ‘boot once, run many prebuilt fixture directories from a shared drive.’”

    Action/result: Checked local QEMU version (11.0.2) and help output. DOS cannot use QEMU 9p/virtfs directly without a guest driver, so the practical candidate is a QEMU FAT-backed block device exposed as a secondary DOS disk.

  82. Reported: “QEMU accepts the FAT-backed secondary drive syntax. Now I’m booting DOS with that share attached and sending a DIR D:\ command, then I’ll capture the VGA screen to confirm DOS can actually see the host file.”

    Action/result: Booted QEMU with -drive file=fat:rw:build/qemu-share,format=raw,if=ide,index=1,media=disk. DOS saw the host directory as drive D: with volume label QEMU VVFAT and listed HOST.TXT.

  83. Reported: “The screenshot confirms it: DOS sees the host directory as D: with volume label QEMU VVFAT, and HOST.TXT is visible. Next I’m testing the part that matters for our harness: running an AGI fixture directly from that shared D: drive without copying it into the DOS image.”

    Action/result: Built build/qemu-share/PIC001 and ran it from D:\PIC001 in QEMU. The capture build/qemu-share/from_share_pic001.ppm compared against picture 1 with 0 mismatches.

  84. Reported: “Running from the shared drive works: the AGI fixture on D:\PIC001 produced a capture with 0 mismatches against the local renderer. The remaining question for ‘boot once’ is whether the generated fixture returns to DOS after drawing; I’m testing that by sending another DOS command after the draw and capturing the screen.”

    Action/result: Sending DIR after running SIERRA left the game screen visible. The generated fixture does not return to DOS, so multi-case no-boot runs need loadvm/reset or a different fixture script.

  85. Reported: “A QEMU internal snapshot with fat:rw: fails because writable vvfat does not support live migration/snapshotting. That’s not fatal: for test inputs we only need the host share read-only inside DOS, and we can put the boot disk on a qcow2 overlay that stores VM snapshots. I’m testing that combination next.”

    Action/result: Created build/dos622/dos622-test.qcow2 as a qcow2 overlay over the raw DOS disk. Read-only vvfat as an IDE disk still failed, but read-only vvfat as an IDE CD-ROM allowed savevm.

  86. Reported: “Useful result: fat:... as a read-only IDE disk still fails, but exposing it as a read-only IDE CD-ROM allows QEMU savevm. Now I’m checking whether this DOS install can actually see that CD-ROM-style drive as D:; if not, we can still use snapshots with another share mechanism, but not this one directly.”

    Action/result: DOS reported Invalid drive specification for the CD-ROM-style share because the current image has MSCDEX but no IDE/ATAPI CD-ROM driver configured.

  87. Reported: “There’s MSCDEX, but no IDE/ATAPI CD-ROM driver in the DOS image. A better idea for a read-only, snapshot-compatible host directory is to expose the QEMU FAT share as a floppy, because DOS can see floppy drives without MSCDEX; I’m testing whether QEMU’s vvfat floppy is large enough and visible.”

    Action/result: Plain if=floppy produced invalid media. fat:floppy: was accepted only when the host directory fit FAT12 capacity. A nested full fixture directory appeared empty, and a root-level fixture exposed only a subset of files, making this path unsuitable for full AGI fixtures.

  88. Reported: “The root-level floppy share is visible but not usable for full AGI fixtures: DOS only sees a small subset of the files. So the clean result is: host directory mounting works well as a secondary D: hard disk with fat:rw:...; snapshot-based no-reboot runs need either a DOS CD-ROM driver for read-only vvfat CD-ROM, or a generated qcow2/FAT test disk instead of a live host directory. I’m documenting the exact working and non-working combinations now.”

    Action/result: Updated AGENTS.md and this evidence log with the working fat:rw: command, the successful run from D:, and the limitations around QEMU snapshots, CD-ROM drivers, and floppy-sized vvfat shares.

  89. Reported: “I’ll commit the source, tests, and docs from this pass, leaving the generated QEMU shares, captures, qcow overlay, build/, installer images, and __pycache__ out of the commit. I’m checking the exact staged set first so the commit doesn’t accidentally pick up emulator artifacts.”

    Action/result: git status --short --untracked-files=all showed the expected generated artifacts under build/, the DOS installer images under 002962_ms_dos_622/, and Python cache files. git diff --check passed.

  90. Reported: “The staged set is exactly 12 source/docs/test files; generated artifacts remain untracked. I’m running the full tests, mdBook, and cached diff whitespace check before committing.”

    Action/result: python3 -B -m unittest discover -s tests passed with 44 tests, mdbook build docs succeeded, and git diff --cached --check passed.

  91. Reported: “The final staged diff is clean. I’m committing it with a message that covers the two main pieces: QEMU validation batches and host-share investigation.”

    Action/result: Created a commit with message Add QEMU validation batches; it was then amended to include this progress log entry.

  92. Reported: “I’ll build and test the qcow2 fixture-disk path end to end: create a normal FAT disk image with prebuilt AGI fixture directories, attach it as D:, verify DOS can see and run from it, then test savevm/loadvm around repeated fixture runs. If it works, I’ll fold that into the harness/docs so we can stop paying the full boot cost per case.”

    Action/result: Confirmed mformat, mcopy, mdir, mmd, mdel, mrd, qemu-img, and qemu-system-i386 are installed. The working tree only had generated artifacts under build/, 002962_ms_dos_622/, and tools/__pycache__/ before this pass.

  93. Reported: “DOS doesn’t accept a partitionless FAT image as a hard disk, so we do need an MBR-style partition. I’m creating a raw disk with a simple FAT16 partition starting at sector 63, formatting that partition with mtools, then converting it to qcow2.”

    Action/result: A partitioned secondary qcow2 fixture disk could be read by mtools, but DOS either treated sector 0 as a garbage FAT boot sector or reported Not ready reading drive D, even with DOS-like partition metadata and forced IDE CHS.

  94. Reported: “Forcing IDE geometry didn’t fix the second-disk case. I’m switching to the more robust variant: create a disposable qcow2 clone of the DOS boot disk with fixture directories preloaded onto C: itself. That still gives us the important property: one boot, savevm at the DOS prompt, then loadvm between fixture runs.”

    Action/result: Copied build/dos622/dos622.img to a disposable raw image, copied PIC001 and VIEW11 fixture directories into its DOS partition, converted it to qcow2, booted once, saved VM state at the DOS prompt, ran PIC001, loaded the saved VM, ran VIEW11, and compared both captures. Both matched with 0 mismatches.

  95. Reported: “The implementation pivot is clear: a generic helper will create a disposable qcow2 clone of the boot disk, preload DOS fixture directories with mtools, boot once, savevm ready, and loadvm ready between cases. Then view_batch.py --snapshot can use it without changing the comparison logic.”

    Action/result: Added tools/qemu_snapshot.py for disposable boot-disk cloning, mtools fixture preloading, and one-boot snapshot QEMU execution. Added tools/view_batch.py --snapshot plus local unit coverage for the snapshot helper’s fixture filtering, mtools image syntax, and monitor key names.

  96. Reported: “The view/object snapshot smoke passed: all six cases matched with zero errors. I’m going to extend the same one-boot mode to the picture fuzz batch command too, because that is where the boot cost really hurts.”

    Action/result: Ran python3 -B tools/view_batch.py --snapshot --dos-prefix VS --output build/view-batch/batches/view_snapshot_smoke.json --boot-wait 5 --draw-wait 8. QEMU booted once from the generated snapshot qcow2, restored the DOS prompt between the six built-in view/object cases, and all captures matched. Extended tools/picture_fuzz.py batch-qemu with --snapshot, using the shared qcow2 boot-disk snapshot helper. Added a mocked unit test proving snapshot-mode fuzz reports are compared and recorded through the existing batch-result path.

  97. Reported: “The real fuzz snapshot smoke passed too: both synthetic picture cases matched the original engine with zero mismatches from one QEMU boot. I’m recording that result now and then I’ll do one final status/check pass.”

    Action/result: Ran python3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_016_visual_fill_box --case base_019_pattern_edge_rectangle --dos-prefix FS --output build/picture-fuzz/batches/snapshot_smoke.json --boot-wait 5 --draw-wait 8. QEMU booted once from the generated snapshot qcow2, restored the DOS prompt between the two cases, and both captures compared with 0 mismatches.

  98. Reported: “I found a useful next lever: the fixture builder can currently combine a real SQ2 picture with a view, or a synthetic picture alone, but not a synthetic picture plus a view. Adding that combined fixture will let us create tiny controlled priority/control backgrounds and compare object overlay behavior against the original engine.”

    Action/result: Began adding a synthetic-picture-plus-view fixture path and targeted object overlay probes so priority/control behavior can be tested against QEMU with controlled picture backgrounds.

  99. Reported: “The first four priority threshold probes matched exactly, which is a strong signal that the basic existing_priority <= object_priority rule is right. I’m adding one more pair that targets the stranger part of the routine: when the destination cell has a low control nibble, it scans downward to find a real priority barrier.”

    Action/result: Ran python3 -B tools/object_overlay_probe.py --dos-prefix OP --output build/object-overlay-probes/batches/base_priority.json --boot-wait 5 --draw-wait 8. The default-control priority 3/4 pair and full-screen-control-6 priority 5/6 pair all matched QEMU with 0 mismatches. Added two scan-down cases that place control 2 at the object’s top row and control 6 one row below.

  100. Reported: “The QEMU result confirms both branches of the priority gate we cared about: direct high-priority comparison and low-control downward scanning. I’m folding that into the docs now as confirmed compatibility evidence rather than just a code hypothesis.”

    Action/result: Ran python3 -B tools/object_overlay_probe.py --dos-prefix OQ --output build/object-overlay-probes/batches/priority_scan_down.json --boot-wait 5 --draw-wait 8. All six object overlay probes matched QEMU with 0 mismatches, including the two low-control scan-down cases. Updated the graphics/object pipeline, compatibility testing notes, clean-room executable notes, and AGENTS tool instructions.

  101. Reported: “The 0x7a path stages two nibbles into object byte +0x24: the sixth operand as the low nibble and the seventh as the high nibble. I’m adding two mismatch probes now to confirm which nibble controls visible overlay priority, because that matters for the spec.”

    Action/result: Added two object overlay probes with intentionally different operand-6 and operand-7 values: priority 3/control 6 on a default control-4 background, and priority 6/control 3 on a control-6-filled background.

  102. Reported: “The nibble-discriminator cases matched too. That confirms visible object priority comes from the low nibble staged by operand 6; the high nibble from operand 7 does not control whether the cel pixels draw in these probes.”

    Action/result: Ran python3 -B tools/object_overlay_probe.py --dos-prefix OR --output build/object-overlay-probes/batches/priority_nibbles.json --boot-wait 5 --draw-wait 8. All eight object overlay probes matched QEMU with 0 mismatches. Updated the graphics/object pipeline, compatibility notes, and clean-room executable notes to describe the low-nibble visible-priority result.

  103. Reported: “Two useful surprises came out. The right-edge case shows placement clamps the object’s left coordinate to 160 - width rather than just clipping pixels. The persistent 0x36 priority-byte case suggests the high nibble can affect persistent placement/control acceptance even though transient visible drawing uses the low nibble, so I’m tightening those models and adding one confirmation case.”

    Action/result: The expanded object overlay run matched 16 of 18 cases. Added right-edge placement clamping to the local overlay model, changed the persistent 0x36 case to expect rejection under control 6, and added a 0x66 persistent priority-byte confirmation case.

  104. Reported: “The right-edge behavior is not a simple clamp: the actual cel lands around x=140, baseline_y=67, so placement search adjusts both axes. The persistent 0x66 case is hidden too, which makes the safer current conclusion: persistent fixed priority bytes with a nonzero high nibble are not valid visible-priority inputs in these probes. I’m encoding those as observed expectations rather than pretending the simple model covers them generally.”

    Action/result: Added per-case expected placement overrides for the right-edge probe and changed the persistent 0x66 probe to expect a hidden object under the controlled priority-6 picture.

  105. Reported: “The full 19-case QEMU batch is clean: clipping, transparent variants, priority-table-derived priority, and persistent-object setup all matched. I’m updating the spec now, including the important caveat that right-edge placement is observed as a placement-search result, not a general formula yet.”

    Action/result: Ran python3 -B tools/object_overlay_probe.py --dos-prefix OG --output build/object-overlay-probes/batches/expanded_all5_final.json --boot-wait 5 --draw-wait 8. All 19 cases matched QEMU with 0 mismatches. Updated compatibility notes, clean-room executable notes, and graphics/object pipeline documentation.

  106. Reported: “I’m picking up from the movement-probe branch of the work. The first QEMU run showed the object stayed exactly at its activation coordinates, which is a useful failure: the current fixture loops inside one logic invocation, so I’m going to switch it to a once-only setup guarded by an engine flag and let the interpreter return to its per-cycle machinery.”

    Action/result: Re-read tools/qemu_fixture.py, tools/object_movement_probe.py, the condition parser in tools/disassemble_logic.py, and the existing logic-bytecode docs.

  107. Reported: “The local notes already have the important conditional-block semantics, but I’m checking them against the disassembler implementation before encoding fixtures by hand. This is one of those tiny-byte-layout spots where one wrong offset can make the engine do a completely different thing while still looking valid.”

    Action/result: Confirmed the condition-list terminator carries a little-endian false delta after the 0xff condition-list end byte.

  108. Reported: “The real scripts make this simpler than the prose sounded: the condition-list terminator’s two-byte delta skips the following then-body directly. I’m adding a tiny bytecode builder for run this setup once unless flag X is already set, then the logic ends normally each tick so the engine can advance motion.”

    Action/result: Added reusable fixture helpers for logic-resource wrapping, end, set_flag, not flag_set, conditional blocks, and once-only logic.

  109. Reported: “I found SQ2’s own startup logic clearing broad high flag ranges, so I’m choosing an explicit fixture init flag and making it configurable per movement case. The generated logic will now look like: if not flag_set(init_flag) { load picture, set up object, start move, set init_flag } end.”

    Action/result: Added persistent_object_once_logic_payload() and optional init_flag support to persistent object fixtures.

  110. Reported: “The unit tests now cover the new byte sequences directly. I’m running the suite and a quick generated-logic disassembly check next; if those pass, I’ll send the two movement cases through QEMU again and compare against the Python renderer.”

    Action/result: Ran python3 -B -m unittest discover -s tests; 72 tests passed.

  111. Reported: “QEMU ran cleanly, but both cases still mismatch. That’s not a dead end; it means we now have captures from a better fixture, and I’m going to locate the object in those captures rather than assuming the target semantics.”

    Action/result: Ran python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_once.json --boot-wait 5 --draw-wait 8. Both one-shot movement cases mismatched.

  112. Reported: “The new captures show the object moved, just not to the target I expected. The mismatch boxes are shifted far beyond the requested target, so I’m waiting on a brute-force position match to identify the actual final coordinates.”

    Action/result: Best-position scan found exact matches at (140,80) for the horizontal one-shot case and (20,167) for the vertical one-shot case.

  113. Reported: “The captures match perfectly at (140,80) for the horizontal case and (20,167) for the vertical case. That strongly suggests move_object_to is not treating the target X/Y operands as immediates in this action variant, so I’m going back to the action table metadata and real script usage before updating the model.”

    Action/result: Rechecked 0x51/0x52 docs, action-table metadata, and local SQ2 script uses. The immediate-target interpretation remained consistent with the static handler analysis.

  114. Reported: “The static handler docs still say immediate targets, so the mismatch is probably in the per-cycle movement semantics, not bytecode operands. The object appears to keep the initial direction until it hits a screen boundary, which means we need to verify where and when the arrived at target check runs.”

    Action/result: Re-read movement-pass docs and concluded that scripts need to reissue move_object_to on later cycles so helper 0x1672 can observe that the current object position has reached the target.

  115. Reported: “This clarifies the right fixture shape: setup should be once-only, but move_object_to itself has to be reissued on later cycles until its completion flag becomes set. I’m changing the movement fixture from start movement once to initialize once, then call movement each tick while completion flag is clear.”

    Action/result: Added optional guarded per-cycle action blocks to persistent_object_once_logic_payload() and moved the probe’s 0x51 action into that block.

  116. Reported: “The suite is green at 73 tests and the diff has no whitespace issues. I’m running the revised QEMU probe now; this one should tell us whether repeated action issuance is the missing semantic.”

    Action/result: Ran tests and git diff --check; both passed.

  117. Reported: “That did it: both reissued-action movement probes match the Python expectation exactly. I’m going to fold this into the docs now, then expand the batch a bit with explicit boundary-style movement cases and add better mismatch observability so future failures tell us where did the object end up automatically.”

    Action/result: Ran python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_reissued.json --boot-wait 5 --draw-wait 8. Both target-arrival cases matched QEMU with 0 mismatches.

  118. Reported: “I’m adding two screen-edge probes to the same harness and teaching mismatch reports to include a best-fit object position. That turns future failures from pixels differ into the engine placed the object at X/Y, which is much more useful for reverse engineering.”

    Action/result: Added right-edge and bottom-edge movement cases plus MovementComparison.best_position.

  119. Reported: “The mismatch-position test found an equivalent drawn placement at the very top edge, which is a nice reminder that clipped cels can alias. I’m moving the synthetic mismatch away from clipping so the inferred coordinate is unambiguous.”

    Action/result: Adjusted the unit test to use an unclipped synthetic mismatch capture.

  120. Reported: “The suite is green at 74 tests. I’m launching the four-case QEMU batch now: two target arrivals plus right and bottom edge completion cases.”

    Action/result: Ran tests and launched python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_edges.json --boot-wait 5 --draw-wait 8.

  121. Reported: “All four QEMU movement cases matched, including the screen-edge stops. I’m documenting the clean-room evidence now: the important semantic is that scripts reissue targeted movement until the completion flag trips; a one-shot start keeps moving until boundary completion.”

    Action/result: The four-case movement batch matched QEMU with 0 mismatches.

  122. Reported: “I’m updating four doc surfaces: the running progress log, the compatibility test recipe, the object/movement model, and the clean-room evidence notes. Keeping these in sync is a bit clerical, but it is what makes the later implementation work grounded instead of folkloric.”

    Action/result: Updated docs/src/compatibility_testing.md, docs/src/graphics_object_pipeline.md, docs/src/logic_bytecode.md, and docs/src/clean_room_executable_notes.md with the targeted movement findings.

  123. Reported: “I’m going to add one control-buffer acceptance case before wrapping: a synthetic picture whose control channel is filled with zero. The hypothesis from 0x56b8 is that proposed movement should be rejected, leaving the object at its start position even though the movement action keeps reissuing.”

    Action/result: Added a fifth movement case using picture payload f2 00 f8 00 00 ff and an initial expectation that the object would remain at (20,80).

  124. Reported: “The control-zero hypothesis did not match, which is useful evidence. I’m inspecting the report’s inferred position now; if it moved normally, control zero is not a simple blanket movement rejection in this setup.”

    Action/result: Ran python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_control.json --boot-wait 5 --draw-wait 8. The first four cases matched, but the control-zero case mismatched. best_position showed an exact match at (50,80), meaning the object reached the target.

  125. Reported: “The inferred position is exact at (50,80): the object reached the target on a control-zero-filled synthetic picture. I’m changing that case from a rejected-move hypothesis into a confirmed acceptance probe, then I’ll rerun the batch so the saved report is green.”

    Action/result: Renamed the case to move_allowed_on_control_zero and changed its expected final position to (50,80).

  126. Reported: “The corrected five-case batch is green. I’m updating the docs one more time so they mention the control-zero acceptance result instead of only the four movement cases.”

    Action/result: Ran python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_control_final.json --boot-wait 5 --draw-wait 8. All five cases matched QEMU with 0 mismatches.

  127. Reported: “I’ve got the source-backed movement model pinned down now, so I’m going to encode more edge cases around 0x16ed: left/up, diagonal, non-divisible distances, zero step override, and already/near-target completion. Then I’ll run them through QEMU and update the docs with what actually matches.”

    Action/result: Added seven movement cases to tools/object_movement_probe.py.

  128. Reported: “The movement corpus now has twelve cases, including the direction and tolerance cases I wanted. I’m updating the tests to lock in that expanded coverage before running QEMU.”

    Action/result: Updated tests/test_object_movement_probe.py; local tests passed.

  129. Reported: “The movement model matched 11 of 12; the only miss is the zero step override. I’m inspecting the report now, because that should tell us the existing default step size the engine actually used.”

    Action/result: Ran python3 -B tools/object_movement_probe.py --dos-prefix MX --output build/object-movement-probes/batches/expanded_movement_edges.json --boot-wait 5 --draw-wait 8. The zero-step case’s best_position was (20,80), proving operand 3 value zero preserved the object’s current zero step in this generated fixture.

  130. Reported: “The zero-step case is illuminating: in this generated object, the existing step byte is zero, so operand 0 preserves zero and the object never moves. I’m changing that case to record zero preserves current step, with the current synthetic default being stationary.”

    Action/result: Updated the zero-step expected final position to (20,80). The rerun python3 -B tools/object_movement_probe.py --dos-prefix MX --output build/object-movement-probes/batches/expanded_movement_edges_final.json --boot-wait 5 --draw-wait 8 matched all 12 cases with 0 mismatches.

  131. Reported: “For object-object collision, the available clean fixture path is less direct than movement: object grouping is not exposed by a simple script action. I’m disassembling 0x4719 closely now so any QEMU probe we add is designed around what the helper really tests, not just a guess about overlap.”

    Action/result: Disassembled 0x4719; confirmed moving-object bit 0x0200, candidate flags, grouping byte +0x02, horizontal overlap, and Y equality/crossing predicates.

  132. Reported: “Good, the startup code initializes object byte +0x02 to the object index, so object 0 and object 1 naturally have different collision groups. I’m adding two-object movement fixtures: one where object 0 is blocked by object 1, and one where setting bit 0x0200 on object 0 skips the collision and allows it through.”

    Action/result: Added set_object_bit_0200_action() to tools/qemu_fixture.py and optional obstacle-object support to tools/object_movement_probe.py.

  133. Reported: “QEMU is now running 14 cases, including the two-object collision checks. I expect the first 12 to stay green; the useful signal will be cases 13 and 14.”

    Action/result: Ran python3 -B tools/object_movement_probe.py --dos-prefix MC --output build/object-movement-probes/batches/movement_collision.json --boot-wait 5 --draw-wait 8. All 14 cases matched with 0 mismatches. The blocked collision case stopped object 0 at (25,80) before touching object 1 at (50,80), and the collision-skip bit case reached (80,80).

  134. Reported: “Before wrapping, I’m checking the autonomous modes (0x53/0x54) call sites so the docs can distinguish what we validated in QEMU from what remains source-only.”

    Action/result: Scanned near call sites and disassembled helper 0x0563. Documented the source-derived cycle ordering: 0x0563 calls dispatcher 0x067a for active objects with countdown byte +0x01 == 1; 0x067a dispatches mode +0x22 value 1 to 0x3f5a, value 2 to 0x0b36, and value 3 to 0x1672, before 0x0563 calls movement pass 0x150a.

  135. Reported: “One thing I’m catching before I wrap this: the current batch finished the targeted movement and collision parts cleanly, but the full steps brief also includes the other motion modes and broader suite shape. I’m going to inspect the existing fixture/probe helpers so I can extend that work instead of calling it done too early.”

    Action/result: Reviewed tools/qemu_fixture.py, tools/object_movement_probe.py, and the existing docs for autonomous modes 0x53 and 0x54.

  136. Reported: “For mode 2, I can make the test deterministic: object 1 approaches object 0, and the visual comparison includes both objects. I’m going to add it with an expected position, run QEMU, and if the real engine lands at a different near-band coordinate I’ll correct the expectation and document that as evidence.”

    Action/result: Added reusable fixture encoders for assignn, set_object_field_1e_var, set_object_field_01_var, approach_first_object_until_near, start_random_motion, and stop_motion_mode. Added one autonomous mode-2 probe case to tools/object_movement_probe.py.

  137. Reported: “The fast suite is green with 78 tests. Now I’m sending the full movement batch through QEMU, including the new autonomous approach case; this is the slower but more interesting part.”

    Action/result: Ran python3 -B tools/object_movement_probe.py --dos-prefix MA --output build/object-movement-probes/batches/autonomous_modes_001.json --boot-wait 5 --draw-wait 8. The first 14 cases matched. The new threshold-25 approach case mismatched; its best captured moving-object position was (60,75), showing the probe had entered collision/stuck-recovery behavior near object 0 rather than cleanly isolating direct approach completion.

  138. Reported: “The mismatch is informative: with a threshold of 25, the object appears to hit the collision/stuck-recovery path near the target instead of cleanly completing. I’m widening the near threshold so this probe isolates mode-2 completion rather than object-object collision recovery.”

    Action/result: Changed the approach threshold to 35 and first expected boundary position to (45,80). The rerun python3 -B tools/object_movement_probe.py --dos-prefix MB --output build/object-movement-probes/batches/autonomous_modes_002.json --boot-wait 5 --draw-wait 8 still mismatched, with best position (50,80).

  139. Reported: “The second report gives a clearer source-derived correction: mode 2 completed at X 50, not 45. That suggests the near test is strict at the threshold boundary; I’m updating the expected result to the real-engine coordinate and rerunning once more for a clean green report.”

    Action/result: Updated the expected approach position to (50,80). The final run python3 -B tools/object_movement_probe.py --dos-prefix MD --output build/object-movement-probes/batches/autonomous_modes_003.json --boot-wait 5 --draw-wait 8 matched all 15 cases with 0 mismatches.

  140. Reported: “Great, the final QEMU batch is green: 15/15 exact matches, including autonomous approach mode. I’m updating the docs and progress log with the failed-threshold observation and the corrected passing run, because that intermediate result is useful clean-room evidence.”

    Action/result: Updated the compatibility docs, object pipeline docs, and clean-room executable notes with the mode-2 QEMU evidence.

  141. Reported: “I’ll take these as an implementation batch: extend the QEMU probes, reverse the stuck/recovery path enough to document it, add object-field notes where source-backed, and start turning view/cycle behavior into the same compatibility harness style.”

    Action/result: Reviewed the existing movement, fixture, overlay, and graphics docs before editing.

  142. Reported: “The current harness can be extended without splitting tools: exact-position checks still work for deterministic modes, and I can add a property-based comparison for random motion where the engine is allowed to choose any final position as long as the object is rendered exactly somewhere valid.”

    Action/result: Added comparison_kind="any_position" support to tools/object_movement_probe.py, plus move_to_once_countdown_gated_completion and random_motion_visible_somewhere cases.

  143. Reported: “The movement harness now has the two new mode cases. I’m adding extra overlay cases for cel and group selection on view 11, which will exercise different frame dimensions and offsets through the real object drawing path.”

    Action/result: Added view 11 group 0 frame 1, group 1 frame 0, and group 1 frame 1 cases to tools/object_overlay_probe.py.

  144. Reported: “Fast tests are clean. I’m going to run the expanded movement QEMU batch first; this will tell us whether countdown-gated mode 3 completes as expected and whether the random-motion property check is robust.”

    Action/result: Ran python3 -B tools/object_movement_probe.py --dos-prefix ME --output build/object-movement-probes/batches/motion_modes_004.json --boot-wait 5 --draw-wait 8. All 17 cases matched with 0 mismatches. The countdown-gated one-shot 0x51 case completed at (50,80), and the random-motion property case matched exactly at final position (140,112).

  145. Reported: “The overlay batch is fully green, including all three new view 11 cel/group selections. I’m going to update the docs now with the QEMU results, the mode-3 clarification, the random-motion property result, and the source-backed stuck-recovery details from 0x0b36.”

    Action/result: Ran python3 -B tools/object_overlay_probe.py --dos-prefix OE --output build/object-overlay-probes/batches/view_cel_selection_002.json --boot-wait 5 --draw-wait 8. All 22 cases matched with 0 mismatches.

  146. Reported: “The label table now includes the mode dispatcher, which will help when we compare later interpreter versions with shifted addresses. I’m appending the new command log/results to the clean-room notes.”

    Action/result: Added symbolic label code.motion.dispatch_mode_step for image offset 0x067a; updated compatibility, logic bytecode, graphics/object pipeline, and clean-room notes with the new QEMU and disassembly evidence.

  147. Reported: “One sanity check before I wrap: I covered cel/group selection, but cycling deserves at least a source-backed pass too. I’m checking the +0x23 handlers/call sites now so I don’t overstate that part.”

    Action/result: Disassembled handlers 0x6b82..0x6c17 for actions 0x48..0x4b. The handlers set object byte +0x23, object flag bits, and optional completion flag byte +0x27; this records the setup side only. Automatic frame-cycling runtime consumers remain a follow-up target.

Logic interpreter opcode coverage and QEMU probes

  1. Reported: “I’ll pick this back up from the current workspace state: first I’m going to re-read the local instructions and inspect the new probe/doc changes, then I’ll run the fast checks before putting QEMU back in the loop.”

    Action/result: Re-read AGENTS.md, inspected the working tree, reviewed tools/logic_interpreter_probe.py, and checked the handoff chapter docs/src/current_status.md.

  2. Reported: “The handoff page and AGENTS notes are consistent: the immediate gap is full logic opcode coverage plus executable-backed probes. I’ve also confirmed there’s already a new QEMU logic probe in progress, so I’m going to stabilize that rather than starting a parallel harness.”

    Action/result: Reviewed the rest of tools/logic_interpreter_probe.py, its unit tests, and the current logic_bytecode.md opcode entries.

  3. Reported: “The docs already use paragraph-style opcode entries and the condition range is documented. One concrete audit miss remains from the earlier pass: action 0x00 is explained structurally but not yet represented as a normal action entry, and I’ll fold that in while covering the rest.”

    Action/result: Ran python3 -B -m unittest discover -s tests; all 84 tests passed before the new edits.

  4. Reported: “The Python suite is still green after the self-loop change. I’m starting the QEMU snapshot batch now; this is the meaningful check for whether the synthetic logic resources are exposing interpreter behavior cleanly.”

    Action/result: Started python3 -B tools/logic_interpreter_probe.py --dos-prefix LJ --output build/logic-interpreter-probes/batches/control_flow_002.json --boot-wait 5 --draw-wait 8.

  5. Reported: “QEMU has built the four fixture directories and is running them from the snapshot disk. While it runs, the specific uncertainty I’m testing is whether a one-shot transient draw has to be held in a live interpreter loop to be visible at screenshot time.”

    Action/result: The QEMU batch completed with 4 matches, 0 mismatches, and 0 errors. The passing cases were jump_skips_first_draw, if_false_skips_then_draw, not_condition_runs_then_draw, and or_group_true_runs_then_draw.

  6. Reported: “Good, the QEMU batch now matches 4/4. That means the interpreter-control fixtures are viable, and the earlier failures were fixture lifetime artifacts rather than evidence against the jump/NOT/OR interpretation. Next I’m auditing every opcode label against the docs so the ‘all opcodes’ pass is explicit rather than vibes in a trench coat.”

    Action/result: Ran an opcode-label audit comparing tools/disassemble_logic.py against docs/src/logic_bytecode.md.

  7. Reported: “The existing logic_bytecode.md is already much further along than the handoff note implied: it has labels and observed behavior for the whole action range through 0xaf, with only the structural 0x00 missing from the normal entry list. I’m going to make that coverage machine-checkable enough to trust.”

    Action/result: Confirmed the action-name map has 176 entries and no range gaps from 0x00..0xaf; the condition-name map has 19 entries and no gaps from 0x00..0x12.

  8. Reported: “The audit came back clean except for exactly one missing normal entry: 0x00 (end). The action label map covers every byte from 0x00 through 0xaf; the condition label map covers every valid-looking condition entry from 0x00 through 0x12.”

    Action/result: Prepared documentation and unit-test edits to add the missing 0x00 entry and lock the coverage audit into the test suite.

  9. Reported: “I’m going to make three small documentation/test edits now: add the missing 0x00 action entry, add a regression test that fails if any opcode label drops out of the docs, and record the QEMU control-flow probe plus the self-loop correction in the evidence/progress chapters.”

    Action/result: Added the 0x00 (end) action entry and coverage audit to logic_bytecode.md, added tests/test_logic_doc_coverage.py, and documented the logic-interpreter QEMU probe in the compatibility/current status/progress chapters.

  10. Reported: “One nuance I want to make explicit before calling this done: ‘all opcodes’ should include the invalid/structural byte ranges too, not just the named action table. I’m adding a short dispatch-range note so future readers know what happens to bytes outside the valid action/condition catalogs.”

    Action/result: Added dispatch-range paragraphs to logic_bytecode.md for action bytes 0x00, 0x01..0xaf, 0xb0..0xfb, 0xfc, 0xfd, 0xfe, and 0xff, plus condition bytes 0x00..0x12, 0x13..0x25, 0x26..0xfb, and structural markers 0xfc..0xff.

  11. Reported: “That range note is in place now: valid actions, invalid action bytes, structural bytes, valid condition predicates, reserved condition bytes, and condition markers are all spelled out separately. I’m adding that last update to the progress record and rerunning verification.”

    Action/result: Verification rerun follows this documentation update.

  12. Reported: “I’ll take this as an implementation pass, not a proposal: expand the logic probe harness, add deterministic opcode-family cases, attach each case to docs/evidence levels, then verify with unit tests, mdBook, and QEMU where practical. First I’m reading the current harness and helper patterns so the new cases fit the existing machinery cleanly.”

    Action/result: Reviewed tools/logic_interpreter_probe.py, tools/qemu_fixture.py, tests/test_logic_interpreter_probe.py, and the logic-bytecode/compatibility status docs.

  13. Reported: “The probe design is promising: most arithmetic/flag/condition opcodes can be validated by doing the operation, then drawing the object only if a condition observes the expected state. If the interpreter disagrees, the screenshot stays blank and the comparison fails loudly.”

    Action/result: Designed the expanded logic probe cases around guarded transient drawing through condition blocks.

  14. Reported: “One wrinkle with self-looped fixtures: mutating opcodes like inc would keep changing state every cycle. I’m making each arithmetic case reset its inputs before the tested action, so the condition remains true on every interpreter pass and the transient draw is stable at capture time.”

    Action/result: Added reset-before-test bytecode patterns for mutable variable, flag, and object-field probes.

  15. Reported: “I’m editing the logic probe now: adding bytecode builders for variable, flag, comparison, and simple object-field checks, then folding those into the default QEMU batch so they run with the existing control-flow cases.”

    Action/result: Expanded tools/logic_interpreter_probe.py to 27 default cases covering control flow, variable arithmetic, indirect variables, flags, comparison predicates, and selected object-field getters/setters.

  16. Reported: “The default logic batch now includes control flow plus arithmetic, indirect-variable, flag, comparison, and three simple object-field probes. I’m running the fast unit tests first; then I’ll send the expanded batch through QEMU and correct anything the original engine disagrees with.”

    Action/result: Ran the logic probe unit tests successfully and confirmed the expanded default batch contains 27 unique cases.

  17. Reported: “The expanded batch has 27 cases and the local probe tests pass. I’m launching QEMU now; the object-field cases are the most likely to teach us something because they touch engine object records without drawing persistent objects.”

    Action/result: Started python3 -B tools/logic_interpreter_probe.py --dos-prefix LK --output build/logic-interpreter-probes/batches/opcode_families_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure.

  18. Reported: “QEMU has all 27 fixture directories loaded into the snapshot disk and is stepping through them. I used --stop-on-failure for this first pass, so if one case is wrong we get the earliest useful failure instead of a pile of downstream noise.”

    Action/result: QEMU continued executing the one-boot snapshot batch.

  19. Reported: “The expanded QEMU run is green: 27/27 exact visual matches. That gives us executable evidence for the core arithmetic opcodes, indirect variable forms, flag opcodes, comparison predicates, and a few simple object-field getter/setter pairs. Next I’m turning that into an evidence matrix so the docs distinguish ‘QEMU validated’ from ‘source-backed only.’”

    Action/result: Saved the matching report at build/logic-interpreter-probes/batches/opcode_families_001.json.

  20. Reported: “I’m adding a small generator for the evidence matrix rather than hand-maintaining a 176-row table. The generator uses only the local dispatch table and our local opcode-label map, then annotates rows with the QEMU probes we have actually run.”

    Action/result: Added tools/logic_opcode_evidence.py, linked the generated chapter from SUMMARY.md, and added a unit test that checks the generated evidence matrix is current.

  21. Reported: “The generator is in place and linked into the book. I’m generating the evidence chapter now, then I’ll wire the run result into the compatibility/evidence notes.”

    Action/result: Generated docs/src/logic_opcode_evidence.md and verified it with python3 -B tools/logic_opcode_evidence.py --check.

  22. Reported: “The generated matrix now covers every action row 0x00..0xaf, all known condition rows 0x00..0x12, and the invalid/structural byte ranges. I’m tightening the tests around the new probe cases so the expanded default batch doesn’t accidentally shrink later.”

    Action/result: Expanded tests/test_logic_interpreter_probe.py to assert representative variable, flag, comparison, arithmetic, control-flow, and object-field cases remain in the default batch.

  23. Reported: “I’m updating the documentation trail with the exact QEMU batch name and the new evidence chapter. This is the clean-room receipt: what ran, what matched, and what remains source-backed rather than emulator-backed.”

    Action/result: Updated compatibility, current-status, logic-bytecode, and progress docs with the expanded logic probe and evidence matrix.

  24. Reported: “I’ll do these in order and keep the artifacts testable: first multi-logic call/resume fixtures, then variable-backed resource/object variants, then object predicates, then strings/messages, then inventory/table predicates. I’m going to inspect the current fixture builder and logic resource format first so the new probes extend the existing QEMU harness instead of becoming a separate one-off.”

    Action/result: Reviewed the logic interpreter probe, QEMU fixture helper, tests, opcode evidence generator, and local logic resource documentation.

  25. Reported: “The existing logic_resource() helper emits a minimal no-message resource, which is fine for the first three steps. For the string/message step I need to construct a real custom message table, so I’m checking the locally documented message format and existing logic parser before editing.”

    Action/result: Re-read docs/src/logic_resources.md, confirming code_length, message_count, table offsets relative to table base, and XOR-encrypted text using the Avis Durgan key.

  26. Reported: “I’m going to extend the single logic probe harness rather than create five tools. The key change is letting a case patch multiple logic resources into VOL.3 and optionally give logic 0 a custom message table, which unlocks call/load and string/message probes cleanly.”

    Action/result: Updated tools/qemu_fixture.py so logic_resource() can encode custom message tables.

  27. Reported: “The fixture helper now supports encrypted custom logic messages using the locally documented XOR key and offset-table layout. Next I’m adding the multi-logic/resource patching and the new probe cases in sequence.”

    Action/result: Added extra-logic metadata, message metadata, multi-logic VOL.3 packing, and new logic probe cases for the five requested groups.

  28. Reported: “I’ve got the new cases drafted; I spotted two fixture-shape issues before running them: a persistent-object var-resource case should not use the conditional transient-draw wrapper, and the NOT-always-false probe needs the marker before the condition byte. I’m tightening those now and then I’ll run the local tests.”

    Action/result: Corrected the persistent object case and the NOT marker ordering.

  29. Reported: “Multi-resource fixture patching is in. I’m running the focused tests now; if they pass, I’ll inspect a generated fixture byte layout before letting QEMU at the larger batch.”

    Action/result: Ran focused logic probe and fixture tests; 28 tests passed. The default logic probe batch now contains 47 cases.

  30. Reported: “The focused tests pass and the default batch is now 47 cases. I’m starting QEMU with stop-on-failure; this will validate the steps in sequence and stop at the first semantic mismatch so I can correct the model before continuing.”

    Action/result: Started the all-case QEMU run five_steps_001.json.

  31. Reported: “Because the batch is ordered, the first failing case will tell us which requested step needs attention. Cases 28-31 are call/load/resume, then the variable-backed object/resource cases, then object predicates, strings/messages, and inventory/table markers.”

    Action/result: The all-case QEMU run did not reach execution. The snapshot disk filled while copying fixture directories, so the failure was a harness capacity limit rather than an interpreter mismatch.

  32. Reported: “The first QEMU attempt did not reach execution: the snapshot boot disk filled up while copying 47 full SQ2 fixture directories. That’s a harness capacity issue, not an interpreter mismatch. I’ll run the new five-step cases as a smaller batch and add a case filter so future runs don’t have to copy the entire corpus.”

    Action/result: Added repeated --case filtering to tools/logic_interpreter_probe.py.

  33. Reported: “The harness can now run named subsets with repeated --case flags. I’m going to validate the five requested steps in chunks: call/load /resume first, then variable-backed resources, then predicates, strings/messages, and inventory/table markers.”

    Action/result: Started filtered QEMU validation.

  34. Reported: “Starting step 1 now: call_logic, load_logic plus call_logic, call_logic_var, and the resume-action smoke probe.”

    Action/result: Step 1 matched QEMU with 4 matches, 0 mismatches, and 0 errors.

  35. Reported: “Step 1 is green: all four call/load/resume probes matched. I’m moving to step 2, the variable-backed object/resource variants, including var-based transient drawing and immediate-completion move_object_to_var.”

    Action/result: Step 2 matched QEMU with 4 matches, 0 mismatches, and 0 errors.

  36. Reported: “Step 2 is green too. The persistent object selected through variable-backed resource/group/frame actions matched view 11 group 1 frame 1, and 0x7b matched the same cel through all-variable operands. Next is object rectangle predicates.”

    Action/result: Step 3 matched QEMU with 4 matches, 0 mismatches, and 0 errors.

  37. Reported: “Step 3 is green: all four object rectangle predicates matched. I’m moving to strings/messages now; this is the new custom-message-table path, so if anything breaks it will most likely be message encoding or parser state rather than drawing.”

    Action/result: Step 4 matched QEMU with 2 matches, 0 mismatches, and 0 errors.

  38. Reported: “Step 4 is green. The custom message resource was accepted by the original loader, 0x72 copied message text into string slots, 0x0f normalized and compared it, and 0x75 plus 0x0e recognized look. Now I’m validating the inventory/object-table marker actions and predicates.”

    Action/result: Started the inventory/table marker QEMU batch.

  39. Reported: “The inventory/table step covers both predicates (0x09, 0x0a) and the marker actions 0x5c..0x61. I’m running them together because they’re small and all use the same table entry.”

    Action/result: Step 5 matched QEMU with 6 matches, 0 mismatches, and 0 errors.

  40. Reported: “QEMU is executing the six inventory/table cases now. If these match, all five requested steps will have QEMU-backed evidence, with the larger all-cases run split only because the DOS snapshot disk filled up.”

    Action/result: Confirmed the six inventory/table cases matched.

  41. Reported: “Step 5 is green. I’m updating tests and the evidence matrix annotations now so the new QEMU-backed opcodes show up as validated rather than source-only.”

    Action/result: Updated regression tests and regenerated the opcode evidence matrix.

  42. Reported: “The evidence matrix is regenerated with the newly validated opcodes. I’m documenting both the successful step runs and the one failed ‘all 47 at once’ attempt, because the failure tells future us about snapshot disk capacity rather than AGI behavior.”

    Action/result: Updated compatibility, current-status, clean-room notes, and progress docs.

  43. Reported: “Checkpoint is bbda90f; only generated/local artifacts remain untracked. I’m starting the next pass by checking the existing object/view action notes and local view metadata so the new probes use expected values that are already derived from local evidence.”

    Action/result: Reviewed object/view getter and bitfield notes in logic_bytecode.md, inspected view 11 with tools/inspect_view.py, and checked the current evidence matrix rows.

  44. Reported: “For this pass I’m splitting the target actions into two evidence types: value probes where a getter/condition observes the expected state, and smoke probes for bitfield actions whose direct bit value is not exposed by an existing script predicate. That keeps the docs honest: ‘this opcode executed and consumed operands under QEMU’ is useful, but different from proving every downstream semantic.”

    Action/result: Added value probes for object/view metadata getters, variable-backed field +0x24, inactive object-distance behavior, and 0x4d clearing direction byte +0x21; added one dispatch smoke case for selected bitfield/helper actions.

  45. Reported: “The new object/view cases are in: metadata getters, variable-backed field +0x24, inactive distance behavior, 0x4d clearing direction, and a dispatch smoke case for the bitfield/helper actions with no direct script-visible getter. I’m running the focused tests now.”

    Action/result: Ran python3 -B -m unittest tests.test_logic_interpreter_probe; 6 tests passed, and the default logic probe set now contains 52 cases.

  46. Reported: “The local tests pass and the default set is now 52 cases. I’m running just the five new object/view cases through QEMU so the snapshot disk stays small.”

    Action/result: The first QEMU attempt under sandbox failed before execution because the VNC socket bind was not permitted.

  47. Reported: “QEMU was blocked by the sandbox when binding the local VNC socket. I’m rerunning the same filtered batch with approval so the emulator can start; this is the same kind of local QEMU validation we’ve been using.”

    Action/result: Reran the filtered object/view batch with approved escalation.

  48. Reported: “QEMU is running the five filtered cases now. The first four are value probes; the fifth is intentionally a dispatch smoke probe for bitfield/helper actions that don’t expose a simple script-visible bit readback.”

    Action/result: The QEMU batch matched with 5 matches, 0 mismatches, and 0 errors.

  49. Reported: “The QEMU batch is green: five matches, zero mismatches. I’m updating the evidence matrix now, marking direct value probes as QEMU-validated and the remaining bitfield/helper actions as QEMU dispatch-smoke rather than overstating their full semantics.”

    Action/result: Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  50. Reported: “The generated matrix now has a separate QEMU dispatch-smoke evidence level. I’m documenting the new object/view batch and the distinction between value probes and smoke probes in the project notes.”

    Action/result: Updated compatibility, current-status, clean-room notes, and progress docs with the object/view follow-up batch.

  51. Reported: “I’ve got the shape of it now: mdBook is using its default narrow content column, and there’s no project CSS override yet. I’m going to add a small theme stylesheet so wide reference tables can use more horizontal room and scroll inside themselves when they still overflow.”

    Action/result: Inspected docs/book.toml, the generated mdBook CSS, and the current worktree state before adding the table-layout override.

  52. Reported: “I found the progress log tail and confirmed the worktree is clean apart from the usual generated/untracked artifacts. Next I’m checking mdBook’s generated selector names so the override lands on the actual content container instead of guessing.”

    Action/result: Confirmed mdBook wraps generated tables in .table-wrapper and applies the page width through --content-max-width.

  53. Reported: “The generated mdBook CSS confirms the hard cap is --content-max-width: 750px, applied to .content main. I can override that variable cleanly from an additional-css file and then give tables their own horizontal overflow behavior.”

    Action/result: Added docs/theme/wide-tables.css to widen the content column and make wide tables scroll horizontally while preserving readable wrapping in the final detail column.

  54. Reported: “mdBook already wraps tables in .table-wrapper, but only adds overflow; the table itself can still be squeezed by the 750px main column. I’m adding a source-controlled CSS file that widens the content column and keeps table columns readable before falling back to horizontal scrolling.”

    Action/result: Updated docs/book.toml to load the new stylesheet via additional-css.

  55. Reported: “I’ll rework the logic bytecode chapter now that the book can handle wider tables. First I’m going to inspect the chapter structure and the current markdown patterns so the table conversion is deliberate rather than a blind format pass.”

    Action/result: Read docs/src/logic_bytecode.md, checked the progress log tail, and confirmed the worktree state before editing.

  56. Reported: “The chapter has several paragraph lists that are really reference tables: runtime metadata examples, main-stream byte ranges, the condition table/ranges/predicates, action entry examples, and decoded action families. I’m reading the rest of the file now so I don’t miss another block that should be restored to table form.”

    Action/result: Reviewed the remaining logic bytecode chapter sections and identified compact opcode catalogs versus longer semantic explanations.

  57. Reported: “There are also long explanatory opcode entries where paragraphs are still the better shape, especially for movement, parsing, save/restore, and display behavior. I’m going to table the compact catalog-like material and leave the multi-paragraph semantic notes as prose.”

    Action/result: Chose a mixed layout: tables for opcode facts and prose for state-machine or helper-flow details.

  58. Reported: “The densest restored tables will be in the front half of the chapter and the local scan summary. For the long decoded action families section, I’m going section by section: compact opcode rows become tables, while explanatory helper blocks stay as explanatory blocks.”

    Action/result: Collected line-numbered context for the runtime, condition, action, and scan-summary sections.

  59. Reported: “I’ve separated catalog data from semantic explanation. The first patch is the high-value cleanup: dispatch/condition catalogs and the short arithmetic/flag/action summary rows go back into tables.”

    Action/result: Converted metadata examples, main-stream byte ranges, condition entries/ranges/predicates, action examples, variable actions, and flag actions to Markdown tables.

  60. Reported: “The first conversion is in. I’m moving through the action families next; these are especially useful as tables because implementers will scan by opcode, label, handler, and effect.”

    Action/result: Inspected the object/view, resource-control, object-state, message/UI, and remaining-entry sections for table conversion.

  61. Reported: “The remaining opcode blocks are large, but most are structurally regular. I’m going to table the resource/object/UI families now and keep the detailed movement subsections as prose where they explain per-cycle behavior.”

    Action/result: Converted the object/view action and resource/interpreter control catalogs to Opcode, Label, Handler, and Observed action tables.

  62. Reported: “I’m making the action-family tables broad but shallow: Opcode, Label, Handler, Observed action. The final action column can now wrap inside the wider mdBook layout, which is exactly what that CSS change was for.”

    Action/result: Kept long behavior descriptions in the final table column so rows remain scan-friendly without losing evidence detail.

  63. Reported: “The object/view and resource-control catalogs are tabled now. Next I’m doing the object-state, message/UI, and misc sections, then I’ll build the book to catch Markdown table mistakes.”

    Action/result: Converted additional object-state, post-movement, dirty position, and object-field-24 action summaries to tables.

  64. Reported: “Object state is now much easier to scan. I’m converting the remaining compact UI/resource/session listings the same way; after that I’ll make sure no Count: opcode paragraphs remain in the scan summary.”

    Action/result: Converted message-display, menu/list, and text/input action catalogs to tables.

  65. Reported: “Most of the chapter’s UI/action catalogs are back in table form. I’m doing the final resource/session/misc groups now, plus the local SQ2 frequency lists at the end.”

    Action/result: Converted resource/table actions, session-control actions, miscellaneous actions, remaining table entries, and frequency summaries to tables.

  66. Reported: “The conversion pass is complete. I’m checking for leftover paragraph-style opcode summaries and then I’ll build the book; Markdown table syntax is fussy enough that I want mdBook to be the judge.”

    Action/result: Initial leftover scan found only movement-mode opcode paragraphs; mdbook build docs succeeded.

  67. Reported: “The only opcode paragraphs I expect to remain are the movement entries where the surrounding explanation matters. I’m rerunning the leftover scan with safer shell quoting and then building the mdBook.”

    Action/result: Confirmed the remaining paragraph-style opcode summaries were 0x51..0x54 movement actions and the book still built successfully.

  68. Reported: “The leftover scan found exactly the movement opcodes I intentionally left in prose. I’m going to improve those too by adding a compact movement-mode table, while keeping the multi-paragraph per-cycle details underneath.”

    Action/result: Added a movement-mode entry-point table for opcodes 0x51..0x54 and rewrote the surrounding prose to avoid duplicate paragraph-style opcode summaries.

  69. Reported: “That takes care of the movement outliers: they now have a table for the entry-point facts, with the state-machine details kept beneath it. I’m doing the final validation pass now.”

    Action/result: Verified no paragraph-style opcode summary patterns remain, mdbook build docs succeeds, and git diff --check is clean.

  70. Reported: “The book builds cleanly and git diff --check is clean. I’m adding the progress-log entries for this documentation pass, then I’ll check the final status.”

    Action/result: Recorded the logic-bytecode table conversion pass in this progress log.

  71. Reported: “I’ll pick the thread back up at the logic-interpreter probe expansion. I’m going to inspect the current probe harness, evidence matrix, and source-backed-only opcode rows so the next batch targets behaviors that can actually be observed in QEMU.”

    Action/result: Inspected the current probe harness, evidence matrix, and documentation targets for additional runtime-visible opcodes.

  72. Reported: “The current harness already has 52 default cases and a separate smoke level in the evidence matrix. The obvious next useful target is to turn some smoke/source-backed object field helpers into value probes where we can observe the effect through existing getters or visible drawing.”

    Action/result: Identified object-state, random, and no-op action entries suitable for direct QEMU probes.

  73. Reported: “I’m going to add a small batch of value/smoke probes that are low-risk and visible: object position variants 0x28, 0x93, 0x94, deactivation/clear-all effects 0x24 and 0x22, deterministic random 0x82 with equal bounds, and no-op/runtime-skip entries 0x7f, 0x9b, 0xaf.”

    Action/result: Added nine candidate cases to tools/logic_interpreter_probe.py.

  74. Reported: “I’m picking up from the just-added probe batch. First I’ll run the harness tests so we know whether the new cases are structurally sound before spending QEMU time on them.”

    Action/result: Ran python3 -B -m unittest tests.test_logic_interpreter_probe; all six tests passed.

  75. Reported: “The local probe tests pass, so the new cases are well-formed. I’m going to run them against the original engine now; this is the part that tells us which hypotheses deserve to graduate from static/source-backed notes into runtime evidence.”

    Action/result: Started the nine-case QEMU probe batch; the sandbox blocked the initial VNC socket bind.

  76. Reported: “As expected, the sandbox blocked QEMU’s local VNC socket. I’ll rerun the same batch with approval so the emulator can bind its display port.”

    Action/result: Reran the nine-case batch with elevated QEMU permission.

  77. Reported: “The QEMU batch is running from a snapshot now. While it works through the fixtures, I’ll prep the evidence-matrix and docs context so the runtime results can be recorded cleanly once they land.”

    Action/result: Reviewed tools/logic_opcode_evidence.py and relevant docs while QEMU was running.

  78. Reported: “The docs context confirms these opcodes were previously mostly source-backed or smoke-only. If the batch passes, I’ll promote the specific ones with observable outcomes into the generated evidence matrix instead of leaving them as ‘we saw it in disassembly.’”

    Action/result: Confirmed target opcodes were suitable for evidence-level promotion pending QEMU results.

  79. Reported: “Still waiting on QEMU; snapshot batches can be quiet until the report is written. I’m letting it finish rather than interrupting, because partial emulator runs are much less useful as evidence.”

    Action/result: Continued waiting for the snapshot-backed QEMU batch.

  80. Reported: “First two probes matched: 0x28 adds the variable deltas as expected, and 0x82 with equal bounds stores that bound. The no-op and object activation cases are next in the batch.”

    Action/result: Observed the first two QEMU matches from the running batch.

  81. Reported: “Good batch, with one interesting wrinkle: eight passed and clear_all_object_bits_removes_persistent_draw did not. I’m going to inspect that mismatch rather than paper over it; 0x22 may be clearing active/update bits but not removing an already-linked draw entry in the way my first fixture assumed.”

    Action/result: Inspected the report build/logic-interpreter-probes/batches/object_state_misc_001.json; eight cases matched and the 0x22 fixture mismatched.

  82. Reported: “The mismatch is exactly at the first object’s footprint, so the original engine still draws the previously activated object after 0x22 in this same logic pass. I’m reading the harness shape now and will turn this into a positive probe for the behavior we actually observed: ‘clear bits does not immediately unlink the already-active draw entry.’”

    Action/result: Confirmed the mismatch bounding box was the previously activated object’s visible footprint.

  83. Reported: “I’m going to make the probe harness a bit more expressive so a case can expect two rendered objects. That lets the 0x22 probe encode the observed behavior directly, instead of pretending the only valid outcome is a single sprite.”

    Action/result: Began extending LogicInterpreterCase with optional additional expected sprites.

  84. Reported: “The first patch missed because the constructor is still using positional arguments. I’ve got the exact spot now and will apply the narrower edit.”

    Action/result: Added the optional expected_extra_sprites field and constructor plumbing.

  85. Reported: “Now I’m changing the 0x22 case itself from a negative expectation into a positive one: expect the old persistent object plus the new transient draw. That will give us a sharper note for the spec.”

    Action/result: Renamed the case to clear_all_object_bits_keeps_current_draw_entry and gave it two expected sprites.

  86. Reported: “One more harness edit: comparison needs to render any extra expected sprites before it overlays the primary expected sprite. After that I’ll rerun just the corrected 0x22 case.”

    Action/result: Updated compare_capture() to compose extra expected sprites.

  87. Reported: “I’ll also update the unit-test coverage list so the newly promoted cases are tracked by name. Small bookkeeping, but it helps prevent accidental loss of these probes later.”

    Action/result: Added explicit test coverage for the nine new probe case ids and raised the minimum case-count assertion.

  88. Reported: “The probe catalog now names all nine new cases explicitly. I’m running the local tests and a quick capture comparison path check before asking QEMU to revalidate the corrected 0x22 fixture.”

    Action/result: python3 -B -m unittest tests.test_logic_interpreter_probe passed again.

  89. Reported: “Local tests are green. I’ll rerun only the corrected 0x22 fixture now, which should be fast relative to the full nine-case sweep.”

    Action/result: The sandbox again blocked the first QEMU VNC bind attempt.

  90. Reported: “Same socket restriction on the single rerun; I’ll rerun it with approval.”

    Action/result: Reran the corrected 0x22 fixture with elevated QEMU permission.

  91. Reported: “The corrected 0x22 probe matches. I’m going to promote the QEMU-backed opcodes in the evidence generator now, including the nuance that 0x22 clears bits without immediately removing the current draw entry.”

    Action/result: Added QEMU evidence entries for 0x22, 0x24, 0x28, 0x7f, 0x82, 0x93, 0x94, 0x9b, and 0xaf.

  92. Reported: “The evidence map is updated. Next I’ll regenerate the mdBook evidence chapter and run its consistency check, then I’ll update the narrative notes with the exact commands and outcomes.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md and ran python3 -B tools/logic_opcode_evidence.py --check successfully.

  93. Reported: “The generated evidence chapter and its check both pass. I’m now recording the reverse-engineering trail: the failed first hypothesis for 0x22, the corrected fixture, and which opcodes moved to runtime-backed evidence.”

    Action/result: Updated clean-room notes, compatibility testing docs, and current status with the new probe evidence.

  94. Reported: “Documentation is updated. I’m running the normal validation pass now: full Python tests, evidence check, mdBook build, and whitespace diff check.”

    Action/result: The first full test run exposed that the documentation coverage test still recognized only the older paragraph-style opcode entries.

  95. Reported: “The book, evidence check, and whitespace check passed, but the full test suite exposed a doc-coverage parser regression: it no longer recognizes the tabled opcode entries in logic_bytecode.md. I’m going to adjust that test to understand the current table format rather than weakening coverage.”

    Action/result: Updated the coverage test to accept documented opcode rows in the current Markdown table format.

  96. Reported: “The coverage test was still looking for the older bold paragraph heading pattern only. I’m updating it to accept both that historical format and the current Markdown table rows, while still requiring every opcode label to appear.”

    Action/result: tests/test_logic_doc_coverage.py now accepts both paragraph headings and table rows.

  97. Reported: “The coverage test now recognizes table rows as documented opcode entries. I’m rerunning the full validation pass to make sure that was the only failure.”

    Action/result: python3 -B -m unittest discover -s tests passed 91 tests; mdbook build docs and git diff --check also passed.

  98. Reported: “Everything now validates: 91 Python tests, mdBook build, and diff check are clean. I’m doing a final status/diff pass so the wrap-up is precise and doesn’t hide generated debris.”

    Action/result: Confirmed the modified tracked files and removed tools/__pycache__/.

  99. Reported: “I’ll continue from the freshly committed logic-probe work. I’m going to scan the evidence matrix for still source-backed opcodes and pick another cluster that can be validated through visible state or script-observable getters.”

    Action/result: Confirmed the worktree only had the known generated/input directories and that the opcode evidence matrix was current.

  100. Reported: “The worktree is clean except the known generated/input directories, and the evidence matrix is current. My first grep had shell quoting trouble around backticks, so I’m switching to a small local parse of the generated matrix.”

    Action/result: Replaced the failing backtick-sensitive scan with simpler source-backed and dispatch-smoke matrix scans.

  101. Reported: “The current harness already has good coverage for variables, strings, inventory markers, object positions, and several draw paths. I’m checking the remaining source-backed rows now with simpler shell quoting; likely next targets are object bitfield helpers or small global/object field actions that can be observed indirectly.”

    Action/result: Identified 0x1f and the object field +0x23 family 0x48..0x4b as practical next targets.

  102. Reported: “The best near-term candidates are object/state opcodes, not UI/save/sound paths. I’m going to focus on opcodes around 0x2d..0x50 where disassembly says they change object bits or fields; some can be made visible via drawing, getters, or movement side effects.”

    Action/result: Reviewed object-state docs and movement harness context for observable probe options.

  103. Reported: “A useful small batch is emerging: variable-backed load_view (0x1f) can be validated by drawing the loaded view, while 0x49 and 0x4b can at least be value-probed for their documented flag-clearing operand. I’ll add those, plus dispatch-smoke cases for 0x48 and 0x4a so the whole +0x23 setup family has runtime coverage.”

    Action/result: Chose a five-case follow-up batch covering 0x1f and 0x48..0x4b.

  104. Reported: “I’m going to add a small bit of flexibility to the logic probe harness first: most cases preload view 11, but the load_view_var probe must start without that preload or it wouldn’t prove anything. Then I’ll add the five cases.”

    Action/result: Added the optional preload_view_no hook to tools/logic_interpreter_probe.py.

  105. Reported: “The preload hook is in. I’m adding a tiny local condition helper for ‘flag is not set,’ then the new runtime cases in the existing case catalog.”

    Action/result: Added not_flag_set_condition().

  106. Reported: “Now I’m inserting the cases themselves: one load_view_var value probe, two +0x23 mode actions that clear a flag as documented, and two dispatch-smoke cases for the no-flag mode variants.”

    Action/result: Added load_view_var_allows_following_draw, object_field_23_mode0_dispatch_smoke, object_field_23_mode1_clears_flag, object_field_23_mode3_dispatch_smoke, and object_field_23_mode2_clears_flag.

  107. Reported: “The new cases are in. I’m updating the unit test’s named coverage list so these probes stay pinned in the default catalog.”

    Action/result: Updated tests/test_logic_interpreter_probe.py to require the five new case ids.

  108. Reported: “I’ll run the local harness tests before QEMU. If any of these bytecode snippets are malformed, it’s cheaper to catch it here.”

    Action/result: python3 -B -m unittest tests.test_logic_interpreter_probe passed.

  109. Reported: “Local tests pass. I’ll run the five new cases against the original engine now; if the VNC bind hits the sandbox again I’ll rerun with approval.”

    Action/result: The first QEMU run was blocked by the sandbox VNC bind restriction.

  110. Reported: “The sandbox blocked the VNC socket as expected. I’ll rerun the same batch with QEMU permission.”

    Action/result: Reran the five-case batch with elevated QEMU permission.

  111. Reported: “The QEMU snapshot batch is running. While it does that, I’ll prepare the evidence-map changes so they’re ready if the cases match.”

    Action/result: Reviewed the evidence generator and existing docs while the QEMU batch ran.

  112. Reported: “The docs already have source notes for 0x1f and 0x48..0x4b; this batch is specifically trying to convert part of that into observed engine behavior. I’m checking the QEMU run now.”

    Action/result: The batch build/logic-interpreter-probes/batches/load_view_field23_001.json matched all five cases with 0 mismatches and 0 errors.

  113. Reported: “All five matched. I’ll promote 0x1f, 0x49, and 0x4b to QEMU-validated evidence, and mark 0x48/0x4a as dispatch-smoke since those cases prove runtime execution and operand consumption but not the hidden +0x23 byte value directly.”

    Action/result: Updated tools/logic_opcode_evidence.py accordingly.

  114. Reported: “The evidence generator is updated. I’m regenerating the matrix and checking it, then I’ll record the commands and results in the clean-room docs.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md and checked it successfully.

  115. Reported: “The matrix regenerated cleanly. I’m updating the narrative docs now: compatibility commands, chronological clean-room notes, current status, and the progress log for the updates from this run.”

    Action/result: Updated the clean-room notes, compatibility testing page, and current status.

  116. Reported: “I’m appending a short dated evidence section and compatibility snippet for this batch. The key distinction in the docs will be ‘value-backed’ for 0x1f, 0x49, 0x4b versus ‘dispatch-smoke’ for 0x48, 0x4a.”

    Action/result: Added the dated evidence section and compatibility command.

  117. Reported: “The compatibility docs now have the replay command. I’m updating current status so the next session won’t repeat this cluster.”

    Action/result: Current status now mentions the 0x1f and 0x48..0x4b follow-up batch.

  118. Reported: “I’m also adding a couple of concise notes in the opcode chapter itself, so readers of the spec see what was runtime-confirmed without having to jump to the evidence matrix.”

    Action/result: Added concise QEMU evidence notes to docs/src/logic_bytecode.md.

  119. Reported: “Docs are updated. I’m running the validation pass now: full tests, evidence consistency, mdBook build, and diff whitespace checks.”

    Action/result: python3 -B -m unittest discover -s tests passed 91 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check also passed.

  120. Reported: “Everything validates: 91 tests, evidence check, mdBook, and whitespace checks are clean. I’m doing final status and diff review, then I’ll wrap this pass.”

    Action/result: Confirmed the diff is limited to logic-probe source, evidence generation, tests, and documentation; generated/input directories remain untracked.

  121. Reported: “I’ll continue with the object bit/control path I recommended: first I’m checking the current uncommitted pass so I can build on it cleanly rather than trampling the latest probe work.”

    Action/result: Confirmed the previous variable-view/object-field probe pass was still uncommitted and read AGENTS.md for the project rules.

  122. Reported: “The previous pass is still uncommitted, and I’m going to extend it rather than split the thread artificially. The most promising value probe is 0x44: set the collision-skip bit with 0x43, clear it with 0x44, then prove collision blocking returns during movement.”

    Action/result: Inspected the object movement harness and existing collision-skip evidence.

  123. Reported: “The movement harness has exactly the shape we need, but it only has a ‘set skip bit’ helper. I’m adding the companion 0x44 encoder plus a filtered movement case that sets then clears the bit and should collide again.”

    Action/result: Added clear_object_bit_0200_action() to tools/qemu_fixture.py.

  124. Reported: “The raw encoder is in. Now I’m threading a clear-skip option through the movement case model and adding the concrete collision-restored case.”

    Action/result: Added moving_clear_skip_collision, filtered --case support, and case move_collision_clear_skip_bit_blocks_again to tools/object_movement_probe.py.

  125. Reported: “The tests now cover the new encoder, filtered movement case loading, and the new collision-restored case. I’ll run the focused tests, then the single QEMU case.”

    Action/result: Updated tests/test_qemu_fixture.py and tests/test_object_movement_probe.py; focused tests passed.

  126. Reported: “Focused local tests pass. I’m running just the new movement probe in QEMU now; the new --case filter avoids rerunning the whole movement suite.”

    Action/result: The first QEMU run was blocked by the sandbox VNC bind restriction.

  127. Reported: “The QEMU VNC bind hit the sandbox restriction. I’ll rerun the same single-case probe with permission.”

    Action/result: Reran the single-case movement probe with elevated QEMU permission.

  128. Reported: “The 0x44 probe matched: after setting and then clearing the collision-skip bit, the object is blocked by the obstacle again. I’m promoting 0x44 from dispatch-smoke to QEMU-validated evidence.”

    Action/result: QEMU batch build/object-movement-probes/batches/clear_skip_bit_001.json matched with 1 match, 0 mismatches, and 0 errors.

  129. Reported: “Evidence metadata is updated. I’ll regenerate the generated matrix, then update the docs with the movement batch command and result.”

    Action/result: Added 0x44 to QEMU-validated evidence and removed it from dispatch-smoke evidence.

  130. Reported: “The matrix now treats 0x44 as QEMU-validated. I’m adding the clean-room evidence trail and a short spec note explaining what the new collision-restored probe proves.”

    Action/result: Regenerated and checked docs/src/logic_opcode_evidence.md.

  131. Reported: “I’m patching the docs in the same evidence-first style: one dated note in the clean-room log, one compatibility command, and one concise semantics sentence in the object/graphics model.”

    Action/result: Updated docs/src/graphics_object_pipeline.md and docs/src/logic_bytecode.md with the 0x44 observation.

  132. Reported: “The spec now records the behavior. I’m adding the reproducible QEMU command to the compatibility chapter next.”

    Action/result: Added the clear_skip_bit_001.json QEMU command to docs/src/compatibility_testing.md.

  133. Reported: “Compatibility has the new command. I’m updating current status and the clean-room notes with the exact batch result.”

    Action/result: Updated docs/src/current_status.md.

  134. Reported: “I’m adding a dated clean-room entry rather than folding this into the older movement section, because it’s a new QEMU observation made in this pass.”

    Action/result: Added the dated 0x44 clean-room section to docs/src/clean_room_executable_notes.md.

  135. Reported: “The clean-room notes now include the exact 0x44 batch and result. I’m adding progress-log entries for this continuation before the final validation run.”

    Action/result: Appended progress-log entries for the 0x44 probe pass.

  136. Reported: “Progress log is updated. I’m running the full validation sweep now, including the movement tests touched by this pass.”

    Action/result: python3 -B -m unittest discover -s tests passed 93 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check also passed.

  137. Reported: “Validation passed: 93 tests now, evidence check, mdBook build, and whitespace check are all clean. I’m doing a final status/diff pass and adding the validation note to the progress log.”

    Action/result: Reviewed the final status and diff summary for the combined variable-view/object-field and 0x44 probe pass.

  138. Reported: “I’ll keep going in the same object-control lane. Since there’s already a validated uncommitted batch, I’m going to look for one more nearby bit action that can be promoted from dispatch-smoke to value-backed evidence without dragging us into UI or save/restore behavior.”

    Action/result: Reviewed remaining object/control bit documentation and the current uncommitted worktree.

  139. Reported: “The next likely value-backed target is the horizon-exempt bit 0x0008: docs say 0x3d sets it and 0x3e clears it, and the placement helper clamps objects against the horizon when the bit is clear. I’m going to add two small visible placement probes around that.”

    Action/result: Chose horizon-like placement probes targeting 0x3d, 0x3e, and 0x3f.

  140. Reported: “I’m adding three horizon placement cases: default bit-clear clamping proves the horizon global is active, 0x3d keeps the object above the horizon, and 0x3e restores the clamp after the bit was set. If these pass, they give us clean value evidence for 0x3d, 0x3e, and 0x3f.”

    Action/result: Added horizon_clamps_object_when_bit_clear, horizon_exempt_bit_keeps_object_above_horizon, and horizon_clear_exempt_bit_restores_clamp.

  141. Reported: “The horizon cases are in the probe catalog. I’ll pin them in the local test list and run the focused harness tests before QEMU.”

    Action/result: Updated tests/test_logic_interpreter_probe.py to require the three new case ids.

  142. Reported: “I’ll run the focused logic probe unit tests now. If those pass, the bytecode is at least structurally sane.”

    Action/result: python3 -B -m unittest tests.test_logic_interpreter_probe passed.

  143. Reported: “The focused unit tests pass. I’ll run these three horizon cases in QEMU now; if the baseline expectation is wrong, the capture comparison should give us the actual position.”

    Action/result: The first QEMU run was blocked by the sandbox VNC bind restriction.

  144. Reported: “The usual QEMU VNC bind was blocked by the sandbox. I’ll rerun the same three-case batch with permission.”

    Action/result: Reran the horizon-bit batch with elevated QEMU permission.

  145. Reported: “The horizon-bit batch is running from a snapshot. I’ll let it complete and then either promote the opcodes or adjust the expected baseline if QEMU tells us the clamp differs.”

    Action/result: Waited for the snapshot-backed QEMU run to complete.

  146. Reported: “All three horizon probes matched. That gives us QEMU-backed behavior for 0x3f setting the horizon, 0x3d exempting the object from the clamp, and 0x3e restoring the clamp. I’m updating the evidence matrix now.”

    Action/result: QEMU batch build/logic-interpreter-probes/batches/horizon_bits_001.json matched with 3 matches, 0 mismatches, and 0 errors.

  147. Reported: “Evidence metadata now promotes 0x3d, 0x3e, and 0x3f. I’ll regenerate the matrix and then patch the docs with the exact QEMU batch and behavior.”

    Action/result: Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  148. Reported: “The generated evidence matrix is current. I’m adding the horizon-bit observations to the prose docs now: the object pipeline, opcode chapter, compatibility command, current status, and the chronological clean-room notes.”

    Action/result: Began documenting the horizon-bit QEMU observations.

  149. Reported: “The placement text has slightly different wording than I expected. I’ve got the exact paragraph now and will insert the horizon QEMU note there.”

    Action/result: Added the horizon clamp note to docs/src/graphics_object_pipeline.md.

  150. Reported: “I’m adding the same observation to the opcode chapter beside the object-bit table, keeping it short and tied to the generated probe names.”

    Action/result: Added the 0x3d/0x3e/0x3f QEMU note to docs/src/logic_bytecode.md.

  151. Reported: “Next I’m adding the exact horizon batch command to the compatibility chapter so the result can be replayed later.”

    Action/result: Added the horizon-bit batch command to docs/src/compatibility_testing.md.

  152. Reported: “Compatibility docs have the replay command. I’m updating current status so the horizon-bit evidence is visible in the ‘where are we now’ page.”

    Action/result: Added the horizon-bit finding to docs/src/current_status.md.

  153. Reported: “I’m adding the dated clean-room note for the horizon probes with the exact commands and observed match count.”

    Action/result: Added the horizon-bit placement section to docs/src/clean_room_executable_notes.md.

  154. Reported: “Progress log is updated for the horizon pass. I’m running the full suite and documentation checks now.”

    Action/result: python3 -B -m unittest discover -s tests passed 93 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check also passed.

  155. Reported: “Everything validates again: 93 tests, evidence check, mdBook, and diff whitespace. I’m adding the final validation entry and doing one last status review.”

    Action/result: Recorded the validation result for the horizon-bit pass.

  156. Reported: “I’ll keep going on the nearby object/control bits, using the same pattern as the horizon probes: make the object state visible in a tiny generated fixture, run it through QEMU, then promote only the behavior that actually matches.”

    Action/result: Selected action 0x38 (clear_object_bit_0004) as the next target because its visible effect can be tested through priority derivation during object placement.

  157. Reported: “The overlay tests give us a nice pattern for this one: put a control-6 background under a fixed-priority object, clear the fixed-priority bit, and see whether the original engine goes back to the Y-derived priority.”

    Action/result: Planned a fixture that fixes object priority/control to 5, clears bit 0x0004, places the object at baseline 80, and expects the Y-derived priority 7.

  158. Reported: “The harness now accepts custom picture payloads and expected priority values, which makes this fixed-priority probe small instead of needing a separate one-off tool.”

    Action/result: Extended tools/logic_interpreter_probe.py to allow custom picture payloads and expected priority values in generated cases.

  159. Reported: “The 0x38 probe is in. I’m running the focused unit test first so any fixture-building mistake shows up before we spend a QEMU run on it.”

    Action/result: Added the clear_fixed_priority_bit_uses_derived_priority case and the matching unit-test expectation.

  160. Reported: “The focused test passes. I’m moving to the original-engine run now; if QEMU agrees, 0x38 can move out of dispatch-smoke status.”

    Action/result: Began the QEMU validation attempt for the one-case fixed-priority batch.

  161. Reported: “I’ll pick up from the fixed-priority probe that was interrupted by the sandboxed VNC bind. First I’m going to verify the current file state, then I’ll rerun that one QEMU case with the needed permission and fold the result back into the docs.”

    Action/result: Verified the working tree and confirmed 0x38 was still listed as dispatch-smoke evidence.

  162. Reported: “The working tree matches the handoff summary, and the fixed-priority probe is already wired in but still listed only as dispatch-smoke evidence. I’m rerunning that single case now through QEMU so we can see whether 0x38 deserves promotion.”

    Action/result: Reran the fixed-priority QEMU batch with elevated QEMU permission after the sandbox rejected the VNC bind.

  163. Reported: “That matched cleanly: one QEMU case, zero mismatches. I’m going to promote 0x38 from ‘dispatch returned’ to ‘behavior validated’, then regenerate the evidence table and document exactly what the probe proved.”

    Action/result: QEMU report build/logic-interpreter-probes/batches/fixed_priority_bit_001.json matched with 1 match, 0 mismatches, and 0 errors.

  164. Reported: “Evidence metadata is patched. I’m regenerating the generated opcode evidence chapter next, then I’ll make the prose changes around the same observation.”

    Action/result: Promoted 0x38 in tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  165. Reported: “The generated evidence file is refreshed. I’m reading the exact local paragraphs now so the new fixed-priority note lands with the related object-bit and compatibility material instead of becoming a stray footnote.”

    Action/result: Located the relevant sections in the opcode, object pipeline, compatibility, clean-room notes, and current status chapters.

  166. Reported: “I found the doc anchors. The new note is intentionally narrow: 0x38 clears the fixed-priority bit, and the original engine then derives priority from Y, enough to draw over a control-6 synthetic background at baseline 80.”

    Action/result: Updated docs/src/logic_bytecode.md with the QEMU-validated visible effect of 0x38.

  167. Reported: “Opcode prose is updated. I’m now tying the same behavior into the object pipeline chapter and the compatibility replay chapter.”

    Action/result: Added the fixed-priority clear-bit note to docs/src/graphics_object_pipeline.md.

  168. Reported: “The object pipeline now records the visible 0x38 effect next to the flag-bit map. I’m adding the exact one-case QEMU command to the compatibility chapter so it can be replayed later.”

    Action/result: Added the fixed-priority batch command and summary to docs/src/compatibility_testing.md.

  169. Reported: “The replay command is in. I’m adding the chronological clean-room note next, including the initial sandbox failure and the successful elevated QEMU run because both are part of the evidence trail.”

    Action/result: Added the fixed-priority clear-bit QEMU section to docs/src/clean_room_executable_notes.md.

  170. Reported: “The clean-room log now has the fixed-priority probe as a dated section. I’m updating the handoff status page so the latest confirmed object-bit behavior is visible at a glance.”

    Action/result: Added the fixed-priority finding to docs/src/current_status.md.

  171. Reported: “The progress log is caught up through the docs edits. I’m running the compatibility/unit checks, the generated evidence check, mdBook build, and whitespace check now.”

    Action/result: python3 -B -m unittest discover -s tests passed 93 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check also passed.

  172. Reported: “All checks passed: 93 unit tests, evidence check, mdBook, and diff whitespace. I’m recording that validation in the progress log, then I’ll do a final status/diff review.”

    Action/result: Recorded the validation result for the fixed-priority clear-bit pass.

  173. Reported: “I’ll keep advancing the object-state opcode evidence, staying close to the current uncommitted work. The next useful pocket is the remaining bitfield actions that are still only dispatch-smoke: I’ll inspect the harness support and pick ones whose effects can be made visible without inventing too much scaffolding.”

    Action/result: Reviewed the current working tree, logic-interpreter probe cases, evidence metadata, and existing docs for the remaining dispatch-smoke object opcodes.

  174. Reported: “The remaining smoke list splits into two kinds: bits that affect drawing/update-list behavior, and fields/modes that can be observed through existing getters or movement outcomes. I’m narrowing on the low-risk ones first: 0x4c/0x4e and the bit toggles that have a visible movement or placement consequence.”

    Action/result: Inspected existing logic and movement probe support to find observable paths for object-state opcodes.

  175. Reported: “There are already getters for some fields, so we can convert several smoke cases into ordinary logic probes without touching the renderer. I’m going to add focused cases for 0x4c and 0x4e, and then look for a clean way to make 0x40..0x42 or 0x58..0x59 observable.”

    Action/result: Confirmed that 0x4e can be observed through motion behavior, while 0x4c still lacks a direct visible assertion.

  176. Reported: “0x4e has a natural visible test: start an autonomous mode, immediately clear mode byte +0x22, and confirm the object stays put during the later update cycles. I’m also reading the acceptance-test docs because bits 0x0100, 0x0800, and 0x0002 appear to influence movement against control pixels.”

    Action/result: Read the movement pipeline and control/priority acceptance sections to confirm that 0x4e can be validated with a focused movement fixture.

  177. Reported: “The movement docs confirm a few of the remaining bits are not just hidden state; they sit inside the movement acceptance predicate. For this pass I’m going to add one focused 0x4e movement case first, since it is straightforward and should either match or give us a precise correction.”

    Action/result: Chose a one-case QEMU movement probe for 0x4e.

  178. Reported: “I’m going to make a small fixture addition rather than hand-assembling bytes in the movement harness. That keeps the evidence trail readable: clear_object_field_22_and_global_action() will map directly to opcode 0x4e.”

    Action/result: Added clear_object_field_22_and_global_action() to tools/qemu_fixture.py.

  179. Reported: “The opcode helper is in place. Next I’m adding the movement case that starts random motion on object 0 and immediately applies 0x4e, expecting the later QEMU capture to remain at the starting position.”

    Action/result: Added movement case clear_field_22_after_random_motion_stops_motion and a new random_motion_then_clear_4e path in tools/object_movement_probe.py.

  180. Reported: “The movement harness now has a random_motion_then_clear_4e path. I’m updating the tests so the new case and byte helper are pinned before I run QEMU.”

    Action/result: Added the new case id to tests/test_object_movement_probe.py.

  181. Reported: “The case list test is updated. I’m adding the direct byte-level fixture test for the new 0x4e helper too, so the generated logic is self-checking.”

    Action/result: Imported and tested clear_object_field_22_and_global_action() in tests/test_qemu_fixture.py.

  182. Reported: “The helper test area already has the autonomous-motion byte encodings. I’m adding 0x4e there beside the related motion-control opcodes.”

    Action/result: Added the expected byte sequence 4e 02 to the autonomous motion fixture test.

  183. Reported: “The fixture tests now pin the new action byte sequence. I’ll run the focused unit tests before spending a QEMU run.”

    Action/result: python3 -B -m unittest tests.test_object_movement_probe tests.test_qemu_fixture passed 33 tests, and python3 -B -m unittest tests.test_logic_interpreter_probe passed 6 tests.

  184. Reported: “The focused tests pass. I’m running the single original-engine movement case now; if QEMU agrees, 0x4e can move from dispatch-smoke to behavior validated for clearing the active motion mode.”

    Action/result: Ran the focused 0x4e movement probe through QEMU with elevated VNC permission.

  185. Reported: “QEMU matched: the object stayed at its start after 0x4e cleared the just-started random-motion mode. I’m promoting 0x4e in the evidence matrix and then documenting the exact replay command and interpretation.”

    Action/result: QEMU report build/object-movement-probes/batches/clear_field_22_001.json matched with 1 match, 0 mismatches, and 0 errors.

  186. Reported: “0x4e is now marked as a QEMU-backed action in the evidence metadata. I’m regenerating the generated evidence chapter.”

    Action/result: Promoted 0x4e in tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  187. Reported: “The generated matrix is refreshed. I’m patching the prose docs now: opcode row note, movement pipeline note, compatibility replay command, clean-room chronology, and current status.”

    Action/result: Began documenting the 0x4e QEMU result in prose docs.

  188. Reported: “The documentation shape is clear. I’m adding the 0x4e result as ‘motion-mode clearing is visible’, while keeping the global side effect sourced to static analysis until we build a direct flag/global probe.”

    Action/result: Updated docs/src/logic_bytecode.md with the QEMU-validated visible 0x4e effect.

  189. Reported: “The opcode row now names the exact QEMU-visible part of 0x4e. I’m adding the same observation to the movement chapter near the mode-control description.”

    Action/result: Began updating docs/src/graphics_object_pipeline.md.

  190. Reported: “The random-motion section wording is a little different than the snippet I had in mind, so I’ve got the exact paragraph now and will insert the 0x4e note there.”

    Action/result: Added the 0x4e random-motion stop note to docs/src/graphics_object_pipeline.md.

  191. Reported: “The movement chapter now records the 0x4e stop behavior. I’m adding the replay command to the compatibility chapter beside the existing single-case movement batches.”

    Action/result: Located the single-case movement-batch section in docs/src/compatibility_testing.md.

  192. Reported: “The compatibility chapter has a good slot right after the 0x44 single-case movement batch. I’m adding the 0x4e replay command there.”

    Action/result: Added the 0x4e movement replay command and match summary to docs/src/compatibility_testing.md.

  193. Reported: “The replay command is documented. I’m adding the chronological clean-room section for the new helper, harness case, QEMU run, and evidence promotion.”

    Action/result: Added the 0x4e clean-room evidence section to docs/src/clean_room_executable_notes.md.

  194. Reported: “The dated evidence note is in. I’m updating the status page with the new confirmed motion-control behavior.”

    Action/result: Added the 0x4e finding to docs/src/current_status.md.

  195. Reported: “The status page is current. I’m catching the progress log up for this continuation, including the QEMU result and evidence promotion, then I’ll run the full validation suite.”

    Action/result: Began appending the progress-log entries for this continuation.

  196. Reported: “The progress log is up to date through the 0x4e work. I’m running the full test suite, evidence check, mdBook build, and whitespace check now.”

    Action/result: python3 -B -m unittest discover -s tests passed 93 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check also passed.

  197. Reported: “All four checks passed. I’m adding that validation note to the progress log, then I’ll do the quick mdBook/whitespace rerun because the log itself changed.”

    Action/result: Recorded the validation result for the 0x4e motion-mode clearing pass.

  198. Reported: “Got it: I’ll keep going through the whole sequence and leave the commit for you.”

    Action/result: Continued the movement/object-bit reverse-engineering pass without committing.

  199. Reported: “The existing renderer gives us a direct way to make control classes visible. I’m going to target 0x58/0x59 first because the disassembly suggests they gate special control acceptance, then use QEMU only to confirm the visible result.”

    Action/result: Chose synthetic control-screen movement probes for the object-bit handlers.

  200. Reported: “I’m going to add explicit fixture helpers for the object-bit opcodes instead of hand-assembling bytes in the probe cases.”

    Action/result: Added reusable logic-action helpers for 0x58, 0x59, 0x40, 0x41, 0x42, and rectangle bounds 0x5a.

  201. Reported: “The 0x58/0x59 byte helpers are in, and I’m wiring the first movement cases around a synthetic control-screen fill.”

    Action/result: Added initial object-movement probe cases for control-class acceptance.

  202. Reported: “The movement cases are wired; I’m adding focused tests so the generated fixture bytes and case registry stay pinned down.”

    Action/result: Added unit-test coverage for the new movement cases.

  203. Reported: “Imports are updated. I’m adding assertions for the exact helper encodings before running the focused tests.”

    Action/result: Added fixture-helper byte-encoding assertions.

  204. Reported: “The tests now cover the helper bytes and the new case ids. I’m running those focused tests before touching QEMU.”

    Action/result: Ran the focused helper/probe tests successfully.

  205. Reported: “Focused tests pass. I’m running the three control-1 acceptance cases through QEMU now.”

    Action/result: Began a targeted QEMU batch for the first control-class movement cases.

  206. Reported: “The QEMU mismatch shows the full-screen control-1 fill is not the right oracle: the engine accepts the move when our expectation said it should block. I’m switching to the rectangle-boundary path the disassembly also points at for 0x58/0x59.”

    Action/result: Reclassified the initial result as a bad probe design and added rectangle-boundary cases.

  207. Reported: “I added a helper for 0x5a rectangle bounds and am replacing the weak control-fill assertions with boundary-crossing movement cases.”

    Action/result: Added 0x5a fixture support and rectangle-boundary movement fixtures.

  208. Reported: “The movement harness now has both the corrected control-1 observations and the rectangle-boundary cases. I’m pinning those names in tests.”

    Action/result: Updated movement-probe tests for the revised case set.

  209. Reported: “Tests are patched. I’m rerunning the focused unit tests before launching the corrected QEMU batch.”

    Action/result: Re-ran the focused unit tests successfully.

  210. Reported: “Focused tests pass. I’m rerunning QEMU with the corrected control-1 observations and the new rectangle-boundary cases.”

    Action/result: Ran the revised QEMU batch.

  211. Reported: “The corrected control-1 cases all matched, but the rectangle case still reached the target. I’m going back to static inspection of the boundary branch before trying another fixture tweak.”

    Action/result: Paused the failed rectangle hypothesis and inspected the movement/boundary code path.

  212. Reported: “Interesting: rectangle-boundary also reached the target, which means the bit probably gates a specific movement mode or countdown path rather than all object motion. I’m dumping the surrounding branch now.”

    Action/result: Disassembled the surrounding movement branch.

  213. Reported: “The static dump shows the boundary check is behind the pre-movement mode dispatcher and only for objects with direction active, active/update bits, and clear 0x0002. I’ll adapt the probe to that path.”

    Action/result: Identified the gating conditions for the rectangle-boundary helper.

  214. Reported: “Before moving on, I’m inspecting the actual branch target so the next fixture is based on code shape instead of another guess.”

    Action/result: Continued static inspection of the rectangle-boundary target and object-field tests.

  215. Reported: “Aha, the bit 0x0002 branch lives in the pre-movement mode dispatcher. I’m changing the rectangle-boundary cases to use the countdown-gated autonomous path that reaches that branch.”

    Action/result: Reworked rectangle-boundary probes to drive the relevant update path.

  216. Reported: “For the rectangle cases I’ll reuse the existing one-shot autonomous mode machinery, because that already gives the object an active direction across cycles.”

    Action/result: Connected the rectangle-boundary cases to the autonomous movement machinery.

  217. Reported: “The rectangle cases now use countdown-gated motion and compare the final object position. I’m running the focused tests again.”

    Action/result: Re-ran focused unit tests after the rectangle-case rewrite.

  218. Reported: “Focused tests pass. I’m running the three rectangle-boundary cases through QEMU.”

    Action/result: Ran the corrected rectangle-boundary QEMU batch.

  219. Reported: “That worked: all three rectangle-boundary cases matched. I’m recording that as the positive evidence for 0x58/0x59, then moving to the control-class bits 0x40/0x41/0x42.”

    Action/result: Promoted the rectangle-boundary result as QEMU evidence for 0x58/0x59.

  220. Reported: “The acceptance helper is clear enough now: control class 2 checks bit 0x0100, class 3 checks bit 0x0800, and 0x42 clears both. I’m adding QEMU cases that follow that exact branch.”

    Action/result: Added class-2/class-3 object-control cases for 0x40, 0x41, and 0x42.

  221. Reported: “The 0x40/0x41/0x42 helpers are in. I’m adding case coverage for control classes 2 and 3 with priority 14 so the scan actually runs.”

    Action/result: Added fixture helpers and movement-probe cases for the control-class bits.

  222. Reported: “The control-class cases are added; I’m expanding the tests to pin the helper encodings and case ids.”

    Action/result: Updated unit tests for the control-class probes.

  223. Reported: “Tests now include the new control-class cases. I’m running the focused suite before QEMU.”

    Action/result: Ran the focused unit tests.

  224. Reported: “Focused tests pass. I’m running the four control-class QEMU cases for 0x40/0x41/0x42.”

    Action/result: Started the control-class QEMU batch.

  225. Reported: “The first 0x0100 case did not block, so I’m inspecting the acceptance path again instead of forcing the expected output.”

    Action/result: Returned to static inspection after the QEMU mismatch.

  226. Reported: “Found the missing piece: I used priority 15, and the code skips the control scan at that priority. I’m lowering these probes to priority 14 and expecting rejection to leave the object visible but stationary.”

    Action/result: Corrected the control-class probes to avoid the priority-15 bypass.

  227. Reported: “The control-class expectations are now tied to the priority-14 scan path. I’m rerunning the focused tests before another QEMU pass.”

    Action/result: Re-ran the focused unit tests.

  228. Reported: “Focused tests pass again. I’m running the combined control/rectangle QEMU batch.”

    Action/result: Ran a larger QEMU batch for control and rectangle behavior.

  229. Reported: “The control-1 rejection now matches, but setting bit 0x0002 did not make the object visible or moving there. I’m going to document 0x58 via the rectangle-boundary evidence and keep the control-1 branch as a separate observed behavior.”

    Action/result: Split the 0x58 positive evidence from the class-1 control-screen observations.

  230. Reported: “The 0x58 case also has no visible object under full control-1 fill, so I’m renaming those cases to describe what they actually prove rather than what I hoped they would prove.”

    Action/result: Renamed the control-1 cases to reflect observed hidden/no movement behavior.

  231. Reported: “Names and expectations now reflect the real class-1 observation. I’m running a clean batch with the renamed cases and the control-class bits.”

    Action/result: Prepared the corrected acceptance batch.

  232. Reported: “The corrected acceptance batch is running. If it matches, I’ll promote the class-2/class-3 results as evidence for 0x40/0x41/0x42.”

    Action/result: Ran the corrected control acceptance batch.

  233. Reported: “Great: control-1 and rectangle matched; 0x0100 differs only because I expected a plain picture while the rejected object remains visible at the start point. I’m updating the oracle to the actual static branch result.”

    Action/result: Corrected the class-2/class-3 rejection oracle to visible, stationary objects.

  234. Reported: “For control class 2, 0x0100 blocks movement but leaves the object visible at its starting location. I’m mirroring that expectation for class 3 and rerunning the four-bit batch.”

    Action/result: Updated both class-2 and class-3 blocked expectations.

  235. Reported: “The class-2 and class-3 rejection cases now expect a stationary visible object. Focused tests pass, and I’m rerunning just those four QEMU cases.”

    Action/result: Re-ran the focused unit tests and the four-case QEMU batch.

  236. Reported: “The 0x40/0x41/0x42 batch matched exactly. I’m running the clean control-1 batch too so that observation has its own compact report.”

    Action/result: Captured matching QEMU evidence for the class-2/class-3 control-bit behavior.

  237. Reported: “The control-class-1 clean report matched too. I’m promoting the QEMU evidence metadata for 0x40, 0x41, 0x42, 0x58, and 0x59 now.”

    Action/result: Promoted the matching movement-probe batches in tools/logic_opcode_evidence.py.

  238. Reported: “The opcode table is regenerated with those five opcodes marked as QEMU-backed. I’m patching the prose docs with the static branch and the matching batch names.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md and began prose documentation updates.

  239. Reported: “I’m going to document the control-screen acceptance helper, rectangle-boundary branch, and corrected probe results together so the earlier false starts are preserved as corrections.”

    Action/result: Updated the opcode, movement pipeline, compatibility, and clean-room notes with the corrected evidence.

  240. Reported: “I found exact doc locations. I’m applying the labels first, then the prose can refer to stable names rather than raw addresses.”

    Action/result: Added symbolic labels for the control-acceptance and rectangle-boundary routines.

  241. Reported: “Symbolic labels are updated. I’m patching the opcode chapter next with the verified summaries and evidence names.”

    Action/result: Updated the logic-bytecode chapter rows for 0x40, 0x41, 0x42, 0x58, and 0x59.

  242. Reported: “The opcode rows now include the bit semantics and matching QEMU evidence. I’m updating the object pipeline chapter with the actual branch-level explanation.”

    Action/result: Added the branch-level explanation to docs/src/graphics_object_pipeline.md.

  243. Reported: “The object pipeline now describes priority-15 bypass, control-class bits, and rectangle-boundary gating. I’m adding replay commands to the compatibility chapter.”

    Action/result: Added replay commands for the matched QEMU batches.

  244. Reported: “Compatibility docs now have the three replay batches. I’m adding the chronological clean-room note with the false starts and corrections.”

    Action/result: Added the detailed chronology to docs/src/clean_room_executable_notes.md.

  245. Reported: “The clean-room notes now preserve the failed assumptions, the corrected static reading, and the matching batches. I’m updating the status page, then I’ll continue to the remaining object animation/update opcodes.”

    Action/result: Updated docs/src/current_status.md with the new control/priority finding.

  246. Reported: “That batch is documented. I’m moving on to 0x46-0x4c, starting from the handler offsets and the update loop before making any new QEMU cases.”

    Action/result: Began static inspection of the object animation/update handlers.

  247. Reported: “The handler offset mismatch is a useful warning: the table is image-relative, while the file has an MZ header. I’m normalizing that before trusting any handler disassembly.”

    Action/result: Started checking the executable image/file-offset mapping.

  248. Reported: “I found a compact visible probe for 0x46/0x47/0x49/0x4c, but before running it I’m checking the code path that decrements frame counters and chooses the next cel.”

    Action/result: Added draft animation probe cases but deferred QEMU pending static confirmation.

  249. Reported: “You’re right. The QEMU probes are valuable, but I let them become the search strategy instead of the cross-check. I’ll course-correct: first inspect and label the disassembled routines, derive the behavior from those code paths, and use QEMU only for small validation probes after the static story is clear.”

    Action/result: Changed the active methodology back to disassembly-first analysis at the user’s direction.

  250. Reported: “First I’m sorting out the offset convention so every handler label lands on the right bytes. Then I’ll walk the 0x46-0x4c handlers and the per-cycle update callback directly from the executable.”

    Action/result: Began verifying MZ header/image-offset conventions before continuing handler disassembly.

  251. Reported: “The progress log is caught up through the correction. I’m now normalizing the executable offsets: the opcode table gives loaded-image offsets, while ndisasm -e needs the MZ header included in the file offset.”

    Action/result: Confirmed that handler table addresses are image-relative and map to file offsets by adding the MZ header size.

  252. Reported: “The offset mapping checks out: handlers from AGIDATA.OVL are loaded-image addresses, and in the MZ file the bytes are at image_offset + 0x200. I’ve confirmed that on the 0x5a, 0x58, and animation-control handlers.”

    Action/result: Used focused ndisasm slices to verify the image/file mapping and handler starts.

  253. Reported: “The static path is now much clearer. The per-cycle object updater at image 0x0563 scans active objects, decrements byte +0x20 when bit 0x0020 is set, calls the frame-mode helper at 0x48b3 when that countdown reaches zero, then reloads +0x20 from +0x1f.”

    Action/result: Identified the frame-timer scan and its reload behavior from disassembly.

  254. Reported: “The frame-mode helper at image 0x48b3 is the missing high-level piece: mode 0 loops frames forward, mode 1 advances forward and stops/sets a flag at the last frame, mode 2 steps backward and stops/sets a flag, and mode 3 loops backward. This gives us a spec-level model for 0x46-0x4c before any emulator check.”

    Action/result: Derived the frame-mode state machine from static disassembly.

  255. Reported: “One important detail from the static path: actions 0x49 and 0x4b set bit 0x1000, and code.object.advance_frame_by_mode consumes that bit by clearing it and returning before the first frame change. So those modes have a one-callback startup delay; the docs need to say that explicitly.”

    Action/result: Added the bit-0x1000 startup-delay behavior to the static model.

  256. Reported: “I’ve added stable labels for the frame timer scan and frame-mode dispatcher, so the rest of the prose can now talk about those routines symbolically rather than as loose addresses.”

    Action/result: Added code.object.frame_timer_update and code.object.advance_frame_by_mode to docs/src/symbolic_labels.md.

  257. Reported: “The opcode chapter now corrects 0x4c: it does not clear +0x20; it copies the variable value into both +0x1f and +0x20. That was exactly the kind of thing the disassembly-first pass was meant to catch.”

    Action/result: Corrected the 0x4c opcode documentation and expanded the frame-timer explanation.

  258. Reported: “I’m adding the static evidence trail now: table offsets, MZ offset convention, focused disassembly slices, and the corrected 0x4c interpretation. Then I’ll let the small QEMU probes validate the derived model rather than discover it.”

    Action/result: Added the static frame-timer/frame-mode analysis section to docs/src/clean_room_executable_notes.md.

  259. Reported: “The clean-room notes now include the course correction and the exact static evidence. I’m tightening the fixture tests for the new helper bytes before running the targeted animation validations.”

    Action/result: Added helper-byte assertions for actions 0x46, 0x47, 0x49, and 0x4c.

  260. Reported: “The helper-byte tests now cover 0x46, 0x47, 0x49, and 0x4c. I’m running the focused Python tests before I ask QEMU to validate the visible frame behavior.”

    Action/result: Ran focused unit tests and the opcode evidence check; both passed.

  261. Reported: “Focused tests passed. Now I’m running only the three frame-timer QEMU cases that follow from the static model: normal mode-1 advance, 0x46 disabling the timer, and 0x47 restoring it.”

    Action/result: Started the frame_timer_001 QEMU validation batch.

  262. Reported: “As expected, the sandbox blocked QEMU’s local VNC bind. I’m rerunning the same narrow validation outside the sandbox so the emulator can open its monitor/display sockets.”

    Action/result: Reran the same QEMU command with approved elevated permission for the object-movement probe.

  263. Reported: “QEMU is running the snapshot batch now. While it works, I’m not adding more guesses; the next edits depend on whether those three cases match the disassembly-derived expectations.”

    Action/result: Waited for the QEMU snapshot batch to complete.

  264. Reported: “The frame-timer QEMU batch matched all three cases. That validates the static model’s visible pieces: 0x4c seeds the countdown, 0x46 suppresses the advance by clearing bit 0x0020, and 0x47 restores it.”

    Action/result: Recorded frame_timer_001 as a matched QEMU batch.

  265. Reported: “I’m promoting only the opcodes directly covered by that batch: 0x46, 0x47, and 0x4c. 0x49 already had QEMU flag-clearing evidence, and this batch will be cited as additional frame-mode evidence in prose rather than overloading the metadata.”

    Action/result: Updated tools/logic_opcode_evidence.py to promote 0x46, 0x47, and 0x4c to QEMU behavior evidence.

  266. Reported: “The evidence metadata now marks 0x46, 0x47, and 0x4c as behavior-validated by frame_timer_001 instead of dispatch-smoke. I’m regenerating the evidence chapter and then I’ll document the QEMU result in prose.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  267. Reported: “The generated evidence chapter is refreshed. I’m adding the replay command and result summary to the compatibility and clean-room docs so the validation trail is reproducible.”

    Action/result: Began documenting the frame-timer QEMU batch in prose docs.

  268. Reported: “I found the compatibility section next to the movement batches. I’m placing frame_timer_001 there and linking it to the exact opcodes it validates.”

    Action/result: Added the frame_timer_001 replay command to docs/src/compatibility_testing.md.

  269. Reported: “The opcode and graphics chapters now include the QEMU-backed frame-timer result. I’m appending the exact batch command/result to the clean-room chronology next.”

    Action/result: Added the frame_timer_001 result to the logic-bytecode, graphics-object-pipeline, and clean-room notes.

  270. Reported: “The clean-room chronology has the frame-timer validation and the sandbox failure/rerun recorded. I’m updating the current status so the ‘next step’ no longer says this is unvalidated.”

    Action/result: Updated docs/src/current_status.md to mark the frame timer model as QEMU-validated for visible mode-1 behavior.

  271. Reported: “Current status is updated. I’m catching the progress log up through the static analysis, the QEMU validation, and the evidence promotion before running the full validation suite.”

    Action/result: Appended progress-log entries for the frame-timer analysis and validation pass.

  272. Reported: “The progress log is current. I’m running the full local compatibility suite, evidence check, mdBook build, and whitespace check now.”

    Action/result: python3 -B -m unittest discover -s tests passed 97 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check also passed.

  273. Reported: “All four checks passed: 97 Python tests, evidence check, mdBook build, and git diff --check. I’m adding that validation note to the progress log, then I’ll do the tiny final mdBook/whitespace rerun because the log changed.”

    Action/result: Recorded the successful validation suite result.

  274. Reported: “I’ll proceed with the disassembly-first next slice: the object frame-mode family around 0x48, 0x4a, and 0x4b. I’ll use QEMU only after the static path says exactly what should happen.”

    Action/result: Started a static-first pass over the frame-mode setup actions and the frame advance helper.

  275. Reported: “I’ve refreshed the repo instructions and current notes. Next I’m reading the movement fixture code so any validation case mirrors the disassembled behavior instead of guessing from screenshots.”

    Action/result: Re-read local harness files and AGENTS/project guidance.

  276. Reported: “Before touching harness code, I’m re-reading the exact action handlers and the frame dispatcher bytes. The goal is to nail down where mode 2 actually completes, since that’s the one most likely to hide an off-by-one misunderstanding.”

    Action/result: Cut and disassembled focused executable slices around image offsets 0x48b3 and 0x6b82.

  277. Reported: “The static read turned up one correction: mode 2 doesn’t just decrement once and stop. It keeps stepping backward and completes when it reaches frame 0, with an immediate-complete shortcut if it starts at frame 0.”

    Action/result: Corrected the frame-mode model before adding runtime probes.

  278. Reported: “For looping modes, I don’t want a flaky ‘capture at just the right cycle’ test. I’m going to make the logic fixture stop the timer after it observes the expected frame, using the already decoded 0x32 object frame read plus 0x46 to clear the timer bit.”

    Action/result: Chose deterministic loop-stopping bytecode for mode 0 and mode 3 validation cases.

  279. Reported: “I’m going to add tiny byte helpers for the exact opcodes the fixture needs, then keep the QEMU cases narrow: mode 0 wraps forward, mode 2 reaches frame 0 and stops, mode 3 wraps backward.”

    Action/result: Planned scoped fixture helper additions for actions 0x48, 0x4a, 0x4b, and 0x32.

  280. Reported: “The fixture helpers are in. I’m wiring them into the movement probe as explicit animation modes rather than making a one-off custom fixture, so future frame-mode cases can reuse the same path.”

    Action/result: Added helper encoders to tools/qemu_fixture.py and threaded animation mode selection through tools/object_movement_probe.py.

  281. Reported: “The movement probe now has three new frame-mode cases. I’m adding unit coverage for the helper encodings and registry entries before I put the original engine through it.”

    Action/result: Added tests for the new helper bytes and movement case IDs.

  282. Reported: “The tests now cover the new byte helpers and all three cases. I’m running the focused Python tests first; if those pass, I’ll run the QEMU batch against the original interpreter.”

    Action/result: Ran focused unit tests for fixture helpers and movement probes.

  283. Reported: “Focused tests passed. I’m running the QEMU validation batch now with the three frame-mode cases against the original engine.”

    Action/result: Started frame_timer_modes_002 through tools/object_movement_probe.py.

  284. Reported: “QEMU has built the snapshot disk and is stepping through the cases now. So far the harness is using the same one-boot snapshot pattern as the earlier movement batches.”

    Action/result: QEMU validation proceeded through the snapshot fixture harness.

  285. Reported: “All three frame-mode cases matched QEMU: mode 0 forward wrap, mode 2 backward completion at frame 0, and mode 3 backward wrap. I’m promoting the opcode evidence and then I’ll record the correction and replay command in the docs.”

    Action/result: Confirmed frame_timer_modes_002 with 3 matches, 0 mismatches, and 0 errors.

  286. Reported: “The evidence metadata is updated: 0x48 and 0x4a are now behavior-backed instead of dispatch-smoke, and 0x4b has both its flag side effect and visible mode-2 completion covered.”

    Action/result: Updated tools/logic_opcode_evidence.py.

  287. Reported: “The generated chapter is refreshed. I’m finding every place where the old ‘mode 2 decrements once’ wording survived so the docs don’t carry a stale hypothesis.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md and searched the docs for old frame-mode wording.

  288. Reported: “I found the places where the older mode-2 wording lives. I’m correcting those and adding the exact QEMU command/result to the compatibility and clean-room notes.”

    Action/result: Began patching prose docs for the corrected mode-2 behavior and frame_timer_modes_002.

  289. Reported: “I’ll pick this back up at the doc-integration step: the code and QEMU probe are in place, so I’m going to finish recording the evidence trail and then run the book/tests checks.”

    Action/result: Resumed after context compaction and continued from the documentation integration step.

  290. Reported: “The scan shows the new code and two doc chapters already changed, with the older mode-2 wording still present in the clean-room chronology and the compatibility/current-status pages not yet caught up. I’m going to patch those together so the evidence trail stays coherent.”

    Action/result: Located remaining docs needing updates.

  291. Reported: “I’m making the doc edits now: correcting the static mode-2 note, adding the exact new QEMU replay command/result, and updating the status page so 0x48/0x4a are no longer described as smoke-only.”

    Action/result: Updated compatibility testing, current status, clean-room notes, and logic-bytecode prose.

  292. Reported: “The main docs are patched. I’m appending the progress-log entries next, including the short updates from this resumed turn so the historical record remains continuous.”

    Action/result: Appended this progress-log block.

  293. Reported: “All core checks passed: 97 tests, evidence check, mdBook build, and whitespace check. One search command tripped over shell backticks in the pattern, so I’m rerunning that search safely and then adding the validation note to the log.”

    Action/result: python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check passed. The corrected safe search found only the historical progress-log mention of the old wording and the corrected clean-room sentence.

  294. Reported: “The safe search only finds the historical progress note and the corrected sentence, so the stale wording is gone from the actual spec prose. I’m logging the validation result and then doing the small final doc/whitespace rerun because the log changed.”

    Action/result: Added the validation result to this progress log.

  295. Reported: “I’ll continue from the post-frame-mode checkpoint. I’m going to re-open the current status and evidence matrix, pick the highest-value source-backed opcode cluster that is still weakly documented, and keep the progress log moving before substantial new work.”

    Action/result: Reviewed current status, git state, recent progress, and evidence gaps after commit a27928a.

  296. Reported: “The evidence scan also caught a stale sentence in logic_bytecode.md that still calls 0x48/0x4a dispatch-smoke. I’ll fix that as part of this pass, then dig into the next weak cluster: the object bitfield/refresh opcodes 0x3a..0x3c, which currently have only smoke evidence.”

    Action/result: Selected the 0x3a..0x3c object bitfield/refresh action cluster for the next source-first investigation.

  297. Reported: “The existing docs already know the direct field writes, but the high-value gap is semantics: bit 0x0010 partitions active objects between update-list roots. I’m reading the surrounding wrappers/list helpers now so the opcode names can graduate from ‘bit twiddles’ toward actual engine behavior.”

    Action/result: Re-read the 0x3a..0x3c, 0x6a54, 0x6a8e, 0x6aab, 0x0307, 0x0358, 0x045e, and 0x0488 source paths.

  298. Reported: “The source read shows 0x3c is broader than its old label implies: it computes the object address from the operand, but then runs the all-list flush, rebuild/draw, and dirty-rectangle refresh path. I’m checking whether that label is baked into tests before renaming it to something more accurate.”

    Action/result: Found no test dependency on refresh_object_helper.

  299. Reported: “There are no tests pinning the old 0x3c label, so I can safely tighten the terminology. I’m going to treat 0x3a/0x3b as list-membership partition toggles and 0x3c as an all-object-list refresh/repaint action, then regenerate the evidence table from the local opcode names.”

    Action/result: Planned the label change to refresh_object_lists.

  300. Reported: “I’m going to edit the label and prose now, not the runtime behavior. The evidence level will stay as QEMU dispatch-smoke until we build a visible fixture, but the specification text can still record the static source semantics precisely.”

    Action/result: Updated tools/disassemble_logic.py, logic_bytecode.md, and graphics_object_pipeline.md with refresh_object_lists semantics.

  301. Reported: “Before editing, I checked the evidence generator: it derives summaries from the opcode labels, so a better 0x3c label will automatically make the table less misleading. I’m applying that targeted terminology update plus the stale 0x48/0x4a sentence fix.”

    Action/result: Removed the stale 0x48/0x4a smoke-only wording from logic_bytecode.md.

  302. Reported: “The label/prose update is in. I’m regenerating the opcode evidence chapter now so the generated matrix uses refresh_object_lists consistently.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  303. Reported: “The regenerated matrix has the new label. One search command again interpreted backticks, but the useful result confirms the generated evidence row changed; I’ll rerun searches with safe quoting when checking cleanup. Next I’m checking the symbolic label map so the update-list helpers have stable names for later interpreter-version comparisons.”

    Action/result: Reviewed docs/src/symbolic_labels.md and confirmed the update-list wrapper helpers were not yet named there.

  304. Reported: “The symbolic map does not yet name the update-list wrappers or the 0x0010 toggle helpers, even though those are exactly the kind of cross-version anchors you asked us to preserve. I’m adding stable labels for them and then I’ll see whether there’s a clean visible QEMU validation for the root-order effect.”

    Action/result: Added symbolic labels for update-list builders, flush, rebuild, refresh, and bit-0x0010 membership helpers.

  305. Reported: “The symbolic labels are in. I’m reading the fixture builders now to find the least invasive way to validate the predicted draw-order effect from bit 0x0010, preferably with two overlapping objects and a stable pixel/sample comparison.”

    Action/result: Reviewed logic and object overlay probe fixture builders.

  306. Reported: “A clean QEMU validation looks feasible: activate two overlapping objects, clear bit 0x0010 on one, then use the source-derived root order to predict which object draws on top. I’m checking the fixture helper defaults and view frames so the expected image is stable and distinguishable.”

    Action/result: Confirmed view 11/group 0 frames 0 and 1 have overlapping nontransparent pixels with different colors.

  307. Reported: “The probe framework can already compare multiple sprites by compositing extras first and the primary sprite last, which fits the predicted root-order test. I hit a small local attribute-name mismatch while checking frame transparency; I’m inspecting the rendered frame structure and then I’ll add the case.”

    Action/result: Confirmed frame transparency is frame.control & 0x0f.

  308. Reported: “The frame renderer uses control & 0x0f as transparency; view 11 frames 0 and 1 differ enough to reveal order. I’m adding one source-derived behavior case to the logic probe: object 11 is moved to the 0x1703 partition with 0x3a, object 10 stays in 0x16ff, and the expected composite has object 10 on top.”

    Action/result: Prepared a visible root-order probe design.

  309. Reported: “I can cover both toggles with two cases rather than overloading one: one where 0x3a moves frame-1 behind a still-0x16ff frame-0 object, and one where 0x3b moves frame-1 back over a 0x1703 frame-0 object. I’ll include 0x3c after the partitioning in both cases to validate that the global refresh preserves the predicted order.”

    Action/result: Added two root-partition cases to tools/logic_interpreter_probe.py.

  310. Reported: “The two behavior cases are in the probe list. I’m adding unit coverage for their registration before trying them under QEMU, so a typo in the case IDs or expected sprites gets caught locally first.”

    Action/result: Added the new case IDs to tests/test_logic_interpreter_probe.py.

  311. Reported: “I’m making the test registration check explicit for the two new root-partition cases. That keeps the probe catalogue honest as it grows.”

    Action/result: Updated the base-case registration test.

  312. Reported: “The catalogue test is updated. I’m running the focused logic-probe tests now, then I’ll run the two new cases through QEMU if local composition passes.”

    Action/result: python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_logic_doc_coverage passed.

  313. Reported: “Focused tests passed. I’m running the two new root-partition cases against the original engine now; this one needs QEMU/VNC access, so I’ll request the same kind of permission as prior batches.”

    Action/result: Ran object_root_partition_001; the first case mismatched.

  314. Reported: “The first QEMU case mismatched, which is useful evidence rather than a dead end: my source-derived draw-order prediction is missing another ordering detail. I’m opening the report and capture samples now, then I’ll go back to the list builder/sort code before adjusting the model.”

    Action/result: Inspected mismatch samples and re-read the list builder and node traversal.

  315. Reported: “The mismatch is not just ‘opposite top sprite’; there are white pixels where the local expectation has object colors, so another object-state detail is in play. I’m comparing the capture against a few source-plausible composites before changing the probe, then I’ll inspect the activation/list code around that detail.”

    Action/result: Determined that activation-time placement/collision was affecting the first fixture design.

  316. Reported: “The capture points to placement, not root order: activating two objects at exactly the same position lets the placement helper nudge one to avoid collision. I’m revising the fixtures to activate the second object elsewhere, then use the already decoded dirty position setter 0x93 to overlap it after activation, so the test isolates the 0x0010 partition effect.”

    Action/result: Updated the fixture to activate object 11 at X 90, then move it to X 50 with 0x93.

  317. Reported: “The fixtures now avoid activation-time collision adjustment. I’m rerunning focused tests and then the same QEMU batch.”

    Action/result: Focused tests passed, but object_root_partition_002 mismatched.

  318. Reported: “The revised case still mismatched, so the remaining issue is likely not placement. I’m going back to the render/list order carefully: the list builder inserts nodes at the head, and the processor walks from the tail, so I need to model that actual traversal instead of assuming root order alone determines topmost pixels.”

    Action/result: Rechecked the source traversal and mismatch report.

  319. Reported: “The second mismatch is identical, so 0x93 did not isolate the object from the placement/update behavior the way I hoped. I’m going to inspect the 0x93 handler before touching the fixture again; if it calls placement or refreshes through the same collision path, I’ll avoid it for this validation.”

    Action/result: Static read confirmed 0x93 calls placement helper 0x593a.

  320. Reported: “Confirmed: 0x93 calls placement helper 0x593a, so it was still letting collision adjustment move the object. I’m switching the isolation step to 0x25, which static docs say writes current and saved position fields directly without placement, then the explicit 0x3c refresh will make the final overlap visible.”

    Action/result: Replaced 0x93 with 0x25 in the root-partition fixtures.

  321. Reported: “The fixture now uses 0x25 for direct overlap, with 0x3c doing the refresh. I’m rerunning the focused unit test and QEMU batch again.”

    Action/result: Focused tests passed, but object_root_partition_003 mismatched.

  322. Reported: “Still mismatching. At this point the fixture is no longer a quick validation, so I’m going to stop iterating by trial and inspect the actual captured shape and relevant draw code before deciding whether this is worth continuing now. The source trail matters more than forcing a probe.”

    Action/result: The capture showed object 11 drawn at both X 50 and X 90.

  323. Reported: “The capture reveals object 11 is still at its activation X (90), so the direct 0x25 write after activation is not enough to move the already-listed persistent object in this path. I’m disassembling the generated fixture and the 0x25/activation code paths to understand why before deciding whether to keep or drop this QEMU case.”

    Action/result: Confirmed the generated fixture contained 0x25 0b 32 50 and that 0x25 rewrites both current and saved coordinates.

  324. Reported: “Now the behavior is clear: 0x25 updates the saved position too, so the restore pass no longer knows to erase the old object at X 90. The result is a deliberate ghost plus the new overlap. I’m checking whether modeling that ghost makes the QEMU capture match; if it does, this becomes a useful combined observation about 0x25 plus the list refresh.”

    Action/result: Local comparison matched exactly when the stale X-90 drawing was included in the expected image.

  325. Reported: “That matches exactly when I include the stale X-90 image left by 0x25. I’m updating the expected composites to include that ghost explicitly, then I’ll rerun both cases; the root-order part still cleanly validates at the overlapping X-50 pixels.”

    Action/result: Updated the two root-partition cases to include the activation-position ghost sprite in expected output.

  326. Reported: “The expected composites now include the observed stale image from the direct position rewrite. I’m rerunning focused tests and then the two-case QEMU batch.”

    Action/result: Focused tests passed.

  327. Reported: “Focused tests pass. I’m running the corrected QEMU batch; this should now distinguish the documented ghost from any actual root-order mismatch.”

    Action/result: QEMU batch object_root_partition_004 matched with 2 matches, 0 mismatches, and 0 errors.

  328. Reported: “Great, both root-partition cases matched the original engine. I’m promoting 0x3a, 0x3b, and 0x3c from smoke to behavior evidence, then I’ll document the source correction, the failed fixture attempts, and the final passing QEMU batch.”

    Action/result: Promoted 0x3a, 0x3b, and 0x3c in tools/logic_opcode_evidence.py.

  329. Reported: “The evidence generator now treats the root-partition opcodes as behavior-validated. I’m regenerating the matrix and then I’ll patch the prose docs around the exact command and result.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  330. Reported: “The matrix now shows 0x3a, 0x3b, and 0x3c as QEMU-validated. I’m updating the prose docs next, including the important caveat that the fixture initially failed until we accounted for 0x25 preserving the stale saved rectangle.”

    Action/result: Began updating compatibility, status, graphics, and opcode prose docs.

  331. Reported: “I found two stale spots: the older view-load/field23 compatibility paragraph still says 0x48/0x4a are smoke-only, and current status still mentions generic bitfield smoke coverage. I’m patching those along with the new object_root_partition_004 replay command.”

    Action/result: Patched compatibility, current status, graphics pipeline, and logic bytecode prose.

  332. Reported: “The compatibility, status, graphics, and opcode chapters are patched. I’m adding the chronological clean-room record now, including the mismatch sequence and why the final fixture needed to model the 0x25 stale image.”

    Action/result: Added the object update-list partition section to docs/src/clean_room_executable_notes.md.

  333. Reported: “The clean-room chronology now has the source commands, the three failed fixture variants, and the final passing QEMU evidence. I’m adding a compact current-status bullet so future resumption sees 0x0010 as confirmed behavior, not just next-work detail.”

    Action/result: Added the root-partition summary to current status.

  334. Reported: “Current status is updated. I’m appending the work log now so the intermediate false starts and the eventual source correction are preserved for the historical record.”

    Action/result: Appended this progress-log block.

  335. Reported: “The progress log is caught up. I’m running the full validation suite now: all Python tests, evidence check, mdBook build, whitespace check, and a stale-wording search.”

    Action/result: python3 -B -m unittest discover -s tests passed 97 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check passed. The stale-wording search found only historical command/log references plus an older chronology sentence.

  336. Reported: “The validation suite passed. The stale search only found historical command/log references plus one old ‘Current smoke rows’ sentence from an earlier chronology section; I’m changing that sentence to make clear it was the state at the time, not the current evidence status.”

    Action/result: Reworded the older clean-room chronology sentence to describe the smoke rows as historical.

  337. Reported: “That historical sentence is corrected. I’m recording the validation result in the progress log, then doing a final mdBook and whitespace rerun because documentation changed after the checks.”

    Action/result: Added this final validation note.

  338. Reported: “I’ll keep going from the uncommitted root-partition work. I’m going to scan the current evidence gaps and pick the next source-backed opcode cluster that can be upgraded without muddying the model.”

    Action/result: Reviewed git status and the opcode evidence matrix after the root-partition pass.

  339. Reported: “The evidence matrix has no remaining dispatch-smoke rows; the next good target is a source-backed-but-not-behavior-validated object bit. I’m going after 0x2d/0x2e and bit 0x2000, because it appears in the per-cycle frame/group selection path and should be possible to validate visibly.”

    Action/result: Selected actions 0x2d and 0x2e for the next source-first investigation.

  340. Reported: “I’ll pick up from the 0x2000 object flag mismatch and start by reading the generated batch report/captures, then I’ll go back to the disassembly before changing the model. The useful thing here is to separate ‘our expected sprite is wrong’ from ‘the flag means something subtler than the static read suggested.’”

    Action/result: Inspected object_bit_2000_001.json; the mismatch was confined to the object pixels.

  341. Reported: “The mismatch is tight: all differing pixels are exactly where the object is drawn, not a random screen-state problem. The captured colors look like a different cel/group than expected, so I’m going to inspect the fixture and renderer expectations before touching the opcode description.”

    Action/result: Listed the fixture and inspected the capture metadata.

  342. Reported: “The probe itself is doing what I intended: object 10 is initialized to view 11 group 0, direction 6, tick divider 1, then either clears or sets bit 0x2000. Next I’m checking whether the engine picked a different group/frame or whether the group-selection pass is gated by another field I missed.”

    Action/result: Re-read the probe setup code and the 0x0563 static frame-update slice.

  343. Reported: “The static slice confirms the bit gate exactly at 0x0593: if 0x2000 is set, it skips the direction-to-group lookup; if clear, it can call the group selector. I’m correcting a local import path for the comparison helper now so I can identify which cel the original engine actually drew.”

    Action/result: Corrected the local helper invocation and tested alternate expected group/frame combinations.

  344. Reported: “The miniature render dump is useful: group 0 cels are 20x5, group 1 cels are 13x7. The capture’s mismatch bbox height spans seven rows, so the original engine likely selected group 1 but mirrored or used the opposite group-1 frame from my expectation.”

    Action/result: Rendered compact view-11 cel dumps; this hypothesis was corrected by the subsequent exact-frame comparison.

  345. Reported: “That explains the failed expectation: the engine drew group 0 frame 0 exactly. The disassembly says the automatic lookup only runs when object byte +0x0b is 2, 3, or >=4; view 11 appears to leave that byte below the threshold, so it was a bad fixture for this flag.”

    Action/result: Exact comparison showed the capture matched view 11 group 0 frame 0. Later source review kept the result but corrected the reason to a fixture-lifecycle problem rather than the view’s group count.

  346. Reported: “The right fixture should use a view whose loaded metadata crosses that threshold. I’m going to generalize the helper from ‘view 11 only’ to ‘selected view,’ use a four-group SQ2 view, and rerun the bit-0x2000 pair against QEMU.”

    Action/result: Inspected view resources and selected view 4 as a compact four-group fixture target.

  347. Reported: “One important wrinkle just popped out: these probe logics loop, so a setup sequence inside the main body can keep reselecting group 0 every cycle after the engine’s update pass. I’m checking the loop shape now; the fix may be to guard initialization so the automatic selector has a later cycle where our script no longer resets the group.”

    Action/result: Re-read base_code and self_loop.

  348. Reported: “I need to revise my earlier loop thought: the fixture’s self_loop is an infinite bytecode jump at the end, so the script body is not repeating, but it may also prevent the engine’s later per-cycle object update from running. I’m going to make these two cases use an end-style logic so the engine can advance into the frame-timer pass, then freeze via the already-drawn persistent object state.”

    Action/result: Compared the logic probe shape to the movement harness, which uses a normal 0x00 end action for per-cycle movement tests.

  349. Reported: “The movement harness already has a pattern for this: initialize once, let per-cycle actions run until a flag, and end the logic instead of pinning the interpreter in a bytecode jump. I’m borrowing that structure conceptually for the logic opcode probe rather than inventing a separate tester.”

    Action/result: Located the guarded one-time setup pattern in tools/qemu_fixture.py.

  350. Reported: “Before editing, I’m going to keep the change small: add a reusable ‘end after body’ option to the logic probe case builder, add an arbitrary-view setup helper, and switch only the two 0x2000 cases to that mode. Then QEMU can tell us whether the static interpretation survives runtime.”

    Action/result: Added end_code, arbitrary-view object setup, and an expected-view parameter to tools/logic_interpreter_probe.py.

  351. Reported: “One more refinement before running it: an end case that redraws the picture every cycle would erase useful evidence, so I’m putting the load/show/setup sequence behind a one-time flag guard. That gives the engine normal cycles afterward without the script resetting the object or repainting the background each time.”

    Action/result: Added guarded one_time_code support.

  352. Reported: “I’ve added the guarded one-time execution path; now I’m wiring the two cases to it and using a loaded view whose group metadata should make the direction table path observable.”

    Action/result: Reworked the 0x2000 cases to use view 4, direction 6, and one-time initialization flags.

  353. Reported: “The probe now initializes view 4 exactly once, sets direction 6 and the bit state once, then returns from logic each cycle. I’m running focused unit tests first so the generated fixture shape is sane before spending another QEMU cycle.”

    Action/result: Focused unit tests passed.

  354. Reported: “The focused tests passed. I accidentally launched a one-case QEMU run with zero waits while checking the command path; I’m letting it finish/close cleanly before starting the real two-case validation.”

    Action/result: The zero-wait run produced a throwaway mismatch and exited.

  355. Reported: “That zero-wait run produced the expected throwaway mismatch, so I’m ignoring it. Now I’m running the real two-case snapshot batch with the normal wait times.”

    Action/result: QEMU batch object_bit_2000_002 matched both cases.

  356. Reported: “Good, the corrected 0x2000 pair matches the original engine: clear bit allows direction-based group selection, set bit suppresses it. I’m promoting those opcodes in the evidence table now and then I’ll update the spec notes with the false start and the static addresses that explain it.”

    Action/result: Added 0x2d and 0x2e to QEMU_ACTIONS.

  357. Reported: “The docs already had the raw 0x2d/0x2e bit writes, but not the higher-level meaning. I’m adding the two direction-to-group data tables and the frame-update helper labels so future version comparisons can key off names instead of SQ2 addresses.”

    Action/result: Added symbolic labels for the two direction-to-group tables.

  358. Reported: “The evidence generator now points 0x2d and 0x2e at the passing object_bit_2000_002 batch. Next I’m adding the stable labels for the two small direction lookup tables in AGIDATA.OVL.”

    Action/result: Continued symbol-map update.

  359. Reported: “The symbol map is updated. I’m expanding the bytecode and graphics chapters so the higher-level behavior is visible where an implementer would look first, not only buried in the chronological notes.”

    Action/result: Patched logic_bytecode.md.

  360. Reported: “The opcode chapter now states both the raw field mutation and the runtime meaning. I’m doing the same in the object pipeline chapter, including the two actual table byte sequences so a clean implementation can reproduce the mapping.”

    Action/result: Patched graphics_object_pipeline.md.

  361. Reported: “The object pipeline chapter now has the implementation-level mapping and the QEMU replay name. I’m adding the chronological clean-room record next, including the first mismatching fixture and why it was wrong.”

    Action/result: Added the clean-room chronology section for bit 0x2000.

  362. Reported: “The chronology now captures both the successful source model and the fixture correction. I’m updating the handoff/status and compatibility chapters so this is visible in the project dashboard too.”

    Action/result: Updated current status.

  363. Reported: “The compatibility patch missed because that chapter’s wording has drifted a bit. I’m locating the nearby section and applying the same replay block against the current text.”

    Action/result: Located the root-partition compatibility section.

  364. Reported: “I found a stale compatibility sentence still calling the old bitfield batch ‘dispatch smoke.’ Since the evidence matrix no longer has smoke rows, I’m cleaning that wording while inserting the new 0x2000 replay command.”

    Action/result: Patched compatibility testing with the new replay command and updated the stale smoke wording.

  365. Reported: “The progress log is caught up through the docs edits. I’m regenerating the opcode evidence chapter now, then I’ll run the full local validation set.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  366. Reported: “The evidence chapter regenerated cleanly. I noticed two long helper lines in the probe while reviewing the diff, so I’m wrapping those before the full validation run.”

    Action/result: Wrapped helper code in tools/logic_interpreter_probe.py.

  367. Reported: “The helper is tidied up. I’m running the full validation pass now: Python unit tests, regenerated evidence check, mdBook build, and whitespace checks.”

    Action/result: python3 -B -m unittest discover -s tests passed 97 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check passed.

  368. Reported: “Validation passed: all 97 tests, evidence check, mdBook build, and git diff --check. I’m doing a quick stale-wording/status scan and then I’ll record the validation result in the progress log.”

    Action/result: Stale scan found only historical progress/chronology mentions and the still-supported generator/category prose, not current opcode evidence rows.

  369. Reported: “I’ll run this as a full pass rather than a proposal: first finish the automatic direction/group semantics, then trace the per-cycle scheduler, then use that to promote more source-backed opcode rows, and finally document the timing model plus the next less-visual families. I’m starting by reading the current evidence matrix and the relevant object/frame-update code so the next probes stay source-led.”

    Action/result: Created the five-step task plan and read current status, opcode evidence, and probe code.

  370. Reported: “The current 0x2000 validation covers the four-plus-groups table only. I’m going to add three focused cases now: one for the 2/3-group table, one sentinel value that must leave the group alone, and one +0x01 != 1 gate case that should block selection even when the table says to change.”

    Action/result: Inspected view 5 as a visibly distinct three-group fixture and dumped the two direction tables from SQ2/AGIDATA.OVL.

  371. Reported: “View 5 is a good 3-group target: group 0 and group 1 are visibly distinct mirror-like cels, so the 2/3-group table can be observed cleanly. I’m adding those cases to the existing logic probe list now.”

    Action/result: Added three initial bit-0x2000 follow-up cases.

  372. Reported: “The three new fixtures are in: 3-group direction 6, 3-group sentinel direction 5, and the +0x01 gate. I’m updating the case registry test, then I’ll run the focused unit tests before QEMU.”

    Action/result: Updated the logic probe registry test.

  373. Reported: “The focused registry tests passed. I again started a zero-wait harness sanity run while checking fixture build behavior; I’ll let that close and then run the real snapshot batch with normal timing.”

    Action/result: The zero-wait dry run mismatched, as expected.

  374. Reported: “The zero-wait dry run mismatched as expected because it captured before the engine had useful draw time. Now I’m running the actual three-case QEMU batch.”

    Action/result: Started QEMU batch object_bit_2000_003.

  375. Reported: “The QEMU batch is still running; it’s in the snapshot execution phase. I’ll wait for the result before deciding whether these fixtures are good enough or need the same kind of source-led correction as the previous one.”

    Action/result: Batch object_bit_2000_003 matched the table and sentinel cases but mismatched the initial +0x01 gate expectation.

  376. Reported: “Two of the three new cases matched: the 2/3-group table and sentinel value are confirmed. The +0x01 != 1 case changed anyway, so I’m inspecting the capture and then tracing who decrements or rewrites +0x01; that may turn the ‘gate’ into a cadence condition rather than a permanent block.”

    Action/result: Exact comparison showed the mismatching case eventually selected group 1.

  377. Reported: “The capture shows the +0x01 = 2 object eventually selected group 1. The source explains why: code.object.frame_timer_update checks +0x01 before code.motion.update_objects decrements the countdown, so a one-shot 2 skips once, then becomes 1 for the next cycle. I’m changing this into two clearer probes: one for that countdown behavior, and one that rewrites +0x01 = 2 every logic cycle to prove the gate itself.”

    Action/result: Added a one-time setup plus per-cycle action fixture helper.

  378. Reported: “I’ve added a reusable one-time-setup plus per-cycle-action fixture shape, which is exactly what we need for timing-sensitive engine behavior. Now I’m revising the +0x01 case and adding the explicit ‘keep forcing 2’ gate probe.”

    Action/result: Replaced the initial gate case with a countdown case and added a forced-gate case.

  379. Reported: “The old gate case is now a countdown case, and the new gate case keeps setting +0x01 to 2 before each engine update. I’m updating the tests and rerunning the targeted batch.”

    Action/result: Updated the registry test for the renamed and new cases.

  380. Reported: “I’m running the focused tests and then a four-case QEMU batch: the two table cases, the countdown case, and the forced-gate case. If the forced case holds, that completes the 0x2000 semantic set cleanly.”

    Action/result: Focused tests passed and QEMU batch object_bit_2000_004 started.

  381. Reported: “The unit tests passed; QEMU is still running the four fixtures. This is the interesting one because it tests timing, not just final object state.”

    Action/result: QEMU batch object_bit_2000_004 matched all four cases.

  382. Reported: “The four-case batch matched: both direction tables, sentinel behavior, countdown-to-1, and the forced +0x01 != 1 gate are now validated. I’m moving to the scheduler trace next: who calls the frame/group update, motion update, logic execution, and redraw steps, and in what order.”

    Action/result: Completed the automatic direction/group semantics plan item.

  383. Reported: “The near-call scan found the key top-level calls: code.object.frame_timer_update is called at image 0x024b, and a pre-pass at 0x0644 is called at 0x0198. I’m disassembling that top-level range now to pin the cycle ordering rather than relying on old notes.”

    Action/result: Disassembled image 0x0100..0x0270 and scanned direct calls in that range.

  384. Reported: “The top-level loop is now visible at 0x0150: it calls input/system helpers, a pre-motion pass, then repeatedly calls logic 0 through code.logic.call_logic, and only after that reaches code.object.frame_timer_update at 0x024b. I’m labeling that cycle and identifying the currently unknown calls well enough to document the ordering without over-naming uncertain helpers.”

    Action/result: Read existing labels and call references around the top-level loop.

  385. Reported: “A useful correction fell out of the scheduler trace: the pre-motion mode dispatcher is at image 0x0644, not 0x0844, and its rectangle helper is 0x06d9, not 0x08d9. The earlier docs had those labels shifted; I’m going to correct the symbolic map and prose before building on them.”

    Action/result: Located all current references to the shifted labels.

  386. Reported: “The scheduler correction also gives us an easy next promotion target: some resource/display opcodes are still source-backed but can be observed visually. After I patch the scheduler labels, I’ll add narrow probes for view discard and picture overlay/priority display where possible.”

    Action/result: Added code.engine.main_cycle and corrected motion helper labels in docs/src/symbolic_labels.md.

  387. Reported: “The symbolic map now has the top-level code.engine.main_cycle label and corrected motion helper addresses. I’m fixing the object pipeline prose so the scheduler order matches the actual top-level call sites.”

    Action/result: Updated per-cycle movement prose in the graphics/object pipeline chapter.

  388. Reported: “The movement chapter now distinguishes the pre-logic motion-mode pass from the post-logic frame/group/movement pass. I’m scanning the evidence matrix for source-backed rows that can be promoted with small, deterministic visual probes.”

    Action/result: Listed source-backed opcode rows from the generated matrix.

  389. Reported: “The remaining source-backed rows are mostly UI/save/sound, but a few are still small and visual. The rectangle-boundary opcodes 0x5a/0x5b already have movement semantics in the harness, so I’m checking whether they can be promoted from existing QEMU batches before writing new fixtures.”

    Action/result: Confirmed existing movement cases exercise 0x5a; 0x5b still needed a focused case.

  390. Reported: “The existing rectangle movement batch already exercises 0x5a because it configures the crossing bounds. 0x5b is not isolated yet, so I’m adding one movement case that sets the bounds and then clears them; the object should reach the target instead of stopping at the rectangle edge.”

    Action/result: Located fixture helper and movement harness extension points.

  391. Reported: “I need a tiny helper for action 0x5b in the fixture layer, then I can add the movement case without special-casing the generated logic. This stays inside the existing object movement harness.”

    Action/result: Added clear_rect_bounds_action().

  392. Reported: “I’m making the 0x5b support as a simple bytecode helper, plus one boolean on the movement case. Then the case can share the same comparison and snapshot machinery as the existing rectangle-boundary probes.”

    Action/result: Added move_rect_boundary_clear_bounds_reaches_target.

  393. Reported: “The new 0x5b movement case is in. I’m running focused tests, then I’ll run the one-case QEMU batch for the clear-bounds behavior.”

    Action/result: Focused fixture/movement tests passed; first QEMU attempt failed under the sandbox VNC bind restriction.

  394. Reported: “The focused tests passed. The QEMU run hit the sandbox’s VNC bind restriction, so I’m rerunning the exact same one-case batch with the required elevated permission.”

    Action/result: Elevated QEMU batch rect_bounds_clear_001 matched with 1 match, 0 mismatches, and 0 errors.

  395. Reported: “The 0x5b QEMU probe matched: after 0x5a sets rectangle bounds, 0x5b clears them and the object reaches the target. I’m promoting 0x5a and 0x5b in the evidence matrix now: 0x5a is covered by the existing rectangle-boundary batch, and 0x5b by this new clear-bounds batch.”

    Action/result: Promoted 0x5a and 0x5b in tools/logic_opcode_evidence.py.

  396. Reported: “The evidence generator is updated. I’m adding the fixture helper test coverage so the new 0x5b bytecode helper is protected like the other generated opcodes.”

    Action/result: Located the relevant fixture and movement registry tests.

  397. Reported: “I found the helper tests and the movement case registry test. I’m adding one assertion for the 0x5b bytecode and one assertion that the new movement case is registered.”

    Action/result: Updated tests/test_qemu_fixture.py and tests/test_object_movement_probe.py.

  398. Reported: “The tests now cover the 0x5b helper and case registration. I’m regenerating the opcode evidence matrix and running the focused fixture/movement tests once more.”

    Action/result: Regenerated the evidence matrix; focused tests passed 48 tests.

  399. Reported: “The focused tests passed and the evidence matrix regenerated. I’m doing the documentation pass now: first the opcode/object chapters, then compatibility/current-status, then the chronological clean-room log with the exact commands and corrections.”

    Action/result: Began documentation updates.

  400. Reported: “A small checkpoint: steps 1 through 3 are now done in code and QEMU evidence. I’m currently on step 4, turning the scheduler/order findings into spec prose that a future implementation can follow.”

    Action/result: Marked the first three plan items complete.

  401. Reported: “The bytecode chapter text has slightly different surrounding lines, so I’m applying this as smaller patches: one new cycle-timing section, one replacement for the 0x2000 QEMU paragraph, and one table-row update for 0x5a/0x5b.”

    Action/result: Updated docs/src/logic_bytecode.md.

  402. Reported: “The bytecode chapter now has the top-level timing model, the expanded 0x2000 behavior, and QEMU-backed 0x5a/0x5b rows. I’m aligning the object pipeline chapter’s 0x2000 paragraph with the new four-case batch.”

    Action/result: Updated docs/src/graphics_object_pipeline.md.

  403. Reported: “Next I’m adding replay commands to the compatibility chapter: the expanded 0x2000 batch and the 0x5b clear-bounds movement batch. That keeps the QEMU evidence directly reproducible.”

    Action/result: Updated docs/src/compatibility_testing.md.

  404. Reported: “The compatibility chapter has the new replay commands. I’m updating the handoff/status page next so future sessions see these as completed findings, not just raw test reports.”

    Action/result: Updated docs/src/current_status.md.

  405. Reported: “The status page now reflects the completed scheduler and rectangle-boundary work. I’m adding the clean-room chronology next, including the correction that the 0x067a mode dispatcher is reached from 0x0644, not 0x0563.”

    Action/result: Corrected an older clean-room chronology paragraph.

  406. Reported: “The historical correction is in place. I’m appending a new chronology section with the exact commands and results from this pass.”

    Action/result: Added the expanded direction/scheduler/rectangle chronology section.

  407. Reported: “The clean-room notes now have the new command trail and the corrected scheduler addresses. I’m adding the remaining-family survey so the ‘what next’ trail is explicit for UI/save/sound/text instead of vague.”

    Action/result: Added the remaining source-backed family survey to current status.

  408. Reported: “That completes the survey step in the handoff doc. I’m marking the last two plan items complete, then I’ll update the progress log and run the full validation suite.”

    Action/result: Marked all five requested steps complete in the task plan.

  409. Reported: “The progress log is caught up. I’m running the full validation suite now: all Python tests, evidence check, mdBook build, and whitespace checking.”

    Action/result: python3 -B -m unittest discover -s tests passed 97 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check passed.

  410. Reported: “The full validation suite passed: 97 tests, evidence check, mdBook build, and whitespace check. I’m doing a final stale-address scan for the corrected scheduler labels, then I’ll record the validation result.”

    Action/result: The stale-address scan found only deliberate historical correction entries and the intermediate object_bit_2000_003 batch recorded in the chronology.

  411. Reported: “I’m picking up from the resource-lifecycle probe mismatch. The useful clue from the disassembly is that overlay_picture_var appears to require the picture to already be loaded/cached, so I’m going to correct the synthetic bytecode around that rather than treating the black-box result as mysterious.”

    Action/result: Re-read current probe and test changes around resource lifecycle fixtures.

  412. Reported: “The failure now lines up with the code path: 0x1c is not a load-and-overlay operation, it’s an overlay of an already resident picture. I’m adjusting the probe to exercise that exact precondition, and I’ll add a discard-picture lifecycle case while we’re in this neighborhood.”

    Action/result: Planned the 0x18 + 0x1c fixture correction and a 0x1b discard/reload case.

  413. Reported: “I’m also checking the shared QEMU snapshot harness before adding input/text probes. If we can represent post-launch keystrokes as data on each fixture, the same runner can handle text prompts and menu/input cases without a separate one-off script.”

    Action/result: Inspected tools/qemu_snapshot.py and related docs.

  414. Reported: “There’s already a clean place to put post-launch input: SnapshotFixtureCase can grow optional key text, and existing callers stay unchanged. That gives us the scaffolding for prompt/menu/save probes even before every UI path has a pixel-accurate comparator.”

    Action/result: Chose a backward-compatible post_launch_keys / post_launch_wait fixture extension.

  415. Reported: “The text/window work is going to be staged honestly: I can add the reusable capture/input plumbing now, but pixel-accurate text rendering is a separate model from the 160x168 graphics renderer. I’ll document it as capture/scaffolding plus source assessment, not pretend a coarse screenshot proves the UI semantics.”

    Action/result: Scoped UI work to deterministic input plumbing plus narrow visible-output probes.

  416. Reported: “I’ve got the concrete additions now: resource lifecycle cases, text/input cases using delayed monitor keystrokes, and menu/sound dispatch-smoke cases. Next I’m making the fixture schema carry that post-launch input through to QEMU.”

    Action/result: Began code edits in tools/qemu_snapshot.py and tools/logic_interpreter_probe.py.

  417. Reported: “The QEMU runner can now type fixture-specific input after launch. I’m threading those fields into the logic probe cases and adding the new bytecode fixtures around the source-backed families.”

    Action/result: Added post-launch input fields and logic-probe fixture cases.

  418. Reported: “I’m keeping these probes visible-output driven where possible: they don’t just run an opcode and hope; they make the original interpreter proceed to a known view draw only if the opcode path returns and the expected state was produced.”

    Action/result: Added message, numeric input, menu, sound, and resource lifecycle cases that end in known draw comparisons.

  419. Reported: “One little plumbing issue surfaced: _draw_if_case did not yet accept input fields, so I’m widening that helper too. This keeps the input cases as normal logic-probe fixtures instead of a special path.”

    Action/result: Extended _draw_if_case to pass input settings through.

  420. Reported: “The probe definitions are in place. I’m adding regression tests for the new case IDs and for the new snapshot input fields so this doesn’t silently break later when we add more UI fixtures.”

    Action/result: Updated logic-probe and QEMU snapshot unit tests.

  421. Reported: “The static tests are next. If these pass, I’ll run the new QEMU batches in smaller groups so a bad UI timing assumption doesn’t obscure the resource-lifecycle results.”

    Action/result: Ran focused unit tests and the opcode evidence check; they passed.

  422. Reported: “The focused Python tests passed. I’m running the resource lifecycle batch first: logic-var load, picture overlay with the now-correct load precondition, discard/reload picture, and view discard/reload variants.”

    Action/result: Started QEMU batch resource_lifecycle_002.

  423. Reported: “The lifecycle batch is in QEMU now. It has built the snapshot disk and is stepping through the five fixtures from the saved DOS prompt.”

    Action/result: Awaited the QEMU lifecycle batch.

  424. Reported: “The corrected overlay precondition still mismatched, so I’m not going to paper over it. I’m reading the report and then I’ll go back to the picture-handler code path to see whether the expected composition model is wrong, or whether another action such as show/finalize is needed after overlay.”

    Action/result: Read resource_lifecycle_002; only overlay pixels were missing.

  425. Reported: “The mismatch is narrower now: the view sprite is being drawn, but the overlaid picture pixels are not visible in the display capture. That fits the handler split: 0x1c mutates the logical picture buffers, while 0x1a is the full-screen display/finalization path, so I’m adding that explicit show step after the overlay.”

    Action/result: Added 0x1a after 0x1c in overlay fixtures.

  426. Reported: “I’ve corrected the overlay fixture to include the display finalization path. I’m rerunning the local tests for the probe shape, then the lifecycle batch again.”

    Action/result: Focused unit tests passed.

  427. Reported: “The local shape tests are still green. I’m rerunning the five-case lifecycle QEMU batch with the explicit 0x1a finalization after overlay.”

    Action/result: Started QEMU batch resource_lifecycle_003.

  428. Reported: “QEMU is running the lifecycle batch again now. If this passes, I’ll move on to the text/input/menu/sound group as separate batches because those touch blocking UI helpers.”

    Action/result: Awaited lifecycle batch completion.

  429. Reported: “The lifecycle batch passed all five cases. That validates the exact overlay nuance: 0x1c composes into the logical picture state, and 0x1a is needed to push that composition to the visible screen. Next I’m running the text/message and typed-input fixtures.”

    Action/result: resource_lifecycle_003 matched 5/5 and text/input QEMU batch was started.

  430. Reported: “The text/input batch is running. These cases use the new post-launch keystroke path: Enter to dismiss message windows, then typed look and 42 for prompt handlers.”

    Action/result: Awaited text_input_001.

  431. Reported: “The three message-window cases passed; the typed string prompt did not reach the expected final draw. I’m inspecting the capture/report before deciding whether the typed input timing is wrong or the assumed parsed word id/condition is wrong.”

    Action/result: Inspected text_input_001; 0x65, 0x66, and 0x97 matched, while the 0x73 string prompt did not.

  432. Reported: “The mismatch region is exactly where the final sprite should be, so the script probably stayed on the prompt path or the condition was false. I’m going to inspect the screenshot visually and then decide whether to relax this to a prompt-capture case or fix the input timing/terminator.”

    Action/result: Converted and viewed the prompt capture.

  433. Reported: “The screenshot is helpful: the prompt displayed and look was typed, but it did not accept/advance before capture. I’m going to give prompt-style input an extra Enter; message windows accepted a single Enter, but this editor helper appears to need a less brittle dismissal sequence under QEMU monitor timing.”

    Action/result: Retried the 0x73 prompt fixture with an extra Enter.

  434. Reported: “The extra Enter still didn’t advance the string prompt. I’m checking whether the second Enter is being ignored while the typed buffer remains active, which would mean this handler needs a different completion key or a more direct event injection strategy.”

    Action/result: Confirmed the string prompt remained active; then ran the numeric prompt case separately.

  435. Reported: “The numeric prompt case passed with the new key-input path, so the input scaffolding is real. The string-editor prompt still stays active after typed text; I’m removing that from the default compatibility suite for now and documenting it as an observed harness limitation/open item rather than leaving a known-failing default case.”

    Action/result: Removed the failing 0x73 case from default probes and kept 0x76 as QEMU-validated input evidence.

  436. Reported: “The default suite now contains only input cases we can make deterministic. I’m rerunning the text/input batch with the three message paths plus numeric prompt.”

    Action/result: Started QEMU batch text_input_002.

  437. Reported: “The trimmed text/input batch is running now. This should give us stable QEMU evidence for message display variants and one typed-input path without overclaiming the string editor behavior.”

    Action/result: Awaited text_input_002.

  438. Reported: “The text/input batch passed all four cases. I’m moving to the menu/list and sound dispatch-smoke probes next; these are deliberately narrower, proving handler execution and return-to-bytecode rather than full UI/audio semantics.”

    Action/result: text_input_002 matched 4/4 and menu/sound smoke batch was started.

  439. Reported: “QEMU is running the menu/sound smoke batch now. If any of these fail, I’ll treat it as a missing precondition and go back to the handler disassembly rather than weakening the result.”

    Action/result: Awaited menu_sound_001.

  440. Reported: “The menu/sound smoke batch passed. I’m updating the generated evidence matrix inputs next so the docs distinguish true pixel/value validation from dispatch-smoke coverage for menu and sound handlers.”

    Action/result: Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  441. Reported: “The evidence generator now knows about the new resource, text/input, menu, and sound probes. I’m regenerating the mdBook evidence chapter from that single source.”

    Action/result: Regenerated the opcode evidence chapter.

  442. Reported: “I’m turning the QEMU results into spec prose now: one pass in the bytecode reference for behavior, one in compatibility testing for replay commands, one in current status for handoff, and one chronological log with the failed string-prompt attempt included.”

    Action/result: Updated bytecode, compatibility, current-status, and clean-room chronology docs.

  443. Reported: “A small but important wording point for the spec: I’m documenting the overlay as a two-stage operation, because the failed capture proved that logical composition alone is not the same as a visible full-screen refresh.”

    Action/result: Added the two-stage 0x1c plus 0x1a overlay note.

  444. Reported: “The handoff docs now reflect which families moved from ‘source-backed’ into QEMU-backed or smoke-tested. I’m appending the chronological evidence trail with the exact commands and the failed 0x73 attempt, because that negative result matters for future work.”

    Action/result: Appended the clean-room chronology section for this pass.

  445. Reported: “The chronological notes are updated. I’m catching up the progress log now with the user-facing updates from this run so the conversational history remains traceable.”

    Action/result: Updated this progress log.

  446. Reported: “I’ll take the whole chain: start source-first at 0x0da9, then adjust the input harness only where the disassembly says it should help, then use QEMU as validation rather than exploration.”

    Action/result: Began the string editor and event-path pass.

  447. Reported: “I’m gathering the local disassembly context first: existing notes, the 0x73 handler, the editor helper, and the event wait path.”

    Action/result: Searched docs/tools for prior 0x73 and 0x0da9 evidence.

  448. Reported: “The existing docs already have the high-level 0x73 shape, but not the completion key details. I’m reading raw disassembly around the helper now.”

    Action/result: Confirmed the existing source-backed handler summary and prepared focused executable slices.

  449. Reported: “The full disassembly file appears to be stale around this helper, so I’m making fresh focused slices from the decrypted executable with explicit image origins.”

    Action/result: Generated and disassembled slices around image 0x0c20 and 0x4420.

  450. Reported: “The editor helper is now clear enough to read structurally: clamp length, copy current string to a local buffer, wait for events, then dispatch through a key table. I’m dumping the table bytes so we don’t infer key names from control flow alone.”

    Action/result: Dumped key dispatch bytes at file offset 0x1060.

  451. Reported: “The dispatch table is the important bit: Enter maps to the accept/copy-back path, Escape returns without copy, Backspace edits, and Ctrl-C/Ctrl-X clear. So QEMU should be able to complete 0x73; the harness probably isn’t delivering the right event shape or timing.”

    Action/result: Identified 0x0d as the accept key for code.input.edit_string.

  452. Reported: “The disassembly says Enter really is a completion key, so I’m going to separate two questions in the probes: did the prompt return at all, and did it copy the edited string into the slot?”

    Action/result: Planned separate return-only and string-slot equality fixtures for 0x73.

  453. Reported: “I’m adding a per-fixture key delay now; text prompts are one of the few places where monitor keystroke timing can masquerade as interpreter behavior.”

    Action/result: Added post_launch_key_delay to the QEMU fixture model.

  454. Reported: “The input delay plumbing is in. Next I’m making the 0x73 cases explicit and keeping them out of the broad default batch until they pass on their own.”

    Action/result: Added string prompt fixtures and kept them targeted.

  455. Reported: “I’m also adding low-risk system/diagnostic probes while I’m in this file, because several remaining opcodes should only need an Enter acknowledgement plus a validation draw.”

    Action/result: Added formatted text, input-line, text-window, and diagnostics fixture cases.

  456. Reported: “The case registry tests need to learn about the new targeted cases, then I’ll do a focused Python test pass before waking QEMU.”

    Action/result: Updated unit tests for the new case registry and QEMU fixture defaults.

  457. Reported: “The local code is ready for a sanity pass. I’m running focused Python tests for the logic probe definitions and snapshot runner.”

    Action/result: Ran focused unit tests for tools/logic_interpreter_probe.py and tools/qemu_snapshot.py.

  458. Reported: “Focused tests passed. I’m running two 0x73 probes first: return-after-Enter and stores-typed-word.”

    Action/result: Started QEMU batch prompt_string_001.

  459. Reported: “QEMU is running string prompt checks. This is the narrowest place to debug the key path before adding the larger text UI batch.”

    Action/result: Awaited the first string-prompt QEMU run.

  460. Reported: “The return-only 0x73 case still did not advance to the draw after typed look plus Enter, despite the source saying Enter should accept. I’m going to inspect the capture and then try sending the return key as a named key event rather than as a typed newline character.”

    Action/result: Treated the mismatch as a harness/event-shape question.

  461. Reported: “The capture is the same editor-active state, so the next probe change is to extend the key sender rather than change interpreter expectations.”

    Action/result: Added support for named post-launch QEMU key events.

  462. Reported: “I’m also separating type text from press named key in the fixture model, with a short wait between them. That gives the DOS keyboard path a cleaner event sequence.”

    Action/result: Added post_launch_after_text_wait and post_launch_key_names.

  463. Reported: “The 0x73 fixtures now type look, pause, then send a named ret key. I’m rerunning the focused unit tests before trying QEMU again.”

    Action/result: Updated the string prompt fixtures for named Enter.

  464. Reported: “The local schema tests passed with the named-key fields. I’m rerunning 0x73 now.”

    Action/result: Started QEMU batch prompt_string_002.

  465. Reported: “QEMU revised string prompt batch is running. If this still stalls, the next source check will be the event normalizer around 0x45d7 and 0x4634.”

    Action/result: Awaited prompt_string_002.

  466. Reported: “Aha, the capture is more subtle: the typed prompt text is still on-screen, but the validation draw may be happening underneath it. I’m adding a full picture refresh after the prompt before comparing pixels.”

    Action/result: Diagnosed the mismatch as text overlay persistence rather than editor non-completion.

  467. Reported: “I’ve inserted the refresh after 0x73. That should make the fixture prove string semantics instead of comparing against leftover text pixels.”

    Action/result: Added 0x1a before the validation draw in the 0x73 probes.

  468. Reported: “The refreshed string-prompt batch is running now.”

    Action/result: Started QEMU batch prompt_string_003.

  469. Reported: “Nice, 0x73 is now validated: both the return-only case and the typed-word storage case matched. The earlier failure was a display cleanup artifact, not an editor completion failure.”

    Action/result: prompt_string_003 matched 2/2.

  470. Reported: “I’m batching UI cases by risk: formatted messages and simple input-line/window state first, then diagnostics/system actions after those pass.”

    Action/result: Started the text UI QEMU probe sequence.

  471. Reported: “QEMU is running the text UI group now.”

    Action/result: Started QEMU batch text_ui_001.

  472. Reported: “The first formatted-message case mismatched, so I’m reading the capture pattern before changing anything. Given the 0x73 lesson, text overlay is the prime suspect.”

    Action/result: Investigated the first formatted-message mismatch.

  473. Reported: “The mismatch is exactly at the formatted message text location. I’m applying the same validation split: acknowledge the message, refresh the picture, then draw the object for comparison.”

    Action/result: Added refresh-before-validation to formatted message cases.

  474. Reported: “The formatted-message probes now separate text display from graphics validation. I’m rerunning the six-case text UI batch.”

    Action/result: Started QEMU batch text_ui_002.

  475. Reported: “QEMU revised text UI batch is running.”

    Action/result: Awaited text_ui_002.

  476. Reported: “Four of six text UI cases passed. The rectangle-clear probe is different: it deliberately changes top text rows, so I need to make the fixture prove it returns before drawing, not preserve the cleared rows.”

    Action/result: Identified the text-rectangle clear comparison problem.

  477. Reported: “The rectangle-clear case returned but intentionally cleared screen rows that the expected image assumed were normal. I’m inserting a refresh before the validation draw there too.”

    Action/result: Added refresh-before-validation to text rectangle clear.

  478. Reported: “The text rectangle probe is corrected. I’m running just the two remaining text-window cases first.”

    Action/result: Started QEMU batch text_clear_001.

  479. Reported: “The remaining text-clear/window cases passed. Now I’m rerunning the full six-case text UI batch to keep one clean evidence artifact.”

    Action/result: text_clear_001 matched 2/2 and text_ui_003 was started.

  480. Reported: “QEMU clean combined text UI batch is running.”

    Action/result: Awaited text_ui_003.

  481. Reported: “The combined text UI batch passed. I’m moving on to the diagnostic/system message group.”

    Action/result: text_ui_003 matched 6/6 and diagnostics were started.

  482. Reported: “Diagnostics batch running now: pause, heap/status, interpreter version, plus low-risk global action dispatch smoke.”

    Action/result: Started QEMU batch diagnostics_system_001.

  483. Reported: “All QEMU batches passed. I’m updating the generated opcode evidence matrix so the docs reflect the new validated and dispatch-smoke rows.”

    Action/result: diagnostics_system_001 matched 4/4 and evidence metadata was updated.

  484. Reported: “Generated evidence chapter refreshed. I’m doing the narrative docs now: bytecode semantics, compatibility replay commands, symbolic labels, current status, and the clean-room/progress logs.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md and began the documentation update pass.

  485. Reported: “I’ll proceed source-first: re-read the opcode handlers and shared text/input helpers from the executable, then only use QEMU where it gives us a clean confirmation test.”

    Action/result: Started the text/status configuration pass.

  486. Reported: “The prior docs already have decent summaries for this family, but I’m going back to the bytes instead of trusting the summaries.”

    Action/result: Created focused executable slices around image offsets 0x3400, 0x7600, 0x4c00, and 0x0d60.

  487. Reported: “The slices are ready. I’m reading them now with explicit origins so the addresses line up with the existing symbolic map and later cross-version comparisons.”

    Action/result: Disassembled the focused slices with ndisasm.

  488. Reported: “The static read confirms the family is more about global text/display state than hidden game semantics: 0x70/0x71 are just status-line flag plus redraw/clear; 0x6f stores row/config globals; 0x79 appends a 4-byte key mapping slot.”

    Action/result: Decided to add narrow QEMU dispatch-smoke tests while keeping richer key-map semantics source-backed.

  489. Reported: “Before editing, I’m checking the probe helper shapes so the new cases fit the existing harness instead of inventing a second style.”

    Action/result: Reviewed tools/logic_interpreter_probe.py, evidence metadata, and case registry tests.

  490. Reported: “I’m adding small, explicit smoke fixtures now: one for text-attribute mode setup/teardown, one for status line show/hide, one for prompt/config globals, one for key-map table insertion, and one for screen-shake return.”

    Action/result: Added five targeted QEMU cases.

  491. Reported: “The new cases are in the registry. I’m updating the case-list tests and the evidence generator so the metadata matches the harness.”

    Action/result: Updated tests/test_logic_interpreter_probe.py and tools/logic_opcode_evidence.py.

  492. Reported: “The metadata is patched. I’m running the focused Python tests before QEMU so any mistake in fixture definitions fails cheaply.”

    Action/result: Focused Python tests passed.

  493. Reported: “Focused tests passed. I’m going to run just the five new QEMU cases as a tight batch; that keeps this as confirmation evidence, not fishing around.”

    Action/result: Started QEMU batch text_status_001.

  494. Reported: “QEMU is running the five-case status/input batch from a snapshot now. If one of these fails, I’ll treat it as a missing precondition and go back to the static path rather than broadening the test blindly.”

    Action/result: Awaited text_status_001.

  495. Reported: “The first two new cases matched: text-attribute setup/teardown and one-count screen shake both returned cleanly. The prompt/config case mismatched, so I’m inspecting that artifact before changing anything.”

    Action/result: Inspected text_status_001 JSON and the mismatching PPM.

  496. Reported: “The mismatch is informative rather than mysterious: using 0x6f with row/config byte 1 changes display-position state enough that the validation sprite appears at a different vertical offset than the local renderer expects.”

    Action/result: Changed the 0x6f smoke fixture to use first operand 0.

  497. Reported: “The fixture correction is deliberately narrow. I’m rerunning the Python sanity check, then I’ll rerun the same five-case QEMU batch so we have one clean artifact.”

    Action/result: Focused Python tests passed after the fixture correction.

  498. Reported: “Focused tests are still green. The revised QEMU batch is starting now with the neutral 0x6f row/config operand.”

    Action/result: Started QEMU batch text_status_002.

  499. Reported: “QEMU is running the corrected batch. The earlier failed run will go into the clean-room notes as a useful constraint on 0x6f: operand 0 affects display offset state, so a later behavior probe can target that directly.”

    Action/result: Awaited text_status_002.

  500. Reported: “Four of five have matched in the corrected batch; only the key-event mapping smoke case is still running/awaiting capture.”

    Action/result: Awaited the final key-event mapping case.

  501. Reported: “The corrected batch matched 5/5. I’m regenerating the opcode evidence chapter next, then I’ll write down the static source findings plus the QEMU confirmation and the one failed 0x6f offset probe.”

    Action/result: text_status_002 matched 5/5 and docs/src/logic_opcode_evidence.md was regenerated.

  502. Reported: “I’m updating the symbolic map first. These labels give us stable names for the status redraw, prompt marker, text attribute, input-line configuration, and key-map insertion helpers instead of tying future notes to raw offsets.”

    Action/result: Updated docs/src/symbolic_labels.md.

  503. Reported: “Next I’m folding the new source/QEMU distinction into the bytecode chapter: the handlers are now smoke-tested, but some deeper behavior, especially key-map event conversion and 0x6f display offset effects, remains a separate target.”

    Action/result: Updated docs/src/logic_bytecode.md.

  504. Reported: “I’m adding the replay command to the compatibility chapter now, including the failed text_status_001 lesson so future probes don’t accidentally rediscover that offset issue.”

    Action/result: Updated docs/src/compatibility_testing.md.

  505. Reported: “I’m updating the handoff/status page so ‘remaining source-backed text opcodes’ doesn’t keep pointing at work that now has at least dispatch-smoke coverage.”

    Action/result: Updated docs/src/current_status.md.

  506. Reported: “The main spec pages are updated. I’m appending the clean-room evidence trail with the exact disassembly slices, the QEMU mismatch, the corrected run, and what each result proves.”

    Action/result: Updated docs/src/clean_room_executable_notes.md.

  507. Reported: “I’ll turn the 0x6f observation into a real behavior case now: script draws the object at baseline 80, but the expected capture baseline is 88 when 0x6f operand 0 is 1.”

    Action/result: Began behavior-level probes for 0x6f, 0x79, and 0x74.

  508. Reported: “I’m going to quantify the offset rather than eyeballing it. The quick check is: compare the operand-1 capture against the same expected sprite at several baselines and see which one minimizes mismatches.”

    Action/result: Compared the recreated operand-1 QEMU capture against expected baselines 70..90; baseline 88 matched exactly.

  509. Reported: “That resolved the ambiguity: the operand-1 capture matches exactly if the expected sprite baseline is moved from 80 to 88.”

    Action/result: Confirmed 0x6f first operand 1 produces an eight-logical-row visible offset in the observed mode.

  510. Reported: “Source check found the right path for 0x79: condition 0x0d does not use the mapping table, but the top-level input helper does.”

    Action/result: Re-read condition 0x0d and helper 0x4566; identified condition 0x0c as the observation point for mapped type-3 events.

  511. Reported: “I’m adding two behavior-level cases now: one where 0x6f(1,...) must move the visible baseline from 80 to 88, and one where 0x79 maps x to status byte 7 and a later cycle draws only after condition 0x0c 7 sees it.”

    Action/result: Added input_line_config_operand1_offsets_display_by_8 and mapped_key_sets_status_byte.

  512. Reported: “The behavior cases are added. I’m updating tests and evidence metadata so 0x6f and 0x79 can move from smoke evidence to behavior evidence if the batch matches.”

    Action/result: Updated the case registry test and opcode evidence generator.

  513. Reported: “The focused test caught a harness-shape mistake: _draw_if_case doesn’t take init_once_flag. I’m switching that mapped-key case to the lower-level _custom_case.”

    Action/result: Reworked the mapped-key case as one-time setup plus per-cycle conditional draw.

  514. Reported: “Focused tests are green. I’m running the two behavior confirmations now: 0x6f offset and 0x79 mapped key to status byte.”

    Action/result: Started QEMU batch input_key_behaviour_001.

  515. Reported: “The 0x6f behavior case matched: operand 1 produced the expected 8-row visible offset. The mapped-key case is still running.”

    Action/result: First case of input_key_behaviour_001 matched.

  516. Reported: “Both behavior probes matched. I’m moving to 0x74 now: first I’ll inspect the table bytes around 0x0c8f, then decide whether a fixture-local AGIDATA.OVL patch is a safe way to create a non-empty table entry for dynamic confirmation.”

    Action/result: input_key_behaviour_001 matched 2/2 and the 0x74 table area was inspected.

  517. Reported: “0x74 has a convenient zero-filled pocket right after the pointer table. I’m going to make the dynamic test explicit and synthetic: patch only the generated fixture’s AGIDATA.OVL so table entry 0 points to a local look\\0 string.”

    Action/result: Added fixture-local AGIDATA.OVL patch support and a synthetic 0x74 behavior case.

  518. Reported: “The synthetic AGIDATA patch support is in. I need one small follow-up: thread that optional patch list through _draw_if_case, since the new 0x74 case uses that convenience wrapper.”

    Action/result: Passed agidata_patches through _draw_if_case.

  519. Reported: “The probe and evidence metadata are ready. I’m running focused tests again before the QEMU batch that combines 0x6f, 0x79, and synthetic 0x74.”

    Action/result: Focused Python tests passed.

  520. Reported: “Focused tests passed. I’m running the three targeted QEMU cases now; the 0x74 one uses only a fixture-local AGIDATA.OVL patch, not a change to the SQ2 source files.”

    Action/result: Started QEMU batch input_key_string_behaviour_001.

  521. Reported: “QEMU is running the three-case behavior batch. This should give us one clean artifact covering the offset, mapped-key event path, and the patched pointer-table copy.”

    Action/result: Awaited input_key_string_behaviour_001.

  522. Reported: “The three targeted confirmations all matched. I’ll regenerate the evidence matrix and update the docs for these before moving on to the inventory/menu source pass, so the text/input family has a clean stopping point.”

    Action/result: input_key_string_behaviour_001 matched 3/3 and the opcode evidence chapter was regenerated.

  523. Reported: “The evidence chapter is regenerated. I’m updating the narrative docs with the new distinction: 0x6f, 0x79, 0x74, and condition 0x0c now have behavior evidence, with 0x74 clearly marked as a synthetic table-entry fixture.”

    Action/result: Updated bytecode, compatibility, and current-status docs.

  524. Reported: “The user-facing docs now reflect the behavior batch. I’m appending the clean-room evidence trail with the exact commands, including the synthetic AGIDATA patch rationale for 0x74.”

    Action/result: Updated docs/src/clean_room_executable_notes.md.

  525. Reported: “I’m picking up from the source-first inventory/menu pass. The text/key/string-table probes are in place; now I’m going to turn the inventory disassembly notes into a couple of focused behavior checks so the docs don’t just say ‘looks like,’ they have original-engine evidence behind them.”

    Action/result: Re-read local harness, QEMU input helpers, test registry, symbolic labels, and the existing 0x7c documentation.

  526. Reported: “The local state already includes the static inventory description, and the symbolic label file has the core inventory table label. I’m checking the harness details before editing so the new probes use the same QEMU input path as the earlier text/editor cases.”

    Action/result: Confirmed post_launch_keys and named-key support in tools/qemu_snapshot.py.

  527. Reported: “The source-backed model for 0x7c is tight enough to probe: flag 13 selects interactive mode, carried entries are the ones with marker 0xff, Enter stores the selected index in variable 0x22, and Escape stores 0xff. I’m adding exactly those cases now.”

    Action/result: Added initial inventory selection probes, later corrected the variable interpretation from bytecode variable 0x22 to absolute byte DS:0x0022.

  528. Reported: “The three new inventory fixtures are deliberately small: one Enter path, one Escape path, and one noninteractive acknowledgement path. They all draw only after 0x7c returns, so a match tells us the handler both accepted the key and resumed the bytecode stream.”

    Action/result: Added probe registry tests for the three inventory cases.

  529. Reported: “The harness-level tests are happy. Next I’m running the original engine batch; this is the useful part for compatibility because it checks the disassembly-derived interpretation against DOS/QEMU captures.”

    Action/result: Focused Python tests passed; started QEMU batch inventory_selection_001.

  530. Reported: “QEMU is in the snapshot batch now: it has prebuilt the three DOS fixture directories and is replaying them from one DOS prompt snapshot. If the Escape monitor key name is wrong, this run should make that obvious from the first mismatch.”

    Action/result: Initial QEMU batch mismatched on the Enter case.

  531. Reported: “The first inventory probe did not match, so I’m not going to paper over it. I’m going back to the generated fixture and capture to see whether the key timing, carried-entry setup, or my conditional draw expectation is the wrong piece.”

    Action/result: Inspected the JSON report and generated capture; the handler returned but the validation condition was false.

  532. Reported: “The capture is not an inventory screen; it is essentially a text-mode/DOS-looking bottom band, which means the probe did not make it through the intended visual path before the screenshot. I’m checking whether the fixture launched, whether the carried-object setup exposed a blocking text window, and whether the baseline expectation needs to account for text-mode cleanup.”

    Action/result: Re-read the inventory disassembly and identified the absolute byte store at DS:0x0022.

  533. Reported: “Found the issue, and it’s a good catch from the disassembly: the inventory handler writes absolute DS:0x0022, while byte variables are based at DS:0x0009. So the script-visible variable is 0x19, not 0x22. I’m correcting the probe and docs language rather than treating the failed QEMU run as noise.”

    Action/result: Renamed and corrected the inventory Enter/Escape probes to check bytecode variable 0x19.

  534. Reported: “The corrected bytecode now checks variable 0x19, matching the absolute store at 0x0022. I’m rerunning the same original-engine batch with the corrected case IDs.”

    Action/result: Focused tests passed; started the corrected QEMU batch.

  535. Reported: “The batch is running again from a fresh snapshot disk. This should also tell us whether QEMU’s monitor esc key name is accepted by the DOS event loop for this interpreter path.”

    Action/result: Corrected inventory_selection_001 matched 3/3, including the named esc key path.

  536. Reported: “Good, all three inventory probes match now. The failed run was useful: it exposed that the handler’s absolute byte store maps to script variable 0x19, which is exactly the kind of cross-address detail we want nailed down before generalizing.”

    Action/result: Prepared to promote 0x7c in the opcode evidence matrix.

  537. Reported: “I’m promoting 0x7c in the evidence generator from source-only/smoke status to behavior-validated status now that the Enter, Escape, and acknowledgement paths have QEMU matches.”

    Action/result: Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  538. Reported: “I’m updating the written trail now: the bytecode chapter needs the corrected variable mapping, the compatibility chapter needs the replay command, and the clean-room notes need both the failed and successful runs because that failure changed the spec.”

    Action/result: Updated the 0x7c bytecode row.

  539. Reported: “The 0x7c table row now says ‘absolute DS:0x0022, exposed as variable 0x19,’ which is the key correction. I’m also adding stable symbolic names for the inventory/list and menu routines so later interpreter-version comparisons have anchors beyond raw addresses.”

    Action/result: Added inventory and menu labels to docs/src/symbolic_labels.md.

  540. Reported: “The symbolic labels now include code.inventory.*, code.menu.*, and the selection result byte. I’m recording the QEMU replay batch in the compatibility chapter next.”

    Action/result: Added the inventory_selection_001 replay command and result to docs/src/compatibility_testing.md.

  541. Reported: “The status and compatibility docs are updated. I’m appending the detailed evidence log now, including the initial mismatch because it documents the correction from ‘variable 0x22’ to ‘absolute 0x0022 / variable 0x19.’”

    Action/result: Added the inventory selection source/QEMU pass to docs/src/clean_room_executable_notes.md.

  542. Reported: “The documentation trail is caught up. I’m running the full verification set now: unit discovery, opcode evidence check, mdBook build, and whitespace diff check.”

    Action/result: Started full validation.

  543. Reported: “All verification passed: 99 unit tests, evidence check, mdBook build, and git diff --check. I’m doing one final diff/status pass so I can summarize exactly what changed without mixing generated build artifacts into the answer.”

    Action/result: Verified final status and diff summary.

  544. Reported: “I’ll work through the five families in order, source-first: menu interaction, view-resource display, system/dialogs, file-backed save/log behavior, then sound completion. I’ll keep QEMU probes narrow and only use them where the disassembly gives us a concrete side effect to validate.”

    Action/result: Started the five-family follow-up pass.

  545. Reported: “I’ve got the current open list. I’m going into the disassembly around the menu interaction, view-resource display, trace/system, file/log, and sound handlers before touching probes; there are a few places where an opcode’s visible result is indirect, so source context matters.”

    Action/result: Created initial source slices.

  546. Reported: “The slices are ready. I’m disassembling the target ranges now and looking for direct byte/flag side effects that can be observed by logic conditions, because those make much better compatibility tests than broad ‘screen changed’ assertions.”

    Action/result: Found the initial slices were misaligned for some targets.

  547. Reported: “One correction from the tooling pass: these image offsets need the executable-file header adjustment when slicing (file offset = image offset + 0x200). I caught that because the first 0x5ebf slice landed in DOS file wrappers instead of the documented view-display handler, so I’m regenerating the slices with the correct file offsets.”

    Action/result: Regenerated aligned slices using image offset plus 0x200.

  548. Reported: “Now the slices are aligned to the image addresses used in the docs. I’m re-disassembling the corrected ranges and pulling out the control-flow facts we can turn into fixtures.”

    Action/result: Re-disassembled menu, view-resource, save/restore, log, sound, trace, signature, and joystick ranges.

  549. Reported: “The source pass is giving us several good anchors: menu Enter enqueues a type-3 event with the item id via 0x44a9, 0x81/0xa2 share 0x5edb, 0x90 appends to logfile, 0x95/0x96 are observable through trace-window globals, and 0x63 clears its completion flag before starting sound. I’m checking how those internal events surface back to logic bytecode so the probes can assert state instead of just ‘the program didn’t hang.’”

    Action/result: Located event/status and helper paths for the planned probes.

  550. Reported: “The menu source shows the interaction routine itself cleanly, but I still need the caller path: 0xa1 only sets [0x1d22], and some top-level code must notice that and call code.menu.interact. I’m locating those references before writing the menu fixture.”

    Action/result: Searched references to [0x1d22].

  551. Reported: “The caller reference is around file offset 0x358d, which maps to image 0x338d. I’m reading that neighborhood; it should tell us how to trigger the menu loop from script-level bytecode.”

    Action/result: Confirmed the menu request word participates in the input/event path.

  552. Reported: “I have enough source detail to add focused cases now. The probes I’m adding are intentionally small: menu Enter should surface as condition 0x0c on the item id; view display and dialogs should return after acknowledgement/cancel; 0x90 should at least execute the DOS append path and return; and 0x63 should clear its completion flag operand as the source shows.”

    Action/result: Added initial QEMU probe cases for menu, view-resource, system/dialog, file/log, and sound.

  553. Reported: “The new cases are in the harness. I’m adding them to the registry test next so future edits don’t silently drop any of these coverage points.”

    Action/result: Updated tests/test_logic_interpreter_probe.py.

  554. Reported: “The registry test has the new cases. I’m running the focused Python tests before QEMU, because it’s much cheaper to catch bad bytecode construction here than after a DOS boot.”

    Action/result: Focused tests passed.

  555. Reported: “Focused tests pass. I’m running the probes in smaller QEMU batches by family, so if something mismatches we can tie it back to one source path instead of sorting through a mixed bag.”

    Action/result: Started menu_interaction_001.

  556. Reported: “The first menu attempt mismatched, which likely means either my trigger path is incomplete or Enter is arriving before the menu loop is actually waiting. I’m inspecting the capture/report before changing the fixture.”

    Action/result: The menu validation sprite was present, but menu/status text polluted the comparison.

  557. Reported: “This mismatch is just the menu/status text strip still visible at the top, not a missing validation sprite. That means the Enter path worked and set the item status byte; the fixture needs a picture refresh before the validation draw, just like the text-window probes.”

    Action/result: Added 0x1a before the menu validation draw.

  558. Reported: “I updated the menu validation draw to refresh the picture first. The underlying behavior was already present; this just prevents the top text strip from polluting the graphics comparison.”

    Action/result: Reran menu_interaction_001.

  559. Reported: “Menu Enter is now QEMU-validated: opening through 0xa1, pressing Enter, and observing condition 0x0c 7 all match. I’m running the view-resource pair next.”

    Action/result: menu_interaction_001 matched 1/1 and view_resource_display_001 started.

  560. Reported: “The view-resource batch is running. These cases are slightly heavier because 0x81/0xa2 load and temporarily render view resources before displaying their text-like payload.”

    Action/result: view_resource_display_001 matched 2/2.

  561. Reported: “The immediate and variable view-resource display paths both match. I’m moving to the system/dialog group now: signature check, restart/confirmation cancel, joystick no-device, guarded display toggle, and trace-window configuration.”

    Action/result: Started system_dialog_001.

  562. Reported: “The system/dialog batch is underway. The risky ones here are the two confirmation prompts; if Escape timing is off, I’ll adjust those from the capture/report rather than changing the source interpretation.”

    Action/result: Awaited system/dialog batch.

  563. Reported: “Still running; this batch has six snapshot restores plus a couple of blocking prompts. I’m waiting for the result rather than starting anything else that could contend for the QEMU/VNC slot.”

    Action/result: Continued waiting for QEMU.

  564. Reported: “By ‘QEMU-validated’ I mean: we ran a generated mini-game/fixture inside the original SQ2 interpreter under DOS in QEMU, drove the needed input, captured the original engine’s screen, and compared that capture against the expected result from our local model.”

    Action/result: Clarified evidence terminology for the user.

  565. Reported: “The trace-window mismatch is similar in shape: the action deliberately draws a trace window, so the screen no longer matches the plain sprite expectation. I’m making that fixture source-observable instead: enable the trace window, then run 0x95 a second time, which should consume one extra byte when [0x1d10] is nonzero.”

    Action/result: First system batch matched 5/6; trace drawing polluted the comparison.

  566. Reported: “The screenshot confirms the trace action is doing exactly what the source says: it draws a boxed trace area. I’m changing the probe to validate the more interesting source detail, the second-call byte-consumption behavior, and then refresh the picture before the comparison.”

    Action/result: Adjusted trace case, then found text-plane pixels still persisted.

  567. Reported: “Continuing. I’ve converted the trace test from ‘screen must stay plain’ to ‘exercise the second-call skip behavior, then clear the trace drawing before comparing,’ which better matches the disassembly.”

    Action/result: Reran focused tests and the system batch.

  568. Reported: “Harness tests are clean. I’m rerunning just the system/dialog batch with the corrected trace case.”

    Action/result: Started the corrected system run.

  569. Reported: “The corrected six-case system batch is running. If the trace case still mismatches, it will tell us that 0x1a doesn’t fully clear text-plane trace output, and I’ll move that case to a source-backed note instead of forcing a visual assertion.”

    Action/result: Trace still mismatched after enabled drawing.

  570. Reported: “The trace case still leaves text-plane pixels after the graphics refresh, so I’m splitting it out: the source notes will document its drawn-window and skip-byte behavior, while the QEMU-validated system batch will cover the five cases that compare cleanly. Next I’m running the file/log and sound cases.”

    Action/result: Started file_log_001.

  571. Reported: “The file/log batch is running. I expect the log append case to be clean; save/restore selector Escape is the part that may need key timing adjustment.”

    Action/result: file_log_001 matched 3/3.

  572. Reported: “File/log paths matched: append returned after touching DOS file I/O, and save/restore selectors both cancel cleanly with Escape. I’m running the sound-completion flag case now.”

    Action/result: Initial sound flag case mismatched.

  573. Reported: “The sound flag case mismatched, so I’m inspecting whether the sound start failed before clearing the flag, whether the flag is later reset on immediate completion, or whether the captured screen simply includes audio-related text/state.”

    Action/result: Capture showed no validation sprite.

  574. Reported: “The sound case didn’t draw the validation sprite, so the condition was false. Looking back at the source, 0x63 clears the flag before starting, but the sound subsystem can quickly set it again if the selected sound completes or fails immediately. I’m adjusting this to a simpler source-backed dispatch validation for 0x63 rather than overclaiming stable completion-flag timing.”

    Action/result: Replaced flag assertion with sound start/stop dispatch.

  575. Reported: “I’m replacing the sound flag probe with a start-and-stop dispatch case. That still gives 0x63 QEMU coverage, while the exact completion flag lifetime stays documented from source until we can instrument a less racy sound resource.”

    Action/result: Added sound_start_stop_dispatch_smoke.

  576. Reported: “The sound case is now a start/stop dispatch-smoke. I’m updating the test registry and running that single QEMU batch again.”

    Action/result: Sound start/stop still mismatched without a prior load.

  577. Reported: “Even the start/stop sound dispatch case didn’t reach the validation draw, so this is not just flag timing. I’m inspecting the report/capture and then I’ll decide whether 0x63 needs a synthetic sound fixture instead of using SQ2 sound 1.”

    Action/result: Determined 0x63 needs the sound resource loaded first.

  578. Reported: “The capture shows the validation draw never happened, and the source explains why: 0x63 asks for an already available sound resource record and can route into the interpreter error path if it is not loaded. I’m adding the preceding 0x62 load before 0x63, then stopping with 0x64.”

    Action/result: Added 0x62(1) before 0x63(1,77).

  579. Reported: “The sound start case now mirrors the resource lifecycle: load sound, start it with a completion flag, stop/clear state, then draw. I’m rerunning the focused test and sound batch.”

    Action/result: Focused tests passed and sound_completion_001 matched 1/1.

  580. Reported: “The loaded-start sound case now matches. Before updating evidence/docs, I’m also tightening the trace fixture: the enabled trace window intentionally leaves text pixels, so the reusable QEMU case should be the no-flag gated path for 0x95/0x96; the enabled-window behavior stays source-documented with the screenshot observation.”

    Action/result: Narrowed trace case to the flag-clear gated path.

  581. Reported: “Trace is now a gated no-op QEMU case, which is a clean dispatch check for 0x95/0x96; the enabled drawing path will be described from source and the observed mismatch capture. I’m rerunning the system batch to get a clean report.”

    Action/result: Started final system_dialog_001 run.

  582. Reported: “The corrected system batch is running again. After this I’ll update the evidence table and docs in one pass.”

    Action/result: Awaited final system batch.

  583. Reported: “The corrected system batch is running again. After this I’ll update the evidence table and docs in one pass.”

    Action/result: Duplicate status update while waiting for QEMU.

  584. Reported: “Great, the system/dialog report is clean now. I’m updating the opcode evidence generator to promote the newly matched cases, then I’ll regenerate the evidence chapter.”

    Action/result: system_dialog_001 matched 6/6; updated tools/logic_opcode_evidence.py.

  585. Reported: “I’m promoting the cases according to what they actually prove: cancellation and menu/view behavior go into QEMU-validated evidence; trace, log append, and sound start are dispatch-smoke because their deeper side effects need a different assertion path.”

    Action/result: Promoted QEMU evidence scopes accurately.

  586. Reported: “The evidence mapping is updated. I’m regenerating the mdBook evidence chapter from it.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  587. Reported: “Evidence chapter regenerated. I’m updating the prose docs now with the exact scope of each batch, including the trace-window and sound caveats so the spec doesn’t overclaim.”

    Action/result: Updated docs/src/logic_bytecode.md, docs/src/current_status.md, and docs/src/compatibility_testing.md.

  588. Reported: “Compatibility docs now have the replay commands. I’m adding a few symbolic labels for the new anchors: menu trigger/status, trace globals, log file handle/name, and sound state.”

    Action/result: Updated docs/src/symbolic_labels.md.

  589. Reported: “Symbolic labels are updated for the new stable anchors. I’m appending the clean-room evidence notes with the source slices, failed intermediate probes, and final successful QEMU batches.”

    Action/result: Appended the follow-up probe evidence to docs/src/clean_room_executable_notes.md.

  590. Reported: “I’m going to pick this back up at the verification stage: first make sure the generated opcode evidence, unit suite, mdBook, and whitespace checks all agree with the new source-backed/QEMU-backed notes. After that I’ll inspect the diff so the final summary is concrete.”

    Action/result: Ran the full unit suite, opcode evidence check, mdBook build, and whitespace check; all passed.

  591. Reported: “The validation pass is clean: 99 tests, opcode evidence check, mdBook build, and diff whitespace all passed. I’m doing one quick diff/status pass now so I can give you a useful handoff rather than a vague ‘it worked.’”

    Action/result: Checked git status --short, git diff --stat, and the progress log diff.

  592. Reported: “There are two new progress updates from this resumed bit, so I’m adding those to the historical log before I wrap. Tiny bookkeeping, but it keeps the clean-room audit trail intact.”

    Action/result: Added the resumed verification updates to this progress log.

  593. Reported: “I’ll take these in the order we sketched: room transitions, priority screen, object diagnostics, menu edge behavior, then promotion of selected dispatch-smoke cases. I’m starting source-first by reading the existing handler notes and probe harness so the new tests line up with the interpreter rather than just poking at it.”

    Action/result: Reviewed current status, opcode evidence, probe harness, and tests.

  594. Reported: “The docs already summarize these handlers, but I’m going back to disassembly for the next changes. In particular I want the branch details for 0x1792, 0x731b, 0x72b5, the menu interaction loop, and the file/log path before I add or promote probes.”

    Action/result: Re-read focused disassembly for room switch, priority screen, object diagnostics, menu interaction, and log append paths.

  595. Reported: “A few probe shapes are emerging cleanly from the source pass: room switching can be exposed through var0/var1, priority screen and object diagnostics can be tested as blocking UI actions that return after Enter, and menu disabled/Escape/navigation behavior can be asserted through status-byte conditions. The trace enabled path is still awkward for pixel comparison because it intentionally leaves text-plane artifacts, so I’m not going to overclaim that one.”

    Action/result: Selected initial source-derived fixture designs.

  596. Reported: “I’m going to add new harness cases now. The cases are intentionally narrow: each one turns a source-observed side effect into either a variable/status-byte assertion or a stable return-to-draw assertion, so the evidence level stays honest.”

    Action/result: Added new logic probe cases for room attempts, priority screen, object diagnostics, menu edges, and sound stop flag.

  597. Reported: “The first batch of new cases is in: immediate/variable room switch, priority screen return, object diagnostics return, menu Escape/disabled/re-enable/down-arrow, and a sound-stop completion-flag assertion. I’m wiring them into the registry test before I ask QEMU anything.”

    Action/result: Updated tests/test_logic_interpreter_probe.py.

  598. Reported: “Registry coverage is updated. I’ll run the cheap Python checks now; then I’ll run the new QEMU cases by family so any failure points back to a small source path.”

    Action/result: Focused Python tests and opcode evidence check passed.

  599. Reported: “The local tests are clean. I’m starting with the non-menu QEMU cases: room switch, priority display, object diagnostics, and sound stop flag. Those should tell us quickly whether the source-derived assertions are stable under the original engine.”

    Action/result: Started room_priority_diag_sound_001; the first room case mismatched.

  600. Reported: “The immediate room-switch case mismatched first, so I’m stopping there and inspecting the capture/report. This is useful: room switching is broad enough that one of the “obvious” assumptions, likely initial previous-room value or post-switch control flow, is off.”

    Action/result: Report showed the validation sprite was absent; first previous-room assertion was rejected.

  601. Reported: “The room switch did change control flow enough to clear the expected sprite, but the var1 == 0 assertion was too specific. The source says var1 receives the previous room byte, and the boot fixture appears not to start from zero there, so I’m narrowing the QEMU assertion to the stable source effect: var0 becomes the target room.”

    Action/result: Narrowed and renamed the room cases.

  602. Reported: “Room switching still isn’t returning to logic 0 with a simple var0 assertion, which is itself a useful correction. I’m adjusting the fixture to validate the destination-room execution path instead: after 0x12/0x13, the target logic resource should be loaded/run and draw the marker.”

    Action/result: Reworked room cases to draw from target logic.

  603. Reported: “The target logic ran into another source-derived reset effect: room switching clears resource/object state, so the destination logic cannot rely on the preloaded view/picture from before the switch. I’m updating the destination logic to load/show picture 0 and load view 11 before drawing.”

    Action/result: Made target logic self-contained; room cases still mismatched.

  604. Reported: “The room-switch fixture still doesn’t produce a stable draw, even with a self-contained destination logic. I’m going to treat 0x12/0x13 as source-backed for now and document the failed attempts; they probably need a fuller synthetic logic-0/current-room loop rather than a tiny one-shot fixture. I’m removing those failing cases from the reusable base registry and continuing with the other four steps.”

    Action/result: Removed failing room cases from reusable probe registry.

  605. Reported: “The failing room cases are out of the reusable suite. I’m running the remaining priority/diagnostics/sound family now; these still directly cover steps 2, 3, and part of step 5.”

    Action/result: Started priority_diag_sound_001.

  606. Reported: “The non-room batch is running with three stable assertions: 0x64 setting the completion flag, 0x1d returning from priority display, and 0x85 returning from object diagnostics.”

    Action/result: priority_diag_sound_001 matched 3/3.

  607. Reported: “Good: sound stop flag, priority screen, and object diagnostics all matched. I’m moving to the menu edge batch now: Escape, disabled item Enter, disable-then-enable, and down-arrow selection.”

    Action/result: Started menu_edges_001.

  608. Reported: “The menu edge cases are running. If one fails, the likely culprit is key timing or the exact QEMU key name for arrows, so I’ll separate timing problems from semantic problems by checking the capture/report.”

    Action/result: Three menu edge cases matched; down-arrow case mismatched.

  609. Reported: “Three of four menu edge cases matched. The down-arrow case mismatched, so I’m checking whether down was the wrong key name/timing or whether the menu interpreter uses a different event for vertical movement than I assumed.”

    Action/result: Capture/report showed the down-arrow case did not reach the expected status-8 validation path.

  610. Reported: “The failed arrow case may simply be the key sequence arriving too tightly while the menu is redrawing. I’m increasing the delay between Down and Enter and rerunning just that case once; if it still fails, I’ll keep arrow navigation as an attempted-but-not-promoted path.”

    Action/result: Slower down-arrow retry still mismatched.

  611. Reported: “The slower Down/Enter attempt still mismatched, so I’m taking that failed arrow fixture out of the reusable suite. We’ll keep the source mapping and failed capture as evidence, but not as a compatibility test until we can instrument the exact key/event conversion path.”

    Action/result: Removed the down-arrow case from the reusable suite.

  612. Reported: “The reusable menu suite now contains the three stable edge cases: Escape without status, disabled Enter without status, and disable-then-enable followed by Enter setting status. I’m rerunning that menu batch cleanly.”

    Action/result: menu_edges_002 matched 3/3.

  613. Reported: “Menu edges are clean. For the dispatch-smoke promotion step, I’ve already got a stable sound completion-flag assertion; now I’m going to see if the 0x90 logfile content can be inspected from the post-run qcow image after a single-case run. If that works, we can promote log append beyond pixels.”

    Action/result: Ran log_file_contents_001; visual comparison matched.

  614. Reported: “The single log case matched visually. I’m converting that qcow back to a raw image and trying to pull LOGFILE out of the fixture directory with mtools, which will tell us whether file-content assertions are practical in this harness.”

    Action/result: Converted the qcow image to raw and extracted LF00000/LOGFILE.

  615. Reported: “Great, the converted post-run image contains LOGFILE, and mtools extracted it. I’m reading the bytes now so we can decide whether to formalize a file-content check in the harness or document it as a reproducible manual assertion.”

    Action/result: File content decoded to two leading newlines, Room 0, empty Input line: , and LOG.

  616. Reported: “The logfile content matches the source-derived format exactly: blank line, Room 0, Input line: , and the message LOG. I’m promoting only the parts now backed by stable evidence, while leaving room switching and enabled trace as source-backed or attempted.”

    Action/result: Updated opcode evidence mapping.

  617. Reported: “Evidence mapping is updated: priority display, object diagnostics, log append content, stable menu setup/toggle behavior, and sound stop flag are promoted; room switching and enabled trace stay out of the promoted set. I’m regenerating the evidence chapter.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  618. Reported: “Now I’m updating the prose docs with the exact scope: three successful menu edge cases, priority/diagnostics/sound flag successes, logfile extraction, and the failed room/down-arrow attempts as non-promoted evidence.”

    Action/result: Updated docs/src/logic_bytecode.md.

  619. Reported: “The logic bytecode chapter now records both the successful promotions and the failed room/down-arrow attempts. I’m adding replay commands for the new batches and logfile extraction to the compatibility chapter next.”

    Action/result: Updated docs/src/compatibility_testing.md.

  620. Reported: “I’m refreshing the current-status handoff so the next session starts from the real frontier: room switch still needs a fuller fixture, menu arrows still need better key/event instrumentation, but priority/diagnostics/log/sound/menu toggles moved forward.”

    Action/result: Updated docs/src/current_status.md.

  621. Reported: “I’m adding labels for the newly touched stable anchors: room switch helper, priority display flag/action, object diagnostics action, and updating existing menu/sound/log notes to mention the new evidence.”

    Action/result: Updated docs/src/symbolic_labels.md.

  622. Reported: “I’m appending a new clean-room note for this pass with the exact disassembly commands, successful QEMU batches, failed room/down-arrow attempts, and the logfile extraction bytes.”

    Action/result: Updated docs/src/clean_room_executable_notes.md.

  623. Reported: “I’m catching the progress log up with the actual updates from this run before doing final validation. This one is a little verbose because the room/menu failures are historically useful, not just noise.”

    Action/result: Added these progress-log entries.

  624. Reported: “The docs and progress log are caught up. I’m running the full validation pass now: unit suite, evidence check, mdBook build, and whitespace check.”

    Action/result: Unit tests, evidence check, and mdBook build passed; whitespace check found one trailing space in a logfile code block.

  625. Reported: “Validation is clean except for one trailing space in the literal logfile block. I’m fixing that formatting nit and rerunning the doc/whitespace checks.”

    Action/result: Removed the trailing space and clarified that the input line buffer is empty.

  626. Reported: “The trailing-space issue is fixed, and the prose now explicitly says the input-line buffer is empty so the code block can stay whitespace-clean. Rechecking the touched validation pieces.”

    Action/result: mdBook build, whitespace check, and opcode evidence check passed.

  627. Reported: “The rechecks are clean. I’m adding the last validation updates to the progress log, then I’ll inspect the final diff/status for the handoff.”

    Action/result: Added final validation updates to this progress log.

  628. Reported: “Commit bc02913 is in place; only generated/untracked directories remain. I’m moving into the next pass now, starting with source around the room cycle and input event pipeline because those are the two places where tiny QEMU probes were too shallow.”

    Action/result: Confirmed the previous commit and began a source-first pass over room switching and menu/input events.

  629. Reported: “The existing notes already captured the earlier failed room-switch fixture shapes, which is useful because it tells us what not to trust yet. I’m going to build the next room probes around the concrete source effect the helper definitely performs: setting flag 5 and re-entering the logic cycle.”

    Action/result: Re-read the room-switch helper and prepared a flag-5-based fixture shape.

  630. Reported: “The disassembly confirms the room-switch wrapper itself returns zero after the helper, and code.engine.main_cycle immediately re-runs logic 0 while that zero return persists. That means a good fixture should expect logic 0 to re-enter after 0x12/0x13, not expect the switched-to logic to execute automatically.”

    Action/result: Confirmed wrapper return behavior and corrected the fixture hypothesis away from automatic target-logic execution.

  631. Reported: “For menu arrows, the source shows a second event-type path rather than normal status-byte mapping. There is a fixed table at 0x16b3 mapping raw event words 0x4800/0x4900/0x4d00/0x5100/0x5000/0x4f00/0x4b00/0x4700 to menu movement codes 1..8; 0x5000 is the down-arrow candidate.”

    Action/result: Identified and recorded the menu-direction key table.

  632. Reported: “I’m adding two room-switch probes that lean on the source-backed flag-5 effect rather than the earlier mistaken ‘target logic should run automatically’ assumption. I’ll also add a small down-arrow key mapping probe so we can separate ‘QEMU produced the raw arrow event’ from ‘the menu loop navigated.’”

    Action/result: Added temporary room-switch and down-arrow probe cases.

  633. Reported: “The room-cycle source gives us a sharper correction: after logic 0 returns zero from the switch action, the engine clears a few variables and immediately invokes logic 0 again in the same top-level loop until it gets a nonzero interpreter return. That’s why the fixture must guard against repeated switching with its own init flag.”

    Action/result: Added an init-flag guard to the temporary room fixtures.

  634. Reported: “I’ve wired the new cases into the local registry test. Next I’m running just the Python suite, then I’ll take the new probes through QEMU in small batches.”

    Action/result: Updated local registry tests and ran the unit suite; it passed before QEMU execution.

  635. Reported: “The immediate room-switch probe still mismatched, so I’m stopping before treating it as evidence. I’m opening the report and the generated fixture bytes now; this is exactly the kind of case where the disassembly should explain why the synthetic control flow is still wrong.”

    Action/result: room_reentry_001 mismatched on the immediate room-switch case.

  636. Reported: “The ‘failed’ room probe actually drew the validation sprite; the mismatch is priority/composition, not control flow. That is a good catch: the source-backed re-entry model appears right, but the fixture’s expected priority assumption is too strict after the room switch rebuilds display state.”

    Action/result: Initially suspected a composition issue from the comparison sample, then inspected the screenshot and rejected that interpretation.

  637. Reported: “One nuance: the report samples are from the downsampled logical buffer, while the raw screenshot inspection reports only two RGB colors in the VGA capture. I’m checking the actual image and the generated LOGIC resource bytes so I don’t misread a pixel-comparison artifact as engine behavior.”

    Action/result: Converted and inspected the QEMU capture; it showed a blank white screen, confirming the validation sprite was not drawn.

  638. Reported: “The fixture stores resources inside VOL files, so there are no standalone LOGIC.* files to inspect in that directory. I’ll use the probe’s Python objects for bytecode inspection and look at the screenshot directly.”

    Action/result: Inspected generated case bytecode through the Python probe object rather than nonexistent standalone resource files.

  639. Reported: “The screenshot confirms no sprite, so this is a real failed control-flow assertion. The most likely source-side explanation is that the room switch calls 0x10d0, which I haven’t fully accounted for; if it clears script flags, my guard flag gets wiped and logic 0 keeps switching forever.”

    Action/result: Re-read 0x10d0; this did not prove flag clearing, so the investigation moved to logic resume pointers.

  640. Reported: “The source points to a better explanation than ‘flags got cleared’: logic records carry a resume pointer. A room switch returns zero from the current action, so logic 0 may be called again from its saved post-switch instruction pointer unless the fixture explicitly restores the logic entry IP first. I’m going to fold opcode 0x92 into the synthetic switch path and rerun.”

    Action/result: Added 0x92 before the temporary synthetic room-switch calls.

  641. Reported: “That edit keeps the fixture narrow: it does not claim 0x12 itself resets logic 0, only that the switch helper sets flag 5 and the engine observes it after an explicit entry-IP restore. I’m running the targeted unit check and QEMU batch again.”

    Action/result: Targeted unit test passed; QEMU room_reentry_002 still mismatched.

  642. Reported: “0x92 did not change the outcome. I’m going back to the interpreter loop now, because the exact place it stores or reuses the logic IP is the source of truth for why logic 0 does not re-enter the way the synthetic fixture expects.”

    Action/result: Re-read code.logic.interpret_main and code.logic.call_logic.

  643. Reported: “The interpreter loop is clear: it starts from record +0x06, and 0x92 does set +0x06 to the logic’s entry pointer. The remaining mismatch looks like display-state contamination around the tiny sprite area rather than a missing draw, so I’m zooming the capture and then I’ll rerun the two room cases without --stop-on-failure to see whether the variable-target case behaves the same.”

    Action/result: Reran both room cases without stop-on-failure; both mismatched.

  644. Reported: “Both room cases mismatch the same way, which is still useful evidence: the fixture reaches a stable screen but not the expected compatibility assertion. I’m going to keep these as attempted/non-promoted for now and move the validated work to the down-arrow event split.”

    Action/result: Decided not to promote the room re-entry fixtures.

  645. Reported: “I’m not going to leave failing room probes in the reusable case list. I’ll preserve the attempted evidence in docs instead, then keep only stable probes in the tool. First I’m checking whether the raw down-arrow status mapping is stable.”

    Action/result: Ran down_key_001; it mismatched.

  646. Reported: “The down-arrow mapping did not validate either, which tells us QEMU’s sendkey down is not currently arriving as the raw 0x5000 event that the source table expects, or it arrives outside the polling window. I’ll remove that from the reusable suite too and document it as an input-instrumentation gap rather than a behavior claim.”

    Action/result: Removed temporary failing room and down-arrow cases from the reusable probe registry.

  647. Reported: “The reusable suite is back to stable cases only. I’m adding symbolic labels for the event-direction table and the logic resume/entry-IP helpers, because those are exactly the cross-version anchors we’ll want when comparing other interpreter builds.”

    Action/result: Added symbolic labels for logic resume helpers and the menu-direction event table.

  648. Reported: “The docs already had most of the raw input machinery; I’m tightening it with two cross-version-friendly names: the type/value event enqueue helper and the menu-direction raw key map.”

    Action/result: Added code.input.enqueue_event and data.input.menu_direction_event_map labels.

  649. Reported: “I’m updating the bytecode chapter to make the important distinction explicit: source shows down-arrow maps to movement code 5 inside the menu event path, but our current QEMU key injection has not produced that raw event reliably.”

    Action/result: Updated logic_bytecode.md with down-arrow and room-switch evidence boundaries.

  650. Reported: “The bytecode chapter now states the boundary between source-backed facts and failed QEMU instrumentation. I’m adding the detailed clean-room trail next: exact disassembly ranges, exact QEMU commands, and what each failed probe ruled out.”

    Action/result: Added a clean-room notes section for this pass.

  651. Reported: “I’m also updating current-status and compatibility notes so the next session doesn’t accidentally retry the same naive room/down-arrow fixtures without new instrumentation.”

    Action/result: Updated current_status.md and compatibility_testing.md with attempted-but-not-promoted fixture details.

  652. Reported: “Docs and logs are updated. I’m running the validation pass now: unit tests, opcode evidence check, mdBook build, and whitespace check.”

    Action/result: python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check all passed.

  653. Reported: “Validation is clean. I’m doing a final diff/status pass now so I can give you an accurate handoff without accidentally including generated artifacts.”

    Action/result: Final status showed docs-only tracked changes plus the existing untracked build/ and 002962_ms_dos_622/ directories.

  654. Reported: “Got it: I’ll leave the current docs changes uncommitted and keep working on top of them. I’m starting with the real SQ2 logic resources, because that should tell us what the room-switch contract actually looks like before we spend more QEMU cycles.”

    Action/result: Started a source-first pass over SQ2 logic resources.

  655. Reported: “The repo already has a local logic disassembler and resource readers, so I can stay inside the clean-room tooling. I’m checking how much it currently prints before deciding whether to extend it for logic0/room-call analysis.”

    Action/result: Re-read tools/disassemble_logic.py and disassembled logic 0.

  656. Reported: “Logic 0 is the real ‘director’ script: it has early room switches during boot, then a flag-5 block that resets ego/object state and preloads resources for the current room. I’m narrowing on the opcodes that actually call room scripts and trigger room switches.”

    Action/result: Searched SQ2 logic 0 for switch/load/call/resume actions.

  657. Reported: “The pattern is already visible in real resources: many room logics begin with if flag 5, do room-entry setup, then continue to per-cycle behavior and end. Logic 0 does not call the current room by number as a var; instead the room resource itself is loaded by the switch helper and resumed/called as part of the linked logic cache behavior. I’m going to map that across room logics rather than infer from one example.”

    Action/result: Checked room logic resources 1 through 4 and then broadened the scan; the next step corrected the initial inference.

  658. Reported: “This corrects my last inference: logic 0 is not calling every room directly, but the loaded room logic clearly begins at offset 0 and handles flag 5 as its entry block. I’m checking whether the engine calls loaded logics through a separate cache/list runner, because code.engine.main_cycle only has the obvious call_logic(0) call in the slice we already read.”

    Action/result: Re-read nearby executable helpers and continued searching logic 0.

  659. Reported: “Found it: logic 0 does call the current room, but later than the entry/menu block. At offset 0x053e it does call_logic_var(v0), so the room number in variable 0 is the dispatch key. That explains why synthetic room fixtures need a logic-0 loop with call_logic_var(v0), not a target logic that magically runs after switch_room.”

    Action/result: Identified SQ2 logic 0 bytecode offset 0x053e as the current-room dispatch point.

  660. Reported: “Now I have a better fixture shape: mimic logic 0’s real pattern by switching rooms, then having logic 0 call call_logic_var(v0) so the loaded room logic runs while flag 5 is still set. I’m adding that as a targeted compatibility probe instead of the earlier one-shot draw.”

    Action/result: Temporarily added two logic-0-shaped room dispatch probes.

  661. Reported: “The new cases are in the reusable suite, but only provisionally until QEMU agrees. I’m running the targeted local test and then the room-switch batch.”

    Action/result: Targeted registry test passed; generated bytecode looked as intended.

  662. Reported: “The bytecode shape looks right: logic 0 switches once, then uses call_logic_var(v0) to run the current room logic. I’m putting the two cases through QEMU now.”

    Action/result: Started QEMU batch room_dispatch_001.

  663. Reported: “Still mismatched, so I’m not promoting it. This time the fixture matches the real call_logic_var(v0) pattern, which tells us another part of real logic 0’s entry sequence is still required. I’m opening the capture/report and then I’ll pull these provisional cases back out.”

    Action/result: room_dispatch_001 mismatched with the same blank-screen signature as prior room attempts.

  664. Reported: “The capture is the same blank screen signature. I’m checking fixture construction too, because if extra logic resources are not patched into LOGDIR correctly then call_logic_var(v0) would never reach the target logic no matter how good the script pattern is.”

    Action/result: Inspected LOGDIR and VOL.3; the extra logic resource was patched at the expected offset.

  665. Reported: “The fixture construction looks correct, including patched LOGDIR entries for logic 1 and 2. So this failure is now genuinely about the runtime room/logic lifecycle, not a missing resource patch. I’m removing the provisional cases again and documenting the stronger source finding: real logic 0 calls call_logic_var(v0) at offset 0x053e, while room logics gate entry setup on flag 5.”

    Action/result: Removed the temporary failing room-dispatch cases from the reusable probe registry.

  666. Reported: “I’m updating the spec now with the real room-script structure: byte variable 0 is current room, byte variable 1 is previous room, byte variable 2 is entry boundary, and SQ2 logic 0 calls the current room via call_logic_var(v0) after the global entry/menu work.”

    Action/result: Updated symbolic labels and logic bytecode prose with the room variable roles and logic-0 dispatch offset.

  667. Reported: “The main bytecode chapter and labels now have the real SQ2 room dispatch model. I’m adding the chronological command trail and the failed QEMU replay name, then I’ll run tests/docs.”

    Action/result: Updated clean-room notes, current status, and compatibility testing with the real-SQ2 room dispatch source pass and failed QEMU batch.

  668. Reported: “The temporary QEMU cases are back out, so the reusable test suite should remain stable. I’m doing a quick diff check on tools/tests now, then I’ll validate the docs and local suite.”

    Action/result: Confirmed there was no remaining tracked diff in tools/logic_interpreter_probe.py or tests/test_logic_interpreter_probe.py.

  669. Reported: “Progress log is caught up. I’m running the validation pass: unit suite, evidence check, mdBook, and whitespace.”

    Action/result: Ran the local unit suite, opcode-evidence check, mdBook build, and diff whitespace check successfully after the room-dispatch documentation updates.

  670. Reported: “Validation is clean. I’m checking final status now so I can hand off exactly what changed and what remains uncommitted.”

    Action/result: Checked final Git status; the remaining worktree changes are documentation files, with generated build/ artifacts and local DOS installer images still untracked.

  671. Reported: “I’ll take the five-step list as the work queue: room lifecycle first, then input/menu, selection/system, sound, and labels/docs. I’m going source-first and will only use QEMU if a result needs confirmation or turns into a durable test.”

    Action/result: Started a five-step source-first lifecycle pass and created a working plan.

  672. Reported: “First I’m gathering the existing symbol map, notes, and helper scripts so I can extend the current evidence trail instead of creating a parallel one.”

    Action/result: Read the symbol map, searched existing notes/tools/tests, and confirmed only generated/local directories were untracked.

  673. Reported: “The current docs already have many labels, so this pass can be a tightening pass rather than a broad search. I’m opening the disassembly around the five target families now, especially the call sites that explain why the previous synthetic room fixture is too thin.”

    Action/result: Opened real SQ2 logic 0 and the first broad disassembly windows for room, main-cycle, input, menu, save, and sound paths.

  674. Reported: “One wrinkle: the raw full disassembly includes executable header bytes, so some addresses are offset from the “image” addresses in the docs. I’m checking the existing slice/disassembly workflow before trusting any raw line numbers.”

    Action/result: Reconfirmed the documented convention that file offset is image offset plus 0x200 for build/cleanroom/AGI.decrypted.exe.

  675. Reported: “The project’s convention is clear: file offset is image offset plus 0x200. I’m using focused ndisasm windows with that correction for the room switch, main cycle, input/menu, save/restore, and sound paths.”

    Action/result: Re-disassembled focused corrected windows for 0x175c, 0x0150, 0x4529, and 0x93d1.

  676. Reported: “The first source pass already answers two useful questions: room switching deliberately returns zero to force the main cycle back into logic 0, and menu arrow navigation is driven by normalized type-2 events, not status bytes. I’m reading save/system and sound now.”

    Action/result: Identified the room-switch zero return and menu type-2 movement dispatch, then opened save, restore, selector, and sound windows.

  677. Reported: “I’m going one layer down on the room reset calls now. The switch helper’s call list is compact, so mapping those callees should give us a higher-level lifecycle spec without guessing from QEMU symptoms.”

    Action/result: Mapped reset/resource-event callees around 0x10d0, 0x1485, 0x7060, and restore replay helper 0x681c.

  678. Reported: “The save/restore path is also cleaner now: the selector helper is responsible for prompt-mode setup, path validation, and preserving the input marker; the save/restore handlers do the actual block I/O after it returns a nonzero selection. I’m checking the event queue root next so the menu/input notes can name the globals.”

    Action/result: Mapped event queue enqueue/dequeue helpers and identified queue storage 0x11ba, write pointer 0x120a, and read pointer 0x120c.

  679. Reported: “I spotted one bad byte dump: I asked xxd for the executable at the data-table offset, which is the wrong backing file for AGIDATA data. I’m correcting that against SQ2/AGIDATA.OVL before I document the menu table.”

    Action/result: Discarded the executable dump and confirmed DS:0x16b3/DS:0x16d7 movement tables from SQ2/AGIDATA.OVL.

  680. Reported: “I have enough source-backed material to start editing: this will be mostly spec/label expansion, plus the progress log. I’m going to keep the wording careful: QEMU did not validate new behavior here, but the disassembly improves the model and narrows what future tests should target.”

    Action/result: Prepared documentation-only edits with no new QEMU cases.

  681. Reported: “I’m making the documentation edits now. The main additions are: the room switch’s exact return-to-main-cycle behavior, event queue/menu navigation roles, save/restore selector responsibilities, restore-time resource replay, and sound state labels.”

    Action/result: Updated docs/src/logic_bytecode.md with refined room, menu/input, save/restore, and sound semantics.

  682. Reported: “The bytecode chapter has the first wave of updates. I’m adding the stable labels next, including the event queue globals and room/save/sound helper roles that future version comparisons will need.”

    Action/result: Added symbolic labels for heap reset, input event queue, menu drawing/navigation helpers, restore replay, sound helpers, and event queue globals.

  683. Reported: “Labels are expanded. I’m adding the chronological clean-room notes for the commands and observations, then I’ll refresh the current-status page so the handoff reflects this pass.”

    Action/result: Added a clean-room notes section for the source-first lifecycle pass.

  684. Reported: “The clean-room note is in place. I’m checking its placement now; if I accidentally inserted it inside an older section instead of after it, I’ll move it so the chronology stays readable.”

    Action/result: Found the new lifecycle note had split an older picture/view command list.

  685. Reported: “Placement is good. I’m reading the following lines too just to make sure the older picture/view section still continues cleanly after the new note.”

    Action/result: Confirmed the split was real because older picture/view commands continued after the new lifecycle note.

  686. Reported: “Yep, one issue: the new section split an older picture/view command list. I’m moving the lifecycle note down below that older section so the historical record stays tidy.”

    Action/result: Removed the misplaced lifecycle note from the middle of the picture/view command list.

  687. Reported: “The accidental split is removed. I’m inserting the lifecycle pass after the picture/view section’s documented result now.”

    Action/result: Reinserted the lifecycle pass after the picture/view documented result and before the next historical section.

  688. Reported: “The note is back in chronological shape. I’m updating current status now: several “what’s next” items have changed from “map this” to “we have the source model; build targeted tests only where possible.””

    Action/result: Updated docs/src/current_status.md with the refined room, menu, save/restore, and sound source models and next-test guidance.

  689. Reported: “The main docs are updated. I’m recording the work-session updates in the progress log before running validation, so the historical trail includes the source pass and the small correction I made while editing.”

    Action/result: Added progress-log entries for the source-first lifecycle pass, including the corrected AGIDATA table dump and the note-placement fix.

  690. Reported: “Progress log is caught up through the documentation edits. I’m doing a quick read of the touched sections for continuity before validation.”

    Action/result: Reviewed the edited bytecode and clean-room note sections for continuity.

  691. Reported: “The touched sections read coherently now. I’m running the validation suite and docs build next; since these are documentation-only edits, the full local unit suite plus mdBook/diff checks should be enough.”

    Action/result: Ran the full local unit suite, opcode evidence check, mdBook build, and diff whitespace check successfully.

  692. Reported: “Validation is clean. I’m checking the final diff/status and then I’ll add the closing progress-log entries for the validation and handoff.”

    Action/result: Checked final status and diff summary; the worktree has documentation changes only, plus expected untracked build/ and 002962_ms_dos_622/ directories.

  693. Reported: “I’m going to turn the room-switch finding into a fixture that mirrors the source-shaped lifecycle more closely: logic 0 performs the switch once, then on engine re-entry dispatches through call_logic_var(v0) into a target room logic that owns its own picture/view setup. Then I’ll let QEMU tell us whether that structure matches the original engine rather than treating a guess as fact.”

    Action/result: Started a room-switch re-entry probe pass focused on source-shaped logic-0/current-room dispatch.

  694. Reported: “The helper insertion point moved a little from what I had in my head, so I’m checking the exact neighborhood before patching. Small friction, but it’s useful because the harness has accumulated a lot of deliberately-shaped fixture helpers.”

    Action/result: Re-read the probe helper area and call-logic case list in tools/logic_interpreter_probe.py.

  695. Reported: “I’ve added explicit helper code for the re-entry shape, with the initialization flag set before the room switch because the switch path itself short-circuits the current logic invocation. That detail is exactly the kind of thing that made the earlier fixtures slippery.”

    Action/result: Added room re-entry helper functions and initial immediate and variable-selected room-switch probe cases.

  696. Reported: “I’m also pinning the new cases in the unit coverage list. It’s not deep verification by itself, but it prevents these probe definitions from quietly disappearing while we iterate on the harder QEMU behavior.”

    Action/result: Added unit coverage assertions for switch_room_reentry_dispatches_current_room and switch_room_v_reentry_dispatches_current_room.

  697. Reported: “The first unit pass caught a naming mismatch in the dataclass constructor, not a behavioral problem. I’m correcting that before moving on to QEMU so the probe harness stays internally clean.”

    Action/result: Fixed the new case constructor to use expected_group_no, expected_frame_no, and expected_baseline_y.

  698. Reported: “The dataclass fix is in. Next I’m doing the quick deterministic test run again, then I’ll run the two new fixtures through the original interpreter.”

    Action/result: python3 -B -m unittest tests.test_logic_interpreter_probe passed: 7 tests OK.

  699. Reported: “The local harness tests pass now. I’m moving to the important part: running just the new room-switch cases under QEMU so we have original-engine evidence before documenting them as validated.”

    Action/result: First QEMU command attempted a bare --snapshot flag, but the harness only exposes snapshot image path options.

  700. Reported: “This harness doesn’t have a --snapshot flag on the current branch; it only exposes the snapshot image paths. I’ll use its normal serial runner for the two-case check.”

    Action/result: Re-ran the room-switch cases with the supported harness arguments.

  701. Reported: “Both room-switch re-entry cases matched the original engine in QEMU, including the variable-selected room form. I’m going to run the project’s compatibility unit suite next, then I’ll update the docs with the evidence and the higher-level semantics.”

    Action/result: QEMU batch room_switch_reentry_001 matched 2/2 with 0 errors and 0 mismatches.

  702. Reported: “The full local compatibility suite is green. I’m documenting the new evidence now: the source-shaped model, what the QEMU cases prove, and why the target room logic has to perform its own resource setup after the switch.”

    Action/result: python3 -B -m unittest discover -s tests passed: 99 tests OK.

  703. Reported: “The docs still correctly preserve the earlier failures, but now need a follow-up paragraph that supersedes their status. I’m also checking the opcode evidence generator so the evidence table doesn’t lag behind the new QEMU result.”

    Action/result: Found tools/logic_opcode_evidence.py still marked action rows 0x12 and 0x13 as source-backed.

  704. Reported: “The evidence generator still marks room switching as source-backed. Since the new QEMU cases match the original interpreter, I’m promoting only the tested behavior: zero-return re-entry plus current-room dispatch, not every side effect of the broad reset helper.”

    Action/result: Updated the evidence generator to cite the two matched room-switch re-entry cases for action rows 0x12 and 0x13.

  705. Reported: “The generator is updated. I’m regenerating the opcode evidence chapter now so the book reflects the promoted room-switch cases.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  706. Reported: “I’m updating the handoff/status text so it no longer tells future us that the room-switch fixture still needs to be built. The older failed attempts stay in the record; the new matched case becomes the current evidence.”

    Action/result: Updated docs/src/current_status.md with the validated room-switch re-entry fixture and revised next steps.

  707. Reported: “I’m adjusting the logic bytecode chapter next. The key nuance is that QEMU validates the control-flow shape, while several internal reset effects are still grounded in disassembly rather than direct fixture assertions.”

    Action/result: Updated docs/src/logic_bytecode.md to describe room_switch_reentry_001 and separate QEMU-validated dispatch behavior from source-backed reset details.

  708. Reported: “I’m adding the exact QEMU command to the compatibility chapter and marking the older failed room fixtures as superseded experiments, not current blockers.”

    Action/result: Updated docs/src/compatibility_testing.md with the new room-switch re-entry batch and superseded-fixture notes.

  709. Reported: “I’m appending the clean-room notes with the actual commands and observed result. This is the audit trail piece: future readers should be able to see exactly how the room-switch fixture went from failed attempts to a matched QEMU batch.”

    Action/result: Added a clean-room notes section for the room-switch re-entry QEMU validation pass.

  710. Reported: “The main docs are updated. I’m logging the intermediate progress updates now so the historical record includes the small test failure, the harness flag correction, and the successful QEMU validation.”

    Action/result: Added progress-log entries for the room-switch re-entry fixture work and QEMU result.

  711. Reported: “The progress log is caught up through the docs edits. I’m running validation now: the local unit suite again, mdBook build, and a quick diff/status pass.”

    Action/result: Ran python3 -B -m unittest discover -s tests and mdbook build docs; both passed.

  712. Reported: “Validation is clean. I’m checking the final diff now so I can give you the concise “what changed / what’s next” without making you sift through generated noise.”

    Action/result: Checked git status --short and git diff --stat.

  713. Reported: “The core validation passed. I’m doing one last whitespace check and a focused status read; then I’ll hand back the result and the next concrete reverse-engineering steps.”

    Action/result: git diff --check passed with no whitespace errors; final status still includes pre-existing modified docs and untracked generated directories.

  714. Reported: “The whitespace check is clean too. I’m adding the closing validation entries to the progress log so this turn’s record includes the green tests and book build.”

    Action/result: Added closing progress-log entries for validation and handoff.

  715. Reported: “I’ll start with the v2 room-entry boundary probe, source-first. I’m going to re-read the switch helper and the existing probe helpers, then shape the QEMU cases so they validate the entry placement through ordinary logic bytecode rather than peeking at memory.”

    Action/result: Re-read room-switch helper 0x1792, probe helper code, and existing room-switch fixtures.

  716. Reported: “The switch helper confirms the four v2 cases in code: 1 writes object 0 Y to 0xa7, 2 writes X to 0, 3 writes Y to 0x25, and 4 writes X to 0xa0 - object_width, then clears v2. I’m shaping the cases so the destination logic reads object 0 with the normal getter and only draws if the expected field matches.”

    Action/result: Added helper code for boundary-selector fixtures that use action 0x27 to read object 0 after room-switch re-entry.

  717. Reported: “I’m going to cover all four boundary selectors. The fourth case depends on object width, so I’ll initialize object 0 with view 11 frame 0 before the switch; the disassembly leaves width intact and uses it for 0xa0 - width, which should give X 140 for that 20-pixel-wide cel.”

    Action/result: Added four base cases for selectors 1 through 4 and pinned them in the logic probe unit test list.

  718. Reported: “The cases are registered. I’m running the local unit test for the probe definitions first; if that’s clean, I’ll run just these four cases through QEMU.”

    Action/result: Logic probe unit tests and py_compile passed.

  719. Reported: “The definition tests pass. I’m launching the four boundary-selector fixtures in QEMU now; this will tell us whether the disassembled field writes line up with visible original-engine behavior.”

    Action/result: Started QEMU batch room_boundary_001.

  720. Reported: “The batch is running from one snapshot boot. It has built all four fixture directories and is now executing the captures.”

    Action/result: room_boundary_001 continued running under QEMU snapshot mode.

  721. Reported: “The first boundary case mismatched, so I’m not promoting the set yet. I’m going to inspect the generated report and capture signature to see whether the validation draw never happened or whether one of the expected coordinates is wrong.”

    Action/result: The first capture was all white; no validation draw occurred.

  722. Reported: “The capture is a blank all-white screen, which means the destination room ran far enough to show its picture but the coordinate condition did not pass. I’m going back to the object-position getter and setup path in the disassembly before trying new hypotheses, because the field write may be right while my bytecode-level observation is reading a different or reset field.”

    Action/result: Re-read object getter/setup-related disassembly.

  723. Reported: “The getter path is byte-based and normal object fields are at +0x03/+0x05, as expected. The failure likely means the switch helper’s later refresh calls alter object 0 after the boundary write, or object 0’s special lifecycle differs from ordinary objects. I’m going to build a diagnostic fixture that encodes the observed value into a visible draw choice, still using bytecode and QEMU rather than debugger memory.”

    Action/result: Generated a diagnostic under build/ to test whether action 0x27 can observe object 0 after ordinary setup.

  724. Reported: “Before guessing the boundary value, I’m checking the observation mechanism itself with a generated diagnostic case under build/: set object 0, read it back with action 0x27, and draw only if the getter reports the fields we wrote. If that passes, the mismatch is specifically post-switch behavior.”

    Action/result: Diagnostic object-0 getter case matched QEMU.

  725. Reported: “The diagnostic getter case matched, so action 0x27 can observe object 0 normally. Now I’m generating a single visual diagnostic that draws different marker positions for several post-switch (x,y) hypotheses, plus a separate marker if v2 was cleared. That should reveal which part of the condition is false.”

    Action/result: Generated and ran the boundary marker-map diagnostic; it still produced an all-white capture.

  726. Reported: “Aha, the diagnostic never drew even the v2 == 0 marker because my pre-switch object setup bound view 11 without loading it first. That likely aborted before the switch. I’m fixing the fixture to load view 11 before setting up object 0, then I’ll rerun the boundary batch.”

    Action/result: Corrected boundary fixtures to load view 11 before binding object 0.

  727. Reported: “The pre-switch setup now loads the view resource before binding object 0. I’m re-running the quick unit check and then the four-case QEMU batch.”

    Action/result: Logic probe unit tests and py_compile passed again.

  728. Reported: “The corrected boundary batch is executing now. If the disassembly-derived values are right, all four should draw their validation sprite after the re-entry dispatch.”

    Action/result: QEMU batch room_boundary_002 matched 4/4 with 0 mismatches and 0 errors.

  729. Reported: “Great, all four boundary selectors matched QEMU after fixing the pre-switch setup. I’m going to promote these as compatibility cases and update the docs with the exact distinction we just learned: object 0 must have a loaded/bound view before the switch for the right-edge width case to be meaningful.”

    Action/result: Promoted the four boundary-selector cases and began documentation updates.

  730. Reported: “I’m checking the relevant doc sections and the evidence generator now so the probe list, opcode evidence, compatibility instructions, and clean-room notes all agree.”

    Action/result: Found the evidence generator needed to mention the new boundary-selector cases for action 0x12.

  731. Reported: “I’m promoting the boundary coverage in the evidence generator and docs now. The wording will stay careful: the bytecode-visible entry-boundary placements are QEMU-validated; unrelated internals like resource-event recording are still source-backed.”

    Action/result: Updated tools/logic_opcode_evidence.py.

  732. Reported: “The generator is updated. I’m regenerating the opcode evidence chapter before editing the prose around it.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  733. Reported: “Next I’m editing the room-switch prose and compatibility instructions to include the four matched v2 selectors and the pre-switch view-load lesson from the failed first attempt.”

    Action/result: Updated docs/src/logic_bytecode.md and docs/src/compatibility_testing.md.

  734. Reported: “The bytecode and compatibility chapters now include the boundary batch. I’m updating the handoff page so “entry boundary” moves out of the recommended next step and into validated room-switch behavior.”

    Action/result: Updated docs/src/current_status.md.

  735. Reported: “I have the concise switch-helper dump now. I’m appending a clean-room note section with the failed first batch, the diagnostic getter check, the corrected batch, and the resulting semantics.”

    Action/result: Added the room entry-boundary selector QEMU validation section to docs/src/clean_room_executable_notes.md.

  736. Reported: “I’ll take the next small, crisp room-switch probe: controlled previous-room updates. I’m going to make the fixture set v0 to a known synthetic previous room before switching, then the destination room logic will assert v0, v1, and cleared v2 through normal bytecode before drawing.”

    Action/result: Started the current/previous-room variable validation pass.

  737. Reported: “I’m checking the current dirty state and helper locations first, since we’re building on the uncommitted boundary-selector work from the last slice.”

    Action/result: Confirmed the uncommitted boundary-selector changes were present and identified the room-switch helper insertion points.

  738. Reported: “The existing helpers are a good fit. I’m adding a narrow previous-room helper now: pre-switch logic writes a synthetic old room into v0, switches to room 1, then the destination room draws only if v0 == 1, v1 == old_room, and v2 == 0.”

    Action/result: Added helper code for current/previous-room validation fixtures.

  739. Reported: “The helper is in. I’m registering both immediate and variable room-switch versions so 0x12 and 0x13 both get previous-room coverage.”

    Action/result: Added switch_room_sets_current_previous_and_clears_boundary and switch_room_v_sets_current_previous_and_clears_boundary.

  740. Reported: “The new cases are pinned in the registry tests. I’m running the local probe tests and Python compile check before QEMU.”

    Action/result: tests.test_logic_interpreter_probe and py_compile passed.

  741. Reported: “The local checks pass. I’m running the two previous-room cases in QEMU now.”

    Action/result: QEMU batch room_previous_001 matched 2/2 with 0 errors and 0 mismatches.

  742. Reported: “Both previous-room cases matched QEMU. I’m updating the evidence generator and docs now so the room-switch section reflects v0/v1/v2 as validated behavior, with the rest still source-backed.”

    Action/result: Began promoting the current/previous-room evidence into docs.

  743. Reported: “I’m going to regenerate the opcode evidence after adding the new case names, then fold the same result into the narrative docs.”

    Action/result: Updated tools/logic_opcode_evidence.py.

  744. Reported: “The evidence table is regenerated. I’m updating the room-switch prose and current-status page next.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  745. Reported: “I’m patching the docs now: v1 moves from planned/source-backed to QEMU-validated, while resource replay and broader reset details remain next.”

    Action/result: Updated docs/src/logic_bytecode.md, docs/src/compatibility_testing.md, and docs/src/current_status.md.

  746. Reported: “The narrative docs are updated. I’m appending the clean-room note section for the v1 validation run with the exact QEMU command and result.”

    Action/result: Added the room current/previous variable QEMU validation section to docs/src/clean_room_executable_notes.md.

  747. Reported: “I’ll proceed with the low-risk custom-logic path first: add the 0x13 variable-room versions of the four v2 boundary selector tests. That gives us another clean behavioral brick before we reach for memory snapshots or patched instrumentation.”

    Action/result: Started the variable-selected room boundary pass.

  748. Reported: “I’m checking the committed baseline and the helper shape so I can add these as a small extension rather than duplicating the immediate-room boundary code.”

    Action/result: Confirmed the working tree only had untracked generated directories and inspected room-switch helper definitions.

  749. Reported: “The helper already takes an arbitrary switch action, so the change can stay tidy: parameterize the boundary case over immediate vs variable switch action, then add four switch_room_v_boundary_* cases.”

    Action/result: Planned a small parameterization of room_boundary_case.

  750. Reported: “I’m making room_boundary_case accept a switch action, defaulting to 0x12, then using that for the four 0x13 variants.”

    Action/result: Updated room_boundary_case to accept an optional caller-supplied switch action.

  751. Reported: “The helper is parameterized. I’m inserting the four variable-room boundary cases next to the immediate boundary cases and using separate validation draw positions so mismatches are easy to identify.”

    Action/result: Added four switch_room_v_boundary_* probe cases.

  752. Reported: “The four 0x13 boundary cases are registered. I’m pinning them in the unit test list now.”

    Action/result: Added unit assertions for the four variable-room boundary cases.

  753. Reported: “I’m running the local probe tests and compile check before launching QEMU.”

    Action/result: tests.test_logic_interpreter_probe and py_compile passed.

  754. Reported: “Local checks pass. I’m running the four variable-room boundary cases under QEMU now.”

    Action/result: Started QEMU batch room_boundary_var_001.

  755. Reported: “The QEMU batch is running from one snapshot boot. It has built the fixtures and is executing the capture/compare loop now.”

    Action/result: QEMU batch execution continued.

  756. Reported: “All four variable-room boundary selectors matched. I’m updating the evidence table and docs now, then I’ll run the full validation suite.”

    Action/result: QEMU batch room_boundary_var_001 matched 4/4 with 0 mismatches and 0 errors.

  757. Reported: “First I’ll update the generated evidence source so 0x13 cites the boundary cases alongside the re-entry and previous-room cases, then regenerate the markdown.”

    Action/result: Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  758. Reported: “The evidence chapter is regenerated. I’m updating the room-switch prose, compatibility commands, current-status handoff, clean-room notes, and progress log.”

    Action/result: Began docs updates for variable-room boundary validation.

  759. Reported: “I’m patching those sections so they explicitly say both immediate and variable-selected room switches now share the same boundary behavior.”

    Action/result: Updated docs/src/logic_bytecode.md, docs/src/compatibility_testing.md, and docs/src/current_status.md.

  760. Reported: “I’m appending a clean-room note section for this run: exact command, matched result, and the fact that it completes the v2 selector coverage for 0x13.”

    Action/result: Added the variable-room entry-boundary selector QEMU validation section to docs/src/clean_room_executable_notes.md.

  761. Reported: “I’ll continue with room-switch object reset behavior using the unmodified interpreter first. I’m going to make a fixture where a persistent object is visible before the switch, then verify the destination room does not retain that draw state.”

    Action/result: Began a targeted room-switch persistent-object reset probe.

  762. Reported: “I’m reading the current helper and comparison code so the new case fits the existing QEMU snapshot harness instead of inventing a new path.”

    Action/result: Inspected logic probe helpers and expected-render comparison behavior.

  763. Reported: “The comparator can already catch ‘extra survivor’ sprites, which is perfect for this. I’m adding two cases now: immediate switch and variable-selected switch, both with a pre-switch active object.”

    Action/result: Planned paired 0x12 and 0x13 object-reset fixtures.

  764. Reported: “The object-reset fixture builder is in. It loads view 11, activates object 10 before the room switch, then expects only the destination-room validation sprite afterward.”

    Action/result: Added room_pre_switch_logic0_code and room_pre_switch_object_reset_case to tools/logic_interpreter_probe.py.

  765. Reported: “The cases are registered and the unit expectations know about them. I’m running the quick local tests and compile check before starting QEMU.”

    Action/result: Added switch_room_removes_preexisting_persistent_object and switch_room_v_removes_preexisting_persistent_object plus unit coverage.

  766. Reported: “Oops, I just noticed the plan still says the local checks are running; they actually passed. I’m correcting that and moving on to the QEMU validation.”

    Action/result: Quick local unit test and py_compile checks passed.

  767. Reported: “Local checks pass. I’m launching the two-case snapshot batch now; if the pre-switch object survives into room 1, the rendered output should mismatch as an extra sprite.”

    Action/result: Started QEMU batch room_object_reset_001.

  768. Reported: “Both object-reset cases matched. That validates the visible effect: an active persistent object from before the room switch is absent in the destination render for both 0x12 and 0x13. I’m documenting that carefully as an observable rendering result, not a complete proof of every object-table field reset.”

    Action/result: QEMU batch room_object_reset_001 matched 2/2 with 0 mismatches and 0 errors.

  769. Reported: “I’ll add the two new case names to the generated opcode evidence, then regenerate the markdown so the action tables point to the new proof.”

    Action/result: Updated tools/logic_opcode_evidence.py evidence rows for actions 0x12 and 0x13.

  770. Reported: “The evidence table is regenerated. I’m updating the higher-level bytecode notes, compatibility testing chapter, and current status with the precise scope of this result.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md and began documentation updates.

  771. Reported: “I’m updating these sections to add the ‘persistent object removed’ observable behavior without overstating it as a full object-field memory reset.”

    Action/result: Updated docs/src/logic_bytecode.md, docs/src/compatibility_testing.md, and docs/src/current_status.md.

  772. Reported: “Now I’ll add the new QEMU command and matched result into the compatibility chapter so the fixture can be rerun later.”

    Action/result: Added the room_object_reset_001 command and result to docs/src/compatibility_testing.md.

  773. Reported: “I’m updating current status with the new observable and the remaining uncertainty around broader room-switch resource/object reset internals.”

    Action/result: Refined docs/src/current_status.md room-lifecycle status.

  774. Reported: “I’m appending the clean-room notes for this object-reset probe: the fixture shape, command, matched result, and the boundary of what it proves.”

    Action/result: Added the room-switch persistent-object reset QEMU validation section to docs/src/clean_room_executable_notes.md.

  775. Reported: “I’ll pick this back up at the object-reset documentation point and then run the full local checks. The useful result we already have is a small but solid one: pre-switch active object drawing does not visibly survive either immediate or variable room switches.”

    Action/result: Resumed from the room-switch object-reset documentation handoff.

  776. Reported: “The repo is exactly where the handoff said it was: variable-room boundary notes are already in place, and the object-reset result still needs its clean-room trail. I’m adding that trail now, including the QEMU command and the narrower interpretation of what the fixture proves.”

    Action/result: Confirmed dirty files and appended the object-reset clean-room evidence section.

  777. Reported: “That note is now in the executable log. Next I’m preserving the user-facing step log, then I’ll validate the code, generated evidence, and mdBook together so the docs don’t drift from the probes.”

    Action/result: Added progress entries for the object-reset probe work.

  778. Reported: “The historical log is caught up through this run. I’m running the usual verification set now: unit tests, mdBook build, and whitespace checks.”

    Action/result: Ran python3 -B -m unittest discover -s tests, mdbook build docs, and git diff --check; all passed.

  779. Reported: “All three checks are green. I’m cleaning up the generated Python cache and then I’ll take one last status/diff snapshot so I can give you a clean handoff.”

    Action/result: Removed tools/__pycache__ and checked status/stat output.

  780. Reported: “The object-reset probe is validated and documented, so I’m going to keep moving rather than stop there. Next I’ll go back to the disassembly first and try to pin down which room-switch helper clears/initializes object state, then I’ll only use QEMU if there’s a specific observable to confirm.”

    Action/result: Shifted from QEMU fixture work back to source-first disassembly analysis of room-switch reset helpers.

  781. Reported: “I’ve got the current handoff notes in view. The next useful static target is code.room.switch_state at image 0x1792 and the dynamic reset helper at 0x1485, because those are where the room switch seems to fan into object/resource cleanup.”

    Action/result: Re-read current status and symbolic labels; searched for room-switch/reset references.

  782. Reported: “One trap popped up in the search output: the whole-file disassembly includes the MZ header, so its printed offsets are not the same as the project’s image offsets. I’m using the focused ndisasm -o image_offset -e image_offset+0x200 form now, which is the reliable one we’ve been documenting.”

    Action/result: Rejected misleading whole-file offset readings and used focused image-offset disassembly.

  783. Reported: “The room-switch body gives a very concrete object-record reset loop: it walks every 0x2b-byte object record and clears only selected fields/bits, while seeding several timing bytes to 1. I’m checking the adjacent helpers now so I can name the calls around that loop instead of leaving them as raw addresses.”

    Action/result: Identified the room-switch object loop at 0x17b6..0x17e5 and inspected helpers around 0x10d0, 0x30d6, 0x4482, and 0x4c23.

  784. Reported: “The static pass found a correction worth making: earlier prose says the room switch clears object bytes +0x1e, +0x1f, and +0x20, but the instructions keep AL = 1 and store 1 into those three bytes. That matters for a portable spec, so I’m going to fix the docs and label the cache-reset helpers while I’m here.”

    Action/result: Determined that room switch seeds object step/frame-timer bytes to 1 rather than clearing them.

  785. Reported: “The object field map confirms those three bytes are step size and frame-timer reload/current counters. I’m checking whether there’s an existing bytecode getter we can use for a small QEMU confirmation, but the spec correction itself already comes straight from the room-switch instruction sequence.”

    Action/result: Re-read object field documentation and opcode getter coverage; no direct bytecode getter exists for all three reset bytes.

  786. Reported: “There’s a second correction in the same static pass: room switching does not wipe the whole logic cache root. The helper at 0x10f7 preserves the first logic cache record and unlinks later records, while separate helpers clear the view, picture, and sound cache roots.”

    Action/result: Mapped helper 0x10d0: 0x10f7 truncates the logic cache to its head, while 0x396d, 0x50cc, and 0x49dc clear view, sound, and picture cache roots.

  787. Reported: “I’m going to make this as a source-backed correction rather than invent a new behavioral claim: exact object bytes from 0x17b6..0x17e5, exact cache reset calls from 0x10d0, and the QEMU result only for the visible persistent-object disappearance.”

    Action/result: Updated docs/src/logic_bytecode.md with the corrected object-reset and cache-reset source model.

  788. Reported: “The bytecode chapter now reflects the corrected reset bytes and cache behavior. I’m adding the same source-backed cache distinction to the logic-resource chapter so future work doesn’t accidentally rebuild the older ‘clear everything’ model.”

    Action/result: Added a room-switch cache reset section to docs/src/logic_resources.md.

  789. Reported: “I’m assigning symbolic names for the newly clarified helpers, since these are exactly the kinds of routines that should carry across later interpreter builds even when addresses move.”

    Action/result: Updated docs/src/symbolic_labels.md with labels for the logic-cache truncation helper, room-cache reset helper, cache-root clear helpers, input reset helper, and cache-root globals.

  790. Reported: “The symbolic map is updated. I’m threading the correction into the status handoff now, so a later session starts from the refined source model instead of the older shorthand.”

    Action/result: Updated docs/src/current_status.md with the corrected room-switch reset model.

  791. Reported: “Before I write the clean-room note, I’m rerunning the key disassembly slices in short form. That gives the documentation a tidy command trail instead of relying on the oversized exploratory dumps.”

    Action/result: Reran compact ndisasm slices for room-switch object reset, room cache reset, view/picture/sound cache root clear helpers, and input reset.

  792. Reported: “The compact slices confirm the correction cleanly. I’m appending the source-pass note now: what changed, what commands support it, and what remains dynamic/QEMU-backed versus source-backed.”

    Action/result: Added the room-switch object/cache reset source-correction section to docs/src/clean_room_executable_notes.md.

  793. Reported: “Verification is green again. I’m doing one last scan for the old incorrect wording and then I’ll summarize where we landed.”

    Action/result: Ran the final test/book/whitespace verification set; an initial wording-scan command failed because shell backticks in the search string were not quoted safely.

  794. Reported: “That last scan command tripped over shell backticks in the search string, not over the repo. I’m rerunning the wording scan with safer quoting.”

    Action/result: Reran the wording scan and found one older historical clean-room note that still said the room switch clears the logic cache root.

  795. Reported: “The scan found one old historical note still saying ‘clears the logic cache root.’ Since that sentence is now known to be inaccurate, I’m correcting it in place and leaving the new source-correction section as the evidence trail for why.”

    Action/result: Corrected the older clean-room note to say room switching resets caches through 0x10d0, with the later correction identifying that it preserves the first logic cache record and clears view, sound, and picture cache roots.

  796. Reported: “I’ll rerun the wording scan and the lightweight checks after that correction.”

    Action/result: Reran the wording scan, mdbook build docs, and git diff --check. The checks passed; the only remaining scan hit is the intentional correction sentence describing the old wording.

  797. Reported: “I’ll pick up the source-first thread: event/resource recording and restore replay, then connect it back to the logic opcodes and docs. I’ll keep QEMU on the shelf unless the disassembly leaves a behavior ambiguous enough to merit a probe.”

    Action/result: Started a source-first pass over the resource-event recorder and restore replay path, using existing docs and disassembly rather than new QEMU probes.

  798. Reported: “The event recorder shape is already clearer from the disassembly: it is a compact two-byte pair log, gated by both a recording-enable word and flag 7. I’m now chasing every call site so the docs tie the bytecode actions to the replayable resource events instead of treating the event buffer as a side note.”

    Action/result: Disassembled 0x705e..0x714c and the event call sites, mapping the pair buffer base/write/read/count globals, capacity, high-water word, recording gates, and all direct code.event.record_pair callers.

  799. Reported: “One useful connection dropped out: event kind 5 is not a normal resource load at all. It records a four-pair packet for a temporary/display object path at 0x2d52, then restore consumes the next three pairs as parameters before replaying that display operation.”

    Action/result: Mapped restore replay dispatch at 0x681c and helper 0x2d52, identifying event kinds 0..8 and the special kind-5 transient object/display packet format.

  800. Reported: “There is also a nice negative rule in the source: the view-resource text/display helper disables event recording while it loads and later discards a temporary view. So the replay log is intentionally about persistent room state, not every internal resource touch.”

    Action/result: Disassembled 0x5edb, confirming that actions 0x81 and 0xa2 disable event recording around their temporary load/display/discard sequence and re-enable recording before returning.

  801. Reported: “I’m going to update the docs now: symbolic labels first, then the bytecode chapter’s higher-level resource-event model, then the chronological evidence log and status handoff. The edits are documentation-only; no generated artifacts should need to be committed.”

    Action/result: Added symbolic code/data labels for the event recorder, replay cursors, action handlers, transient-display object helper, and temporary view-resource display helper.

  802. Reported: “The bytecode chapter now has the high-level replay model in spec language: pair format, capacity/gates, event-kind meanings, and the special four-pair transient-object packet. I’m adding the clean-room command trail next so the claim remains auditable.”

    Action/result: Updated docs/src/logic_bytecode.md with the replay-log model and appended this source-pass evidence trail to docs/src/clean_room_executable_notes.md.

  803. Reported: “A source detail worth tightening before verification: restore replay disables event recording, and the replay routine itself does not show a matching re-enable call in the slice I read. I’m checking the caller path so the docs don’t overstate that lifecycle.”

    Action/result: Disassembled the restore action caller, the save action for surrounding context, and all observed calls to 0x705e, 0x706d, and 0x681c.

  804. Reported: “The caller check confirms it: the only 0x706d calls in the executable are room switch and the temporary view-display helper. Restore replay and display-mode rebuild call 0x681c, which disables recording, and I don’t see a re-enable on those paths. That’s odd enough to document carefully rather than smooth over.”

    Action/result: Corrected the replay documentation and symbolic label so they no longer imply automatic recording re-enable after restore replay.

  805. Reported: “I’ve corrected the docs so they don’t imply replay turns recording back on. The current model is more honest: replay disables recording, duplicate prevention is clear, but the later lifecycle needs a targeted dynamic check or more source context.”

    Action/result: Added the no-observed-re-enable finding to docs/src/clean_room_executable_notes.md, docs/src/logic_bytecode.md, docs/src/symbolic_labels.md, and docs/src/current_status.md.

  806. Reported: “I’ll take those in order: first settle the replay recording gate from source, then map the save/restore data it depends on, then add only the QEMU validation that gives us new confidence. I’ll keep the docs updated as I go, since this is exactly the sort of trail future us will be grateful for.”

    Action/result: Began a follow-up source-first pass over the replay log, save/restore blocks, and display-mode replay path.

  807. Reported: “The first source pass is already showing a deeper wrinkle: the replay log’s active count appears to be carried through the save/restore block around [0x05e1], but replay itself reads [0x0143]. I’m tracing that carefully now, because it affects whether 0xab/0xac are just script rollback helpers or part of the save/restore contract.”

    Action/result: Re-examined the save/read block helpers and the memory ranges passed by the save and restore action handlers.

  808. Reported: “One of those disassembly slices was shifted by the EXE header offset, so I’m rerunning the save/read helper slices with the correct image offset + 0x200 file skip. This is exactly why keeping the command trail matters; the wrong slice was plausible-looking but not the helper we needed.”

    Action/result: Corrected the image/file-offset relationship used for the whole-file disassembly and reran the relevant save/restore slices.

  809. Reported: “The save-block dependency is now clearer: the first length-prefixed save block is not a tiny [0x05e1] block. It writes 0x05e1 bytes starting at DS:0x0002, which includes the pair count at [0x0143] and capacity at [0x0141]. The actual pair bytes are a later block from [0x1707], and the recording gate [0x170d] is not in either.”

    Action/result: Confirmed that save/restore persist replay capacity and active pair count in the first state block, persist pair bytes in a separate block, and do not persist the recording-enabled gate there.

  810. Reported: “For dynamic validation, a full save/restore UI probe would be useful but expensive and brittle. A better first target is action 0x8c: it calls the same replay routine without involving the save selector UI. If we can build a room-switch fixture that records events and then triggers 0x8c, we get a replay validation path with much less machinery.”

    Action/result: Chose display-mode replay as the first QEMU validation target because it reaches the same replay helper while avoiding the save-slot UI.

  811. Reported: “I’ll make the replay probe use the existing logic harness rather than a new tool. It can already patch AGIDATA.OVL, so if needed I can force the display-mode guard values for this fixture without changing the original DOS image.”

    Action/result: Extended the logic snapshot harness to support per-case launch commands, needed for SIERRA -p -c, and added monitor key mapping for -.

  812. Reported: “For the 0x8c replay fixture, I want the guard to actually enter the replay branch. The source says it requires display hardware word [0x112e] == 0 and mode word [0x1130] not 2 or 3, so I’m checking whether our fixture overlay starts that way or whether we need a small AGIDATA.OVL patch.”

    Action/result: Checked SQ2/AGIDATA.OVL; the words at 0x112e and 0x1130 initialize to 0xffff, so the QEMU fixture patches both words to zero and launches with SIERRA -p -c.

  813. Reported: “I’m picking the replay thread back up from the source-first angle: finish the save/restore dependency map, add the least brittle dynamic check, then fold it back into the spec. I’ll also preserve these working updates in the progress log before I make new substantive edits.”

    Action/result: Reviewed current docs, harness state, and diffs before continuing the replay probe.

  814. Reported: “The replay harness change is in place, and the regular unit suite passes. The first QEMU run is interesting in exactly the useful way: the engine reaches the display-mode replay fixture, but the visible background is still the second picture. I’m going back to source and memory state before assuming the replay model is wrong.”

    Action/result: Initial QEMU display-mode replay probe produced a screenshot mismatch against the first-picture hypothesis, while confirming the fixture reached the expected visual state after 0x8c.

  815. Reported: “I’m adding a second replay case that uses flag 7 instead of 0xab/0xac. If the source model is right, both should exclude the second picture from the event log; if the screen still shows the second picture, the missing piece is in display refresh rather than the log gates.”

    Action/result: Added display_mode_replay_skips_flag7_unrecorded_picture beside the 0xab/0xac rollback case.

  816. Reported: “Both replay probes show the same visible result: the second-picture background remains. That narrows it nicely: either 0x8c is not taking the replay branch under this launch mode, or the replay branch rebuilds resource state without clearing the currently visible CGA buffer. I’m checking memory around [0x112e], [0x1130], and the event buffer next.”

    Action/result: Switched from screenshot comparison to a direct QEMU memory probe of display-mode words and replay-log state.

  817. Reported: “The QEMU memory probe found the branch did execute: [0x1130] changed from 0 to 1 after 0x8c. The event log also excludes the flag-7 picture as expected. So the surprising part is display-buffer behavior, not the replay-pair gate.”

    Action/result: With DS = 0x16a5, reads showed [0x112e] = 0, [0x1130] = 1, capacity 0x32, pair count 8, and recording enabled again after the later script action.

  818. Reported: “The pair buffer is wonderfully concrete: it has room-switch logic 1, picture 0 load/prepare, view 11 load, and the final transient object packet. No picture 1 event is present. I’m capturing one fresh screenshot from the same paused VM so we can tie the memory evidence to the visible mismatch.”

    Action/result: Decoded the pair buffer as (0,1), (2,0), (4,0), (1,11), (5,0), (11,0), (0,50), (80,255) and captured the same visible background as the earlier automated run.

  819. Reported: “Fresh capture from the memory-probed VM has the same result: the log excludes picture 1, 0x8c toggled display mode, but the screen still shows picture 1’s color. I’m going to change the automated cases so they validate the observed screen behavior, while documenting the memory probe as the stronger evidence for replay-log semantics.”

    Action/result: Updated the replay fixture expectations to compare against the visible second-picture background instead of the initial first-picture hypothesis.

  820. Reported: “I’ll pick this back up from the replay work and keep it source-first. I’m going to check the current diffs and the doc spots that need to be brought into line with the QEMU memory probe before I edit anything.”

    Action/result: Reviewed the working tree, replay-related docs, and the code/test diffs for the launch-command and replay-fixture changes.

  821. Reported: “The current tree has the replay probe code changes, but the docs still contain the earlier no re-enable observed wording. I also found the new progress section inserted into an old fuzz section; I’ll move that history to the chronological tail so the log stays readable.”

    Action/result: Identified the stale replay lifecycle language and the misplaced progress section.

  822. Reported: “I’m going to patch the docs in one pass: correct the replay lifecycle wording, add the memory-probe evidence, connect kind-4/kind-5 replay back to graphics/object rendering, and move the misplaced progress section to the end with the new updates appended.”

    Action/result: Started the documentation patch pass.

  823. Reported: “I’m editing now. First pass is housekeeping: remove the replay section from the middle of the older fuzz chronology and append it after the current logic-interpreter entries with the newer replay-memory findings.”

    Action/result: Removed the misplaced replay progress section from the synthetic picture fuzz chronology.

  824. Reported: “That misplaced section is out of the fuzz chapter now. I’ll append it at the chronological end, keeping the earlier updates and the fresh observations together so the evidence trail reads in order.”

    Action/result: Appended the replay follow-up progress entries here.

  825. Reported: “The progress log now has the replay work in the right chronological spot. Next I’m tightening the spec language so it says what we actually know: replay-pair duplicate suppression is confirmed, but the later recording lifecycle and visible buffer behavior have sharper caveats.”

    Action/result: Updated the replay model in logic_bytecode.md, current_status.md, and symbolic_labels.md.

  826. Reported: “The compatibility chapter now separates the screen assertion from the memory-backed replay semantics. I’ll add the graphics/object pipeline connection next, since replay kind 4 and kind 5 are where the resource log actually turns back into drawn pixels.”

    Action/result: Linked replay event kinds 4, 5, and 8 to picture decode, overlay decode, and transient-object drawing in graphics_object_pipeline.md.

  827. Reported: “The clean-room notes now have the corrected save layout and the QEMU memory probe. I’m going to scan for stale replay wording before running the executable checks.”

    Action/result: Appended the save-block correction, static recording-gate scan, and display-mode memory probe to clean_room_executable_notes.md.

  828. Reported: “The first wording scan accidentally let shell backticks run as commands, so that result is just a quoting miss. I’m rerunning it with single quotes and then I’ll move into tests.”

    Action/result: Reran the scan safely; only intentional current caveats and historical correction wording remained.

  829. Reported: “The unit suite is green and the whitespace check is clean. I’m running the two replay fixtures against the original engine now; this is the slowest check because it boots QEMU and captures the screenshots.”

    Action/result: python3 -B -m unittest discover -s tests passed with 99 tests, git diff --check passed, and the first QEMU replay batch started.

  830. Reported: “The first replay fixture still mismatched, so I’m not going to paper over it. I’ll inspect the generated report and capture comparison to see whether the expected B background is mapped differently in this launch mode, or whether the local expected picture setup is still not matching the original screen.”

    Action/result: The generated report showed the capture and expectation were not a plain first-picture or plain second-picture match.

  831. Reported: “I misread the tuple direction from the report. In this harness, samples are (x, y, captured, expected): QEMU is showing nibble 6 and the new expected payload is nibble 4. I’m checking the old manual capture and an A-vs-B local comparison now, because the fixture should follow the evidence rather than my earlier shorthand.”

    Action/result: Compared the capture against both single-picture expectations and confirmed neither matched.

  832. Reported: “Neither plain A nor plain B matches: row 0 looks like nibble 6, while row 1 and onward show nibble 4 in the sample set. That points to the display-mode toggle changing the screen interpretation or row mapping. I’m sampling the downsampled capture by row before changing the fixture expectation.”

    Action/result: Downsampled row sampling showed an alternating pattern: even rows are nibble 6, odd rows are nibble 4.

  833. Reported: “The capture is an alternating-row display: even logical rows are nibble 6, odd rows nibble 4. I’m changing the replay fixtures to expect that interleaved background, which matches the actual display-mode effect better than either single-picture hypothesis.”

    Action/result: Added alternating_row_picture_payload() and changed both replay cases to use it as the expected background.

  834. Reported: “The remaining 62 pixels are the validation sprite, not the replay background. Since these cases are about display-mode replay, I’m going to make them background-only instead of mixing in object-color behavior from the toggled display mode. The separate memory probe already covers the replay log.”

    Action/result: Added compare_view=False, skipped the post-replay validation sprite in both replay fixtures, and taught compare_capture() to compare background-only cases.

  835. Reported: “The current spec pages now say row-interleaved and note that the automated comparison is background-only. I’m correcting the clean-room note’s result wording too.”

    Action/result: Replaced the earlier second-picture shorthand in current docs with the row-interleaved display observation.

  836. Reported: “Unit tests are green at 100 tests and whitespace is clean. I’m rerunning the replay QEMU batch with the background-only row-interleaved expectation.”

    Action/result: The corrected unit suite passed with 100 tests, and the replay QEMU batch was rerun.

  837. Reported: “The first replay case now matches. QEMU is still running the second case from the same snapshot, so I’ll let it finish and then inspect the report.”

    Action/result: Both replay cases matched QEMU with 2 matches, 0 mismatches, and 0 errors.

  838. Reported: “Both corrected replay cases match QEMU now. I’m recording that result in the compatibility and clean-room notes, then I’ll run mdBook.”

    Action/result: Added the passing build/logic-interpreter-probes/batches/replay_visible_001.json result to compatibility_testing.md and clean_room_executable_notes.md.

  839. Reported: “The compatibility chapter has the passing replay-batch result. I’m adding the same outcome to the clean-room entry beside the harness changes.”

    Action/result: Completed the clean-room result note for the corrected replay batch.

  840. Reported: “Clean-room notes have the successful batch result. I’m appending the latest working updates to the progress log so the failed hypothesis and correction are preserved rather than erased.”

    Action/result: Added progress entries for the single-picture mismatch, row-interleaving discovery, background-only harness correction, and QEMU replay success.

  841. Reported: “The progress log now records the failed single-picture hypothesis, the row-interleaving discovery, and the passing QEMU rerun. I’m building the mdBook now.”

    Action/result: mdbook build docs succeeded and git diff --check passed.

  842. Reported: “mdBook builds cleanly and whitespace is still clean. I’ll do one final wording scan and status check before wrapping this pass.”

    Action/result: Checked replay wording, working-tree status, diff stat, and the corrected QEMU replay report.

  843. Reported: “The final scan only finds historical progress-log text for the earlier working hypothesis plus the current row-interleaved wording. I’m logging the last verification steps, then I’ll rebuild mdBook once more because the progress log is itself a docs change.”

    Action/result: Recorded this final progress-log update before the last docs build.

  844. Reported: “That framing makes sense: I’ll treat the row-interleaving as a display-adapter/mode artifact, not as normative AGI semantics for the EGA path. I’m going source-first through the 0x8c display refresh chain now, with QEMU only if the disassembly leaves a specific ambiguity worth checking.”

    Action/result: Started a source-first pass over startup display-mode selection, 0x8c, display refresh helpers, and graphics overlays.

  845. Reported: “The source scan is already separating two things: the row-interleaving fixture deliberately forces hardware selector [0x112e] == 0 and toggles mode [0x1130] from 0 to 1, which is not the normal full EGA path. I’m tracing where startup assigns EGA values and where the graphics overlay branches on those words.”

    Action/result: Disassembled the command-line parser, display auto-detect, 0x8c, cursor-save/restore helpers, and logical-buffer clear/display helpers.

  846. Reported: “The static path is pretty telling: command-line -c sets [0x1130]=0, -r sets [0x1130]=1, and -p forces [0x112e]=0. The EGA-ish auto-detected/full-color path is elsewhere in startup and does not satisfy 0x8c’s hardware guard, so I’m digging into the overlay entry that only runs for [0x112e]==0.”

    Action/result: Disassembled the graphics overlay entry tables for EGA, CGA, VGA-like, and JR/Tandy-like overlays.

  847. Reported: “There’s a better explanation than old picture leaking through: the CGA overlay’s color mapper returns two bytes in mode 1, so replaying recorded color 6 after 0x8c can itself produce alternating visible colors. I’m checking the AGIDATA tables that the CGA overlay indexes now; that will tell us whether odd-row color 4 is actually the mode-1 mapping for color 6.”

    Action/result: Inspected AGIDATA.OVL:0x1d36 and confirmed the CGA overlay uses a three-byte-per-color table, with mode 1 returning a two-byte word.

  848. Reported: “The CGA table backs that up: for [0x112e]==0, the color mapper indexes three bytes per color in AGIDATA.OVL:0x1d36 and uses a two-byte mapping when [0x1130]==1. So the odd rows are more likely the recorded picture being redrawn under a different CGA mapping, not the unrecorded picture surviving. I’m checking the overlay selection code next so the docs can say why this is out of the EGA target path.”

    Action/result: Confirmed from source that 0x5685 returns colors unchanged for non-CGA/EGA-target paths and delegates to the overlay mapper only for hardware selector [0x112e] == 0 with modes other than 2 or 3.

  849. Reported: “The replay path also clears before drawing: 0x4acf -> 0x6445 -> 0x5257(0x4f4f) resets the logical picture buffer, then decodes the recorded picture. That rules out a persistent unrecorded-picture buffer as the primary explanation. I’m updating the docs and labels to classify this as a CGA-only mode-toggle artifact, outside the EGA target semantics.”

    Action/result: Updated the bytecode chapter, compatibility chapter, graphics pipeline, current status, harness descriptions, and symbolic labels to classify the row-interleaved fixture as CGA-only remapping.

  850. Reported: “I’m making a small but important correction now: the fixture names still describe the event-log exclusion, but their descriptions and docs should say the visible rows come from CGA remapping of the replayed recorded picture, not from the second picture surviving.”

    Action/result: Corrected the replay case descriptions in tools/logic_interpreter_probe.py.

  851. Reported: “The bytecode and compatibility chapters now say this is CGA-only remapping, not replay leakage. I’ll add the source trail to the graphics pipeline chapter and symbolic labels.”

    Action/result: Added the CGA color-map explanation to graphics_object_pipeline.md.

  852. Reported: “The graphics pipeline now explains the CGA mapper. I’m adding stable labels for the main color mapper, the CGA overlay mapper, and the data table so later interpreter-version comparisons can track this by role.”

    Action/result: Added symbolic labels for 0x5685, the CGA overlay mapper at near 0x9815, and AGIDATA.OVL:0x1d36.

  853. Reported: “Symbolic labels are in. I’ll update the handoff status so the next session doesn’t treat the CGA row effect as an EGA compatibility requirement.”

    Action/result: Updated current_status.md with the CGA-only/EGA-target distinction.

  854. Reported: “I’m adding the evidence trail now: startup flags, the 0x8c guard, 0x5685, CGA overlay 0x9815, the AGIDATA.OVL:0x1d36 color table, and the picture decode clear that rules out buffer leakage.”

    Action/result: Appended the source-backed correction section to clean_room_executable_notes.md.

  855. Reported: “The clean-room notes now have the correction and command trail. I’m appending the progress-log entries for this turn, then I’ll run the normal checks.”

    Action/result: Recorded this progress-log update before verification.

  856. Reported: “The documentation updates are in. I’ll scan for stale unrecorded picture supplies odd rows wording, then run tests, mdBook, and whitespace checks.”

    Action/result: The stale-wording scan found only intended corrected wording and historical progress text. python3 -B -m unittest discover -s tests passed with 100 tests, mdbook build docs succeeded, and git diff --check passed.

  857. Reported: “The scan only finds the intended corrected wording and historical progress text. Tests, mdBook, and whitespace all passed; I’ll record that final verification in the progress log and do one last mdBook/whitespace pass because the log changed.”

    Action/result: Recorded this final verification note before the last docs build.

  858. Reported: “I’ll take the recommended slice: source-first mapping of the event/replay/restore machinery, with QEMU only if the disassembly leaves a behavior question. I’ll update the historical progress log as I go so the trail stays intact.”

    Action/result: Began the replay/restore source pass and checked current status, recent clean-room notes, labels, and git status.

  859. Reported: “The current docs already contain a decent first pass on event pairs, but there’s still a gap around exactly what save/restore writes back and where recording becomes enabled again. I’m narrowing onto those routines now, with the source addresses already named in the labels file.”

    Action/result: Re-read the prior resource-event and save/restore notes plus the existing symbolic labels.

  860. Reported: “A useful correction is already staring at us: the save block explicitly excludes data.event.recording_enabled, and the static scan only found two enable calls. I’m going to re-read the raw routines around restore/replay and the callers, because the unresolved piece is probably not a hidden write but a control-flow path we haven’t named cleanly yet.”

    Action/result: Re-disassembled the replay, event helper, restore, and display-toggle routines.

  861. Reported: “The disassembly confirms the replay routine starts by stopping/clearing runtime display state, disabling event recording, and then consuming the pair stream. The restore action then performs several display/menu refreshes after replay, but still no obvious re-enable in that immediate path. Next I’m checking the saved state helpers and the room-switch enable path so the spec can distinguish ‘saved data’ from ‘runtime-only gate.’”

    Action/result: Re-read logic-cache serialization, room-switch setup, and temporary view-resource display helpers.

  862. Reported: “Found the likely missing link. In the earlier replay disassembly, the jump table bytes caused ndisasm to misparse the instruction immediately after the table; if I start disassembly at the loop-exit target itself, the first instruction should be the re-enable call. I’m checking that precise target now before changing the docs.”

    Action/result: Disassembled at image 0x6927 and confirmed call 0x706d, the event-recording re-enable helper.

  863. Reported: “That resolves the dynamic/static mismatch: replay disables recording while rebuilding resources, then explicitly calls code.event.enable_recording at image 0x6927 after the replay-pair loop. The earlier static scan missed it because the jump table bytes swallowed the E8 43 07 instruction when disassembled linearly.”

    Action/result: Promoted the post-loop re-enable from hypothesis to source-backed behavior and updated the plan.

  864. Reported: “I’ve got enough to edit. The core change is not a new behavior, but a better source map: 0x681c contains both the temporary recording disable and the post-loop re-enable at 0x6927, with the event-kind jump table at 0x6915 sitting right before it.”

    Action/result: Patched logic_bytecode.md with the corrected replay lifecycle.

  865. Reported: “The bytecode chapter now has the corrected replay lifecycle: disable during pair replay, re-enable at the post-table exit target, then rebuild visible state. Next I’m touching the labels and handoff docs so future cross-version work has the right anchors.”

    Action/result: Updated symbolic labels for the re-enable helper, replay dispatch table, replay finish target, and replay entry.

  866. Reported: “Labels now include the replay jump table and the post-loop re-enable target. I’m updating the status/compatibility prose so the current project state no longer carries the older open question.”

    Action/result: Updated current_status.md, graphics_object_pipeline.md, and compatibility_testing.md.

  867. Reported: “That patch missed because the status paragraph has slightly different wrapping. I’m reading that exact block and will patch it with the current text shape.”

    Action/result: Re-read the status paragraph and applied the corrected patch.

  868. Reported: “The narrative docs now agree on the corrected replay gate lifecycle. I’m adding the evidence section to the chronological clean-room notes, including why the earlier scan failed.”

    Action/result: Added the clean-room note section for the replay re-enable source correction.

  869. Reported: “The clean-room notes now preserve the command trail and the correction. I’m appending the matching progress-log entries for this turn, then I’ll scan for stale wording.”

    Action/result: Recorded this progress-log batch before stale-wording scans and verification.

  870. Reported: “The stale scan found only the historical section that originally recorded the unresolved state. I’m going to annotate that old paragraph so readers immediately see it was corrected by the later source pass, while preserving the history.”

    Action/result: Patched the earlier clean-room note to point to the 0x6927 correction instead of leaving a dangling unresolved statement.

  871. Reported: “That old note now points forward to the correction instead of leaving a dangling open question. I’ll rerun the stale-wording scan and then verification.”

    Action/result: Reran the stale-wording scan and git diff --check; both were clean.

  872. Reported: “The stale scan is clean and whitespace checks pass. I’m running the mdBook build plus the local unit suite now; there were no decoder/parser code changes, but this is a useful sanity check after touching the spec around tests.”

    Action/result: mdbook build docs succeeded and python3 -B -m unittest discover -s tests passed with 100 tests.

  873. Reported: “Both checks passed: mdBook built cleanly and the 100-test unittest suite is green. I’m recording that verification in the progress log, then I’ll rebuild once more because the log itself is part of the book.”

    Action/result: Recorded this verification note before the final mdBook build.

  874. Reported: “Final verification pass is running now: mdBook after the progress-log update, plus a last whitespace/status check.”

    Action/result: mdbook build docs and git diff --check passed; status showed only the intended modified docs plus the expected untracked 002962_ms_dos_622/ and build/ directories.

  875. Reported: “The final build and whitespace pass are clean; only the intended docs are modified, with build/ and the DOS images still untracked. I’m adding that last verification note to the log and doing one no-comment rebuild after it.”

    Action/result: Recorded the final verification note before a no-comment mdBook rebuild.

  876. Reported: “I’ll build this as a living project dashboard rather than a throwaway checklist: opcode coverage plus broader engine areas, with explicit status labels and evidence pointers. I’ll read the current opcode docs/generator first so the names match what we already use.”

    Action/result: Started the PROGRESS.md pass and checked the current git status, AGENTS instructions, and opcode evidence generator.

  877. Reported: “One search command tripped over unescaped backticks in the shell, so I’m ignoring that output and using the local opcode tables directly. The evidence generator imports the canonical ACTION_NAMES and COND_NAMES, which is exactly what we want for a no-drift progress file.”

    Action/result: Used the local opcode-name and evidence tables from tools/disassemble_logic.py and tools/logic_opcode_evidence.py.

  878. Reported: “I’m going to generate the opcode checklist text from the existing clean-room tables rather than hand-copying 195 labels. That keeps PROGRESS.md aligned with tools/disassemble_logic.py and the evidence matrix.”

    Action/result: Generated the action/condition opcode checklist from the local Python tables, writing only via apply_patch.

  879. Reported: “The generated checklist shows only a handful of opcode entries are still at dispatch-smoke or source-backed level; most action and condition rows have focused QEMU evidence. I’ll fold that into a broader dashboard so it’s useful for ‘how close are we’ at a glance, not just as an opcode dump.”

    Action/result: Counted opcode evidence levels: 148 QEMU-validated action opcodes, one structural action opcode, 27 dispatch-smoke action opcodes, 18 QEMU-validated condition opcodes, and one source-backed condition.

  880. Reported: “I’m creating PROGRESS.md now with a simple ASCII status legend: [x] for sufficiently specified/evidenced, [~] for partial or smoke/source-backed, and [ ] for not yet covered. The opcode section will stay compact, while the engine-area section will carry the real remaining-work list.”

    Action/result: Added root PROGRESS.md with structural bytes, all action opcodes, all condition opcodes, subsystem coverage areas, and highest value remaining work.

  881. Reported: “PROGRESS.md is in place with all action opcodes, condition opcodes, structural bytes, and subsystem coverage areas. I’m updating AGENTS.md now so future work treats this as a maintained dashboard, not a one-time snapshot.”

    Action/result: Updated AGENTS.md to require keeping PROGRESS.md aligned with opcode/status and subsystem progress.

  882. Reported: “The agent instructions now point future work at PROGRESS.md and define when it must be updated. I’m adding the corresponding progress-log entries, then I’ll run the book and whitespace checks.”

    Action/result: Recorded this progress-log batch before validation.

  883. Reported: “Validation is running now. Since this is documentation/tracking rather than decoder code, the key checks are mdBook and whitespace; I’ll also run the existing unittest suite so we don’t unknowingly disturb the broader working set.”

    Action/result: mdbook build docs succeeded, git diff --check passed, and python3 -B -m unittest discover -s tests passed with 100 tests.

  884. Reported: “All checks passed: mdBook, whitespace, and the 100-test suite. I’m recording that validation in the progress log and then doing one last mdBook/whitespace pass because the log changed.”

    Action/result: Recorded the validation result before the final post-progress-log checks.

  885. Reported: “Final pass is just making sure the freshly updated progress log still renders cleanly and has no whitespace issues.”

    Action/result: mdbook build docs and git diff --check passed. Status showed the expected modified docs and untracked PROGRESS.md, plus a generated tools/__pycache__/ directory from the Python checks.

  886. Reported: “The final checks passed, and running the Python helpers left a generated tools/__pycache__/ behind. I’m cleaning only that generated cache directory; the requested PROGRESS.md stays untracked until you decide whether to commit.”

    Action/result: Removed generated tools/__pycache__/.

  887. Reported: “Cache cleanup is done. I’m recording the final verification/cleanup note and doing one last lightweight status/build check so the handoff is tidy.”

    Action/result: Recorded the final cleanup note before the last status and mdBook checks.

  888. Reported: “I’ll treat this as a real work batch: first I’ll read the current tracker and harnesses, then I’ll promote only the opcode coverage that the probes or disassembly actually justify, add the raw-key condition probe, and update the docs/test suite as evidence lands.”

    Action/result: Began the Highest-Value Remaining Work pass and set the active checklist for tracker, opcode, raw-key, graphics, state-machine, and verification work.

  889. Reported: “I’m reading the tracker and the probe/evidence scripts now so I can distinguish already-tested behavior from smoke coverage. That should keep the next edits from accidentally overstating what we know.”

    Action/result: Read PROGRESS.md, tools/logic_interpreter_probe.py, tests/test_logic_interpreter_probe.py, and git status.

  890. Reported: “The tracker is current and the harness already has many cases; the fastest useful path is to add a raw-key condition case and promote only opcodes with existing behavior probes behind them. I’m narrowing in on the event/input helpers and the overlay cases now.”

    Action/result: Searched the logic, picture, view, overlay, and docs harnesses for raw-key, dispatch-smoke, replay, sound, and graphics edge evidence.

  891. Reported: “I’m picking up exactly where the last pass left off: first I’m checking the existing opcode evidence mappings and the focused probe definitions so I can promote only things the disassembly/probes really support.”

    Action/result: Re-read the relevant logic probe case definitions and opcode evidence maps.

  892. Reported: “The existing suite already has one strong replay case for 0xab/0xac, and a sound completion case that observes the 0x63/0x64 flag path rather than just ‘it returned.’ I’m checking the disassembly notes around the raw-key predicate before I add a probe for it.”

    Action/result: Reviewed raw-event queue documentation, symbolic labels, and source-backed notes for condition 0x0d.

  893. Reported: “The raw-key predicate is source-mapped well enough to make a tight test: it calls the raw event helper directly, so the probe should press a plain key and draw only after 0x0d sees the nonzero low byte. Next I’m making that case and adding a couple of valid edge-render cases that are cheap to keep in the compatibility suite.”

    Action/result: Identified the raw-key probe shape and the view/object edge cases to add.

  894. Reported: “I have enough context to edit now. I’m going to keep the code changes narrow: one raw-key condition helper/case, a small --case filter for the overlay batch so focused QEMU runs are practical, two clip cases, and then the evidence/docs tracker updates that tie those to the clean-room record.”

    Action/result: Began code edits in the logic and object overlay probe harnesses.

  895. Reported: “Before editing, I also found that the overlay harness always ran its entire base set. I’m adding the same repeated --case filtering the logic harness already has, because it will make future original-engine graphics checks much faster and less noisy.”

    Action/result: Added selected-case loading and CLI filtering to tools/object_overlay_probe.py.

  896. Reported: “One subtle evidence point: I’m not going to claim the whole sound system is specified. The existing sound_stop_sets_completion_flag case does validate the opcode-level contract for load sound, start sound with completion flag, and stop sound sets that flag, so I’ll promote those opcode rows while leaving audio playback semantics as partial.”

    Action/result: Scoped the sound opcode promotion to load/start/stop completion-flag behavior only.

  897. Reported: “I’m applying the code edits now. After this I’ll run the focused raw-key probe under QEMU; if it matches, the last condition opcode can move from source-backed to QEMU-validated.”

    Action/result: Added raw_key_event_available_condition() and the raw_key_event_available_draws_after_typed_key logic probe case.

  898. Reported: “The raw-key probe is in place. I’m adding the focused overlay filtering and the missing left/top clip cases next, so graphics compatibility can grow without needing a full 20-plus-case run every time.”

    Action/result: Added focused overlay cases for left-edge and top-edge placement.

  899. Reported: “The overlay harness now has --case, matching the logic harness. I’m tightening the tests around those new IDs and the filter behavior before I do any emulator runs.”

    Action/result: Added unit assertions for the raw-key case, overlay case IDs, overlay filtering, and top-edge expected-position override.

  900. Reported: “The logic test now tracks the new raw-key case. Next I’m adding overlay test assertions for left/top clipping and proving the new case filter keeps its ordering and rejects unknown IDs.”

    Action/result: Completed overlay test updates.

  901. Reported: “The tests now cover the new fixture selectors. I’m updating the opcode evidence generator next so the docs and PROGRESS.md don’t drift from the actual harness cases.”

    Action/result: Promoted 0x62, 0x63, 0xab, 0xac, and condition 0x0d in tools/logic_opcode_evidence.py, pending dynamic validation of the new raw-key case.

  902. Reported: “The focused unit tests pass. I’m running the raw-key case in the original interpreter now; this is the piece that decides whether condition 0x0d can be promoted rather than just source-described.”

    Action/result: Ran python3 -B tools/logic_interpreter_probe.py --dos-prefix RK ... --case raw_key_event_available_draws_after_typed_key; it matched QEMU with 1 match and 0 mismatches.

  903. Reported: “The raw-key probe matched under QEMU: one typed x was enough for condition 0x0d to trigger the validation draw, without any key mapping. I’m doing the small overlay clip run now so the new graphics cases have original-engine evidence too.”

    Action/result: Started the two-case object overlay QEMU batch.

  904. Reported: “The overlay run hit the sandbox’s VNC socket restriction, the same one we’ve seen before. I’m rerunning the identical command with escalation so QEMU can bind its local VNC display.”

    Action/result: The first overlay run failed with QEMU VNC bind permission error; the escalated rerun matched QEMU with 2 matches and 0 mismatches.

  905. Reported: “The clip batch matched both cases in the original interpreter. I’m regenerating the opcode evidence chapter now, then I’ll fold the new QEMU results into the spec, tracker, and progress log.”

    Action/result: Ran python3 -B tools/logic_opcode_evidence.py.

  906. Reported: “The generated evidence matrix is current. I’m editing the human-readable chapters next: raw-key behavior, the promoted sound/replay opcodes, the graphics clip captures, and concise state machines for implementation work.”

    Action/result: Began documentation updates across bytecode, compatibility, graphics, runtime model, clean-room notes, and tracker.

  907. Reported: “One small cleanup before documenting: the new left-edge fixture is really ‘flush against the left edge,’ not a negative-X clip, because script operands are bytes. I’m correcting that wording so the case name doesn’t smuggle in a false interpretation.”

    Action/result: Corrected the left_clip_view11_priority15 description.

  908. Reported: “I’m converting the results into implementation-facing docs now. The key change is to separate ‘validated observable contract’ from ‘everything about the subsystem’: sound load/start/stop has opcode-level flag evidence, while actual playback/audio timing remains explicitly partial.”

    Action/result: Updated logic_bytecode.md with raw-key, sound, and replay rollback evidence.

  909. Reported: “The bytecode chapter now records the raw-key match and the sound/replay promotions. I’m adding the graphics edge-capture notes and the state-machine summaries next.”

    Action/result: Updated graphics_object_pipeline.md with the clip_edges_001 result.

  910. Reported: “The graphics chapter now names the new clip batch. I’m putting the subsystem state machines in runtime_model.md, where future implementers or agents will naturally look for the language-neutral model.”

    Action/result: Added resource lifecycle, object drawing lifecycle, and motion/animation state-machine tables to runtime_model.md.

  911. Reported: “The state-machine section is in place. I’m updating the compatibility chapter so the commands somebody reruns later include the new raw-key and overlay-filter cases, not just the older broad batches.”

    Action/result: Updated compatibility_testing.md with raw-key, clip-edge, sound, and replay-rollback validation notes.

  912. Reported: “The compatibility docs now point at the focused replayable commands. I’m updating PROGRESS.md to reflect what actually moved: four action opcodes promoted, the final condition opcode validated, and the high-value list revised to the next frontier.”

    Action/result: Updated opcode counts, condition status, subsystem evidence, and highest-value remaining work in PROGRESS.md.

  913. Reported: “PROGRESS.md is updated. I’m adding the clean-room evidence trail now, including the failed sandboxed QEMU attempt and the successful escalated rerun so the history stays reproducible.”

    Action/result: Added the raw-key/edge-render evidence section to docs/src/clean_room_executable_notes.md.

  914. Reported: “The clean-room notes have the new command evidence. I’m appending the user-facing progress updates now, then I’ll run the full test and book checks.”

    Action/result: Appended this progress-log batch before validation.

  915. Reported: “The documentation pass is complete. I’m running the full local suite, the generated evidence check, the mdBook build, and whitespace checks now.”

    Action/result: Ran python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check.

  916. Reported: “All four checks passed: 102 unit tests, evidence matrix --check, mdBook, and git diff --check. I’m recording that validation in the progress log and then I’ll do a final lightweight status/whitespace check because the log changed.”

    Action/result: Recorded the successful validation result before final status and whitespace checks.

  917. Reported: “Final mdBook and whitespace checks still pass after the validation log entry. I’m doing the last internal wrap-up now: marking the checklist complete and preparing the concise summary with the exact QEMU batches and checks that passed.”

    Action/result: Final mdBook and whitespace checks had passed; the plan was marked complete before this final log correction.

  918. Reported: “I just sent one more status update, so I’m adding that to the progress log too before the final checks. Tiny bookkeeping, but it keeps the historical trail honest.”

    Action/result: Added this final progress-log correction before rerunning final mdBook and whitespace checks.

  919. Reported: “I’ll commit the finished reverse-engineering batch now, staging only the tracked source/docs/test changes and leaving the generated build/ and DOS installer images alone.”

    Action/result: Began the requested commit pass and confirmed only tracked source/docs/test files would be staged.

  920. Reported: “I added the commit-prep update to the progress log, so the evidence trail still matches what you saw. I’m doing the quick doc/whitespace check for that tiny log change and then I’ll create the commit.”

    Action/result: Added the final commit-prep progress note before re-running mdBook and whitespace checks.

  921. Reported: “I’ll take the next slice from PROGRESS.md: first I’ll read the current remaining-work list and partial opcode rows, then pick a source-backed target that can be turned into concrete probes or implementation-ready docs without overclaiming.”

    Action/result: Started the next PROGRESS-driven work pass.

  922. Reported: “The tracker is pointing pretty clearly at text/input UI now: most remaining partial opcodes are in that cluster, and the current screenshot harness is weak for text-plane state. I’m checking the QEMU harness shape to see whether a small memory-observation probe would let us validate those globals directly instead of only ‘it returned.’”

    Action/result: Read PROGRESS.md, QEMU snapshot helpers, text/input docs, and partial opcode evidence.

  923. Reported: “There’s one good quick win in the remaining list: 0xaa copies the save-description buffer into a string slot, and we can validate it cleanly by patching only the fixture’s AGIDATA.OVL buffer to look and comparing it against a normal message-backed string slot. I’m going to add that probe, then use this same pass to add a text/input UI state-machine summary.”

    Action/result: Selected an initial 0xaa probe candidate and a text/input state-machine documentation target.

  924. Reported: “The 0xaa case is nice because it exercises saved-state-adjacent behavior without needing a memory probe or BIOS text rendering. I’m making it a normal draw-if fixture so it can live in the reusable compatibility suite.”

    Action/result: Added a provisional 0xaa draw-if fixture and evidence mapping.

  925. Reported: “The new 0xaa fixture and evidence mapping are in place. I’m running the focused unit test and regenerating the opcode matrix before booting QEMU for the new case.”

    Action/result: Focused logic-probe tests passed and the opcode matrix was regenerated.

  926. Reported: “The focused test passes and the evidence matrix regenerated cleanly. I’m running the original-engine QEMU case now to check that 0xaa really copies the patched save-description buffer into the script string slot.”

    Action/result: Ran the provisional 0xaa QEMU fixture; it mismatched.

  927. Reported: “The first 0xaa run did not match, so I’m treating that as useful evidence rather than forcing the promotion. I’m going back to the disassembled handler and the generated fixture bytes to find whether the source buffer address, slot operand, or string terminator assumption is off.”

    Action/result: Inspected the QEMU report, generated AGIDATA.OVL bytes around 0x0e72, and the 0xaa handler disassembly.

  928. Reported: “That mismatch is actually informative: the validation view did not draw, so the runtime 0x0e72 buffer was not equal to the patched file bytes by the time the script ran. I’m not going to promote 0xaa on that. I’m looking for another remaining opcode whose state change can be observed through existing object or logic behavior, and I’ll either pivot or keep this as an attempted-but-not-promoted note.”

    Action/result: Removed the 0xaa promotion plan and searched for a better visible-state target among remaining partial opcodes.

  929. Reported: “I’m pivoting to 0x84 instead: the handler’s source says it clears object 0’s motion mode, and the movement harness already has a clean random-motion stop oracle for the sibling 0x4e. I’ll add a parallel movement case for 0x84, and remove the unpromoted 0xaa fixture from the reusable base set.”

    Action/result: Removed the provisional 0xaa base case/evidence and added the 0x84 movement-probe plan.

  930. Reported: “The helper insert missed the exact location because the action helpers are packed a little differently than I remembered. I’m locating that block and applying the small helper patch against the actual file shape.”

    Action/result: Located the object-action helper block in tools/qemu_fixture.py.

  931. Reported: “Found it. I’m inserting the 0x84 helper alongside the nearby object-motion helpers now.”

    Action/result: Added set_global_0139_and_clear_object0_field_22_action().

  932. Reported: “The movement case is added. It starts random motion on object 0, immediately runs action 0x84, and expects the object to remain at its starting position, matching the source claim that 0x84 clears object 0’s motion byte.”

    Action/result: Added action_84_after_random_motion_stops_motion to the movement harness and tests.

  933. Reported: “Local tests pass for the movement and fixture changes, and the evidence chapter is regenerated. I’m running the focused QEMU movement case now; this should either promote 0x84 or give us another boundary note.”

    Action/result: Focused movement/logic/fixture tests passed and the 0x84 QEMU batch started.

  934. Reported: “The 0x84 QEMU case matched, so that one can move from dispatch-smoke to behavior evidence. I’m updating the docs/tracker now, and I’ll record the 0xaa attempt as deliberately not promoted because the runtime buffer did not retain the fixture-file patch.”

    Action/result: QEMU batch action_84_motion_001 matched with 1 match and 0 mismatches.

  935. Reported: “I’m updating the human docs around 0x84 in three places: the opcode chapter, the movement compatibility commands, and the runtime model. The key wording is that QEMU validates the object-0 motion-byte effect; the broader [0x0139] role remains source-backed.”

    Action/result: Updated logic_bytecode.md and graphics_object_pipeline.md for the 0x84 movement effect.

  936. Reported: “I found the compatibility movement section; I’ll add 0x84 beside the existing 0x4e single-case stop probe because they share the same observable motion-byte contract.”

    Action/result: Added the action_84_motion_001 command and result to compatibility_testing.md.

  937. Reported: “PROGRESS.md now reflects the one-opcode promotion. I’m also adding the failed 0xaa experiment to the ‘attempted but not promoted’ record, because it tells future us not to assume file-patching that buffer is enough.”

    Action/result: Updated action coverage to 154 of 176 and recorded the unpromoted 0xaa probe in compatibility docs.

  938. Reported: “The compatibility docs now keep the failed 0xaa probe out of the registry while preserving why it failed. I’m appending the clean-room command/results section next.”

    Action/result: Added the clean-room notes section for the 0xaa attempt, 0x84 movement probe, and text/input lifecycle model.

  939. Reported: “The clean-room notes have the new command trail. I’m adding this turn’s progress updates to progress_log.md, then I’ll run the full test/book checks.”

    Action/result: Appended this progress-log batch before validation.

  940. Reported: “The progress log is caught up. I’m running the full unit suite, evidence-matrix check, mdBook build, and whitespace check now.”

    Action/result: Ran python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check.

  941. Reported: “All four validations passed: 102 tests, opcode evidence --check, mdBook, and whitespace. I’m recording that result in the progress log, then I’ll do the final status/diff pass.”

    Action/result: Recorded the validation result before final status and diff checks.

  942. Reported: “One final mdBook/whitespace check is needed because I just updated the progress log with the validation result. After that I’ll give you the short handoff.”

    Action/result: Added the final validation-log note before rerunning mdBook and whitespace checks.

  943. Reported: “I added that last update to the log; now I’m doing the actual final check without more commentary unless something fails.”

    Action/result: Added the final progress-log bookkeeping note before the final check.

  944. Reported: “That was the last bookkeeping loop; running final checks now.”

    Action/result: Added the final short bookkeeping note before final mdBook, whitespace, status, and diff checks.

  945. Reported: “I’ll commit the current PROGRESS-driven batch now, staging the tracked docs/tools/tests changes and leaving generated build/ plus installer images untracked.”

    Action/result: Began the requested commit pass and confirmed the intended tracked change set.

  946. Reported: “The docs still build and whitespace is clean after the commit-log note. The tracked change set is staged; I’m creating the commit now.”

    Action/result: Recorded the final commit-status update before committing.

  947. Reported: “I’ll pick up from PROGRESS.md, find the next highest-value open items, and keep the trail in the docs as I go. First I’m going to re-read the current progress map and status so I don’t step on anything already changed.”

    Action/result: Read PROGRESS.md, current git status, recent progress log entries, and searched the remaining-work markers.

  948. Reported: “The tracker is clean after the last commit, and the open work is concentrated in the remaining partial action opcodes plus subsystem writeups. I’m going to read the rest of PROGRESS.md and then choose a small batch that can be grounded in disassembly first, with QEMU only as confirmation.”

    Action/result: Read the engine coverage and highest-value remaining work sections, then searched the partial text/input/system opcodes.

  949. Reported: “There are 22 partial actions left. The quickest useful batch looks like the text/input cluster: several opcodes already have source-backed handlers, and we can turn at least the geometry/status/prompt cases into observable QEMU probes instead of mere ‘it returned’ checks.”

    Action/result: Inspected existing text/input logic probe cases and fixture helpers.

  950. Reported: “The current harness compares rendered pixels, so behavior-level promotion is easiest when an opcode changes where or whether a known object appears. I’m checking how the comparator derives expected frames before I add cases, because a sloppy probe here would only look stronger than it is.”

    Action/result: Read the logic probe comparator and existing runtime text model.

  951. Reported: “Before editing probes, I’m re-reading the actual handler disassembly for the candidate opcodes and nearby helpers. The goal is to make each new fixture test one source-derived state transition, not just discover behavior by poking at the executable.”

    Action/result: Re-read disassembly around text/status/input handlers, including 0x34bd, 0x3726, 0x76ca, and 0x78cb.

  952. Reported: “One promising non-UI target is 0x8e: the disassembly says it changes the resource-event pair capacity, which should have visible consequences during display-mode replay. I’m reading that event buffer code now because it may give us a cleaner QEMU validation than trying to model BIOS text glyphs first.”

    Action/result: Re-read resource-event helpers around 0x705e and 0x716a, plus existing replay docs and tests.

  953. Reported: “0x8e does reset capacity, but overflowing the pair buffer calls the interpreter’s error path, so I’m treating that as a risky validation target for now. I’m looking for cases where changing the capacity resets or narrows a replay log without causing the original engine to walk off into error handling.”

    Action/result: Reviewed existing display-mode replay fixtures and kept 0x8e as a later target.

  954. Reported: “A better first promotion is the rectangle-clear pair 0x69/0x9a: the source says they clear text regions through BIOS scroll/clear helpers, and existing message probes already show formatted text can remain on the captured surface unless we refresh the picture. I’m going to add cases that display text, clear only via those actions, and then compare the clean graphics result.”

    Action/result: Chose the text rectangle clear pair as the focused behavior-promotion target.

  955. Reported: “I’m making the probe change now: two cases, one for the row-clear wrapper and one for explicit rectangle bounds. Both start from source-derived operands and deliberately avoid the 0x1a picture refresh that would mask leftover text.”

    Action/result: Added initial text_rect_clear_rows_removes_formatted_text and text_rect_clear_bounds_removes_formatted_text probe cases.

  956. Reported: “The fixture cases are in place. I’m adding small unit coverage so the case catalog keeps these behavior probes from silently disappearing during later refactors.”

    Action/result: Inspected and prepared updates to tests/test_logic_interpreter_probe.py.

  957. Reported: “The catalog test already has a good ‘known case ids’ check, so I’m just extending that list for the two new behavior probes.”

    Action/result: Added the two text-rectangle behavior case ids to the probe catalog test.

  958. Reported: “Next I’m running the local unit check for the probe catalog, then I’ll run just the two new cases under QEMU. If either case mismatches, I’ll treat that as feedback on my operand assumptions rather than forcing the docs to fit.”

    Action/result: Ran python3 -B -m unittest tests.test_logic_interpreter_probe successfully and reviewed the focused diff.

  959. Reported: “The local probe tests pass. I’m starting a two-case QEMU batch now; this should tell us whether the row and bounded clears really remove the formatted-message residue in the original engine.”

    Action/result: Started text_rect_clear_behaviour_001; the first command used an invalid bare --snapshot option.

  960. Reported: “Small harness wrinkle: this probe runner does not have a bare --snapshot flag, only explicit snapshot image paths. I’m rerunning the same two cases through the normal batch path.”

    Action/result: Reran the two-case text rectangle batch without the invalid option.

  961. Reported: “QEMU is running both cases from a generated snapshot disk now. I’ll wait for the captures and then inspect any mismatch instead of assuming the rectangle operands are right.”

    Action/result: Waited for the QEMU snapshot batch.

  962. Reported: “The row-clear case mismatched, so I’m treating that as a useful correction. I’m inspecting the report and capture now to see whether the clear bounds were wrong, the formatted helper draws somewhere else, or the action is clearing in text coordinates that don’t line up with the assumed row.”

    Action/result: Inspected text_rect_clear_behaviour_001; the mismatch showed a black band at logical Y 40..55.

  963. Reported: “The mismatch is actually informative: 0x69 did clear exactly rows 5-6, but to black text-background pixels, while my expected image assumed the white picture remained. I’m updating the comparator to let a probe declare expected screen rectangles, which is the right model for text clears that alter the display surface rather than the picture resource.”

    Action/result: Decided to model expected display-surface rectangles in the logic probe comparator.

  964. Reported: “I’ll keep the rectangle expectation local to the fixture comparator: it changes only the low visual nibble over an expected text-cell rectangle, preserving the picture/control half of the cell. That matches the source-level split between text display effects and the picture/control buffers.”

    Action/result: Inspected agi_graphics.py structures to patch the expected visual nibbles without changing picture decoding.

  965. Reported: “The case schema now carries optional expected rectangles. I’m wiring those rectangles into comparison and setting the two text-clear cases to the source-derived text-cell coordinates.”

    Action/result: Added expected_visual_rects to the logic probe case schema and initial expected rectangles.

  966. Reported: “I’m patching the probe model now so the expected image can include ‘display-only’ visual rectangles. This keeps the picture renderer itself clean while letting text/UI probes assert the observable screen state.”

    Action/result: Began adding the display-rectangle application helper.

  967. Reported: “The patch anchor was off by a function name, so I’m locating the fixture builder and applying the comparison helper at the actual spot.”

    Action/result: Located build_logic_fixture and compare_capture.

  968. Reported: “Found it. I’m inserting the rectangle helper just before compare_capture and then rerunning the local tests.”

    Action/result: Added apply_expected_visual_rects() and called it from compare_capture.

  969. Reported: “The expected-rectangle support is wired in. I’m running the unit tests again, then the QEMU batch with the corrected expectations.”

    Action/result: Ran the focused unit tests and py_compile successfully.

  970. Reported: “The probe code still compiles and the catalog tests pass. I’m rerunning the two original-engine captures now; this time the expected image includes the black text-clear rectangles.”

    Action/result: Started text_rect_clear_behaviour_002.

  971. Reported: “QEMU is through fixture build and running the snapshot batch. I’m waiting on the two captures now.”

    Action/result: Waited for the second QEMU text rectangle batch.

  972. Reported: “The row-clear case now matches; the bounded-rectangle case still mismatches, so I’m narrowing that one. The first result already gives us a solid 0x69 behavior probe.”

    Action/result: Inspected text_rect_clear_behaviour_002 and saw the bounded case expected X range was wrong.

  973. Reported: “The bounded clear did run, but the X mapping corrected my assumption: column 5 maps to logical X 20, not 40. I’m checking the BIOS wrapper helper before updating the expected rectangle so the docs describe the coordinate scale correctly.”

    Action/result: Re-read code.text.clear_bounds and code.text.clear_rows disassembly.

  974. Reported: “The helper confirms the documented argument order: top, left, bottom, right, attribute. The remaining surprise is how QEMU’s graphics text grid maps columns onto logical pixels, so I’m measuring the capture row directly rather than guessing from the mismatch summary.”

    Action/result: Measured the bounded-clear capture directly and found black ranges X 20..83 for rows 64..71.

  975. Reported: “Measured directly: 0x9a(8,5,8,20,0) clears logical X 20-83 over Y 64-71. That matches a 40-column text grid: column units are four logical pixels wide and row units are eight logical pixels tall. I’m correcting the expected rectangle.”

    Action/result: Corrected the bounded expected rectangle to X 20..83/Y 64..71.

  976. Reported: “The bounded case expectation is fixed. I’m adding a test assertion for those exact expected rectangles so this coordinate mapping stays visible in the test suite.”

    Action/result: Added test assertions for the expected display-surface rectangles.

  977. Reported: “The unit test now pins the text-clear rectangle mapping. I’m running the focused tests and then the two-case QEMU batch one more time.”

    Action/result: Ran the focused unit tests and py_compile successfully.

  978. Reported: “The focused local tests pass with the new rectangle expectations. I’m rerunning the original-engine comparison now.”

    Action/result: Started text_rect_clear_behaviour_003.

  979. Reported: “The QEMU batch is running from its snapshot disk again; waiting for both comparisons to finish.”

    Action/result: Waited for the final two-case QEMU text rectangle batch.

  980. Reported: “Both text-clear probes now match the original engine. I’m promoting 0x69 and 0x9a in the evidence matrix, then I’ll regenerate the generated opcode evidence chapter and update the human docs/progress trail.”

    Action/result: Confirmed text_rect_clear_behaviour_003 matched 2/2 and prepared evidence promotion.

  981. Reported: “With the generated evidence refreshed, I’m updating the human-facing docs: progress counts, opcode descriptions, compatibility command/results, and the clean-room notes about the source helper plus the QEMU correction from the first mismatch.”

    Action/result: Inspected the relevant docs and symbolic labels before documentation updates.

  982. Reported: “I’m updating tools/logic_opcode_evidence.py so the generated matrix treats 0x69 and 0x9a as QEMU-validated by the new behavior probes instead of smoke-only.”

    Action/result: Promoted 0x69 and 0x9a in the evidence registry and removed them from smoke-only actions.

  983. Reported: “The evidence registry is updated. I’m checking the generator options and then regenerating the opcode evidence chapter from the registry.”

    Action/result: Checked tools/logic_opcode_evidence.py --help and script behavior.

  984. Reported: “The generator writes the mdBook chapter by default. I’m regenerating that file now.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  985. Reported: “I don’t see stable labels for the clear helpers yet, so I’m adding them to symbolic_labels.md while I’m touching this subsystem. That will make later cross-version comparisons easier than referring to raw 0x2b78/0x2bc4 everywhere.”

    Action/result: Checked existing text labels in symbolic_labels.md.

  986. Reported: “I’m adding the helper labels right beside the other text labels, with the observed QEMU coordinate mapping in the notes.”

    Action/result: Added code.text.clear_rows and code.text.clear_bounds symbolic labels.

  987. Reported: “Now I’m updating PROGRESS.md: two more action opcodes are covered at behavior level, so the covered count moves from 154 to 156 and partial actions drop from 22 to 20.”

    Action/result: Updated PROGRESS.md counts and marked 0x69/0x9a as QEMU-validated.

  988. Reported: “I’m tightening the opcode chapter next: the handler rows now mention the display-surface coordinate mapping, and the QEMU paragraph changes from smoke-only to behavior-level evidence for the two clear operations.”

    Action/result: Updated docs/src/logic_bytecode.md for 0x69, 0x9a, and the new behavior fixture.

  989. Reported: “I’m also adding a short implementation-facing note in the runtime model: these clears are display-surface operations, not picture-resource edits. That distinction matters for a clean-room engine.”

    Action/result: Added text-rectangle clear semantics to docs/src/runtime_model.md.

  990. Reported: “Next I’m adding the exact QEMU command/result to compatibility_testing.md, including the corrected first failed run so the evidence trail shows why the expected rectangles were added.”

    Action/result: Added text_rect_clear_behaviour_003 command/results and the earlier mismatch correction to compatibility docs.

  991. Reported: “I’m appending the clean-room notes section now with the disassembly commands, QEMU commands, the mismatch correction, and the exact behavior promoted.”

    Action/result: Prepared the clean-room executable notes update.

  992. Reported: “I’m adding the new section after the 0x84 section, so the chronology stays intact.”

    Action/result: Appended the 2026-07-04 text rectangle clear behavior section to clean_room_executable_notes.md.

  993. Reported: “I’ve got the substantive docs updated. I’m doing the progress-log bookkeeping now, then I’ll run the full validation suite for the changed probe/tool/docs set.”

    Action/result: Began appending this progress-log batch before final validation.

  994. Reported: “The progress log is caught up through the documentation pass. I’m running the full validation set now: unit tests, generated evidence check, mdBook build, whitespace, and status.”

    Action/result: Ran python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check.

  995. Reported: “All four validations passed: 103 unit tests, evidence --check, mdBook, and whitespace. I’m adding that validation result to the progress log and then doing a final status/diff pass.”

    Action/result: Recorded the successful validation result before final status and diff checks.

  996. Reported: “Because the progress log changed after validation, I’m rerunning the lightweight doc/whitespace checks and then I’ll give you the concise handoff.”

    Action/result: Reran mdbook build docs, git diff --check, status, and diff-stat checks.

  997. Reported: “The final checks are clean, and I spotted a generated tools/__pycache__/ from the Python runs. I’m removing that disposable cache and recording this last validation/status update so the handoff is tidy.”

    Action/result: Removed generated tools/__pycache__/ and confirmed the tracked change set plus existing untracked generated/input directories.

  998. Reported: “Last log entry is in. I’ll do one quiet final check now.”

    Action/result: Reran mdBook, whitespace, and status checks; this note was added because that status update itself is part of the historical log.

  999. Reported: “One final bookkeeping note got added by my own update, so I’m recording that too and then I’ll stop the commentary loop.”

    Action/result: Recorded the final bookkeeping note before the truly quiet final mdBook, whitespace, and status checks.

  1000. Reported: “I’ll continue from the current PROGRESS.md map and keep the work focused on the remaining partial opcodes. I’m going to re-check the tracker, evidence matrix, and working tree first so the next probe batch lands cleanly.”

    Action/result: Re-read PROGRESS.md, git status --short, the end of this progress log, and the opcode evidence/current tracker references.

  1001. Reported: “The remaining partial list is still mostly text/input and a few VM-global toggles. I’m going to look for the next source-backed state change that can be made observable without inventing a glyph renderer; status/input clears and global motion/input gates look like the best candidates.”

    Action/result: Re-read the text/input opcode notes, disassembled around action 0x83, and inspected existing logic-interpreter probe cases.

  1002. Reported: “I’m resuming at the partial text/status/input opcodes and will start with the ones whose source clearly funnels into row-clearing code. That should let us promote a couple of opcodes with source-backed, QEMU-validated behavior instead of just dispatch smoke.”

    Action/result: Re-read the probe catalog, unit tests, evidence mapping, and text opcode chapter around the input/status handlers.

  1003. Reported: “The source path is lining up: both status-hide and input-disable appear to use the same cleared text-row primitive that we already validated for rectangle clears. I’m checking the exact dispatch targets now so the new probes can cite the code path rather than just the visual outcome.”

    Action/result: Searched docs for 0x70, 0x71, 0x77, and input/status labels; inspected the relevant probe cases and disassembly ranges.

  1004. Reported: “One minor wrinkle: direct ndisasm with manual skips landed midstream in one range, so I’m switching to the existing full disassembly artifact for address-aligned evidence. That keeps the notes tied to the same address map the rest of the project uses.”

    Action/result: Checked the full disassembly artifact and existing notes for the status/input helper address map.

  1005. Reported: “The previous notes already captured the core handlers; I’m adding narrowly-scoped probes now. The fixture will configure the target row, run the hide/disable opcode, then draw the usual validation object, so any leftover text-row state shows up as a capture mismatch.”

    Action/result: Inspected the aligned source snippets and test helper assertions before editing.

  1006. Reported: “I’m going to make the row clears visible by first drawing a short formatted message on the same text row, then running the opcode under test. That turns a global/UI side effect into an ordinary screen comparison: if the source interpretation is wrong, the text remnants should remain.”

    Action/result: Confirmed existing text clear evidence and planned the two row-clear fixtures.

  1007. Reported: “I’m about to edit the probe catalog and its unit assertions. I’ll keep the new cases small: one for 0x71 clearing the configured status row, one for 0x77 clearing the configured input row.”

    Action/result: Added input_line_disable_clears_configured_row and status_line_hide_clears_configured_row to tools/logic_interpreter_probe.py.

  1008. Reported: “The two probe cases are in place. I’m wiring the catalog tests next so future edits don’t silently drop the behavior coverage.”

    Action/result: Added unit assertions for the two new case ids and their expected visual clear rectangles.

  1009. Reported: “The static catalog checks are updated. I’m running the focused unit test before invoking QEMU, since that catches bad fixture metadata cheaply.”

    Action/result: Ran the two focused tests.test_logic_interpreter_probe.LogicInterpreterProbeTests methods; both passed.

  1010. Reported: “The unit checks passed. I’m running the two new cases through the original interpreter now; this is the point where the clean-room hypothesis either earns behavior-level evidence or gets revised.”

    Action/result: Started QEMU snapshot batch text_hide_clear_behaviour_001 with the two new cases.

  1011. Reported: “QEMU is running both fixtures from a snapshot batch now. The build phase completed and the interpreter is executing the cases, so I’m waiting on the captures/comparisons.”

    Action/result: Waited for the running QEMU batch to finish.

  1012. Reported: “Both QEMU cases matched: 0x77 and 0x71 now have visible row-clear behavior, not just ‘returns safely.’ I’m updating the evidence matrix and docs so the tracker reflects that promotion.”

    Action/result: Promoted action opcodes 0x71 and 0x77 from smoke to QEMU-validated in tools/logic_opcode_evidence.py.

  1013. Reported: “The evidence script supports direct regeneration, so I’m letting it rewrite the opcode evidence chapter from the source-of-truth mapping rather than hand-editing the table.”

    Action/result: Ran python3 -B tools/logic_opcode_evidence.py to regenerate docs/src/logic_opcode_evidence.md.

  1014. Reported: “The generated evidence table is refreshed. I’m updating PROGRESS.md next: the action coverage count moves up by two, and the remaining partial list loses 0x71 and 0x77.”

    Action/result: Updated PROGRESS.md to 158/176 action opcodes at [x] level, with 18 partial action opcodes remaining.

  1015. Reported: “PROGRESS.md is updated. I’m scanning for stale counts or old ‘dispatch-smoke’ references before touching the narrative docs.”

    Action/result: Started a stale-reference scan; the first command failed because a literal backtick confused the shell quoting.

  1016. Reported: “That scan tripped on a shell quote in the literal backtick pattern, so I’m rerunning it with simpler text patterns. No state changed.”

    Action/result: Reran the stale-reference scan and found only historical notes from earlier work.

  1017. Reported: “The stale hits are historical notes from earlier runs, not current trackers. I’m adding a new dated notes section rather than rewriting that history.”

    Action/result: Re-read symbolic labels, opcode docs, runtime model, and compatibility text to prepare focused updates.

  1018. Reported: “I’m also adding a symbolic label for the single-row clear helper used by both handlers. That gives later cross-version comparisons a stable name instead of just another absolute address.”

    Action/result: Disassembled code.text.clear_row at image 0x2ba6 and added it to docs/src/symbolic_labels.md.

  1019. Reported: “The symbolic label is added. I’m tightening the opcode chapter now, with the implementation detail that 0x2ba6 is the single-row wrapper around the broader text-row clear helper.”

    Action/result: Updated opcode rows for 0x71 and 0x77 in docs/src/logic_bytecode.md.

  1020. Reported: “The opcode rows now carry the behavioral evidence. I’m adding the batch summary near the other text/UI QEMU notes so readers see exactly how those two opcodes were promoted.”

    Action/result: Added the text_hide_clear_behaviour_001 narrative to the logic bytecode chapter.

  1021. Reported: “I’m adding the higher-level runtime contract too: these handlers are ‘visible text surface’ operations, not changes to the picture resource. That distinction is useful for a future implementation.”

    Action/result: Added status/input single-row clear semantics to docs/src/runtime_model.md.

  1022. Reported: “The runtime model has the visible-surface contract now. I’m adding the exact QEMU command and result to the compatibility chapter.”

    Action/result: Added the text_hide_clear_behaviour_001 command and result to docs/src/compatibility_testing.md.

  1023. Reported: “The compatibility chapter has the new batch. I’m appending the clean-room notes section with the disassembly command, fixture changes, QEMU result, and tracker updates.”

    Action/result: Added the dated clean-room notes section for the status/input single-row clear behavior probes.

  1024. Reported: “The main docs are updated. I’m doing the progress-log bookkeeping now, including the short status messages from before and after the context handoff so the historical trail stays continuous.”

    Action/result: Appended this progress-log batch before final validation.

  1025. Reported: “The progress log is caught up through the doc edits. I’m running the full validation pass now: unit suite, generated evidence check, mdBook build, and whitespace check.”

    Action/result: Ran python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check.

  1026. Reported: “All four validations passed: 103 unit tests, evidence --check, mdBook, and whitespace. I’m recording that validation result and then I’ll do a final diff/status pass.”

    Action/result: Recorded the successful validation result before final diff and status inspection.

  1027. Reported: “Because the progress log changed after validation, I’m rerunning the lightweight doc/whitespace checks and grabbing the final status/diff summary.”

    Action/result: Reran mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, git status --short, and git diff --stat.

  1028. Reported: “The second mdBook/evidence/whitespace pass is clean. Final status shows the expected tracked edits plus the existing untracked DOS installer/images under 002962_ms_dos_622/ and generated build/.”

    Action/result: Recorded the final validation/status summary after the second lightweight check pass.

  1029. Reported: “I’ll commit the current row-clear/evidence work first, leaving the existing generated build/ and DOS installer directory untracked. After that I’ll pick up the next highest-value partial opcode from PROGRESS.md and keep the new work uncommitted until you ask again.”

    Action/result: Re-read status, diff stat, and PROGRESS.md before staging the completed row-clear work.

  1030. Reported: “The commit candidate is exactly the 11 modified files from the row-clear work. I’m staging only those tracked files now; build/ and 002962_ms_dos_622/ stay out of the commit.”

    Action/result: Staged the 11 tracked files from the status/input row-clear validation pass.

  1031. Reported: “Staging succeeded. I’m committing this as the status/input row-clear behavior validation, then I’ll continue from the next partial opcode cluster.”

    Action/result: Attempted git commit -m "Validate status and input row clears".

  1032. Reported: “The sandbox blocked the commit while trying to create .git/index.lock, so I’m rerunning the same commit with escalation.”

    Action/result: Reran the commit with escalation. Commit aa55bda (Validate status and input row clears) succeeded with 11 files changed.

  1033. Reported: “Commit aa55bda is in. I’m back on the tracker now, and the next likely wins are still in the text/input group: 0x70 status redraw, 0x78 input redraw, and the refresh/erase helpers. I’ll read the source path first and only use QEMU once there’s a precise behavior hypothesis to validate.”

    Action/result: Confirmed the post-commit status and re-read the relevant source-backed text/input opcode notes and disassembly.

  1034. Reported: “The committed tree is clean aside from the long-standing untracked input/generated directories. I’m noticing the same address-map trap as before: one disassembly artifact is file-offset oriented, while the docs use image-oriented labels, so I’m cross-checking against byte patterns before naming any new behavior.”

    Action/result: Searched for byte patterns and reran image-oriented ndisasm commands around the status, input, and text-attribute handlers.

  1035. Reported: “Two behavior probes look worth trying from the disassembly: 0x78 should redraw/clear the configured input row, and 0x6a should enter alternate text mode and clear the visible text surface through code.text.clear_rows. I’m adding both as screen comparisons, with QEMU as confirmation rather than discovery.”

    Action/result: Began adding input_line_enable_clears_configured_row and text_attribute_enable_clears_visible_surface to the probe catalog.

  1036. Reported: “That patch missed one nearby context block, so I’m reopening the exact probe catalog slice and applying the additions in smaller pieces.”

    Action/result: Re-read the probe catalog slice around the existing text/input cases.

  1037. Reported: “I’ve got the exact insertion points now. I’m adding the 0x78 row-redraw case and the 0x6a visible-surface clear case separately.”

    Action/result: Added the 0x78 input-line enable row-clear probe.

  1038. Reported: “The 0x78 case is in. I’m adding the 0x6a full visible-surface clear check right after the existing text-attribute smoke case.”

    Action/result: Added the initial 0x6a visible-surface clear probe.

  1039. Reported: “Both new probe definitions are in place. I’m updating the unit assertions now so the catalog and expected rectangles stay pinned down.”

    Action/result: Re-read the unit-test catalog and expected-rectangle assertions.

  1040. Reported: “The test file already has the recent row-clear assertions; I’m extending that same check for 0x78 and the full-surface 0x6a case.”

    Action/result: Added unit assertions for the two new case ids and expected visual rectangles.

  1041. Reported: “The catalog tests are updated. I’m running the focused unit checks before taking the two hypotheses to QEMU.”

    Action/result: Ran the two focused logic-interpreter probe unit tests; both passed.

  1042. Reported: “The focused checks passed. I’m running the new 0x78 and 0x6a cases through the original engine as a two-case QEMU snapshot batch now.”

    Action/result: Started QEMU batch text_enable_attr_behaviour_001.

  1043. Reported: “The 0x78 input-line enable case matched. The 0x6a full-surface case is still running, so I’m waiting for that capture before updating evidence.”

    Action/result: Waited for the second case in text_enable_attr_behaviour_001.

  1044. Reported: “The batch split neatly: 0x78 matched, while my 0x6a full-black expectation was too blunt. I’m inspecting the mismatch before deciding whether to revise that case or leave 0x6a as source-backed smoke for now.”

    Action/result: Inspected the QEMU report, the 0x6a PPM capture, and text-attribute helper disassembly.

  1045. Reported: “The 0x6a mismatch is interesting rather than bad: the capture is entirely black, so the surface clear happened, but the later transient-object validation draw did not appear while alternate text mode was active. I’m adjusting the harness carefully so this case can compare the visible surface without requiring the usual object draw.”

    Action/result: Re-read the probe helper and comparator paths for compare_view handling.

  1046. Reported: “I’m going to keep the 0x6a probe, but make the expected result match what the original actually does in this mode: black visible surface, no composed validation object. That means adding a small compare_view switch to the shared case helper instead of special-casing the comparator.”

    Action/result: Added a compare_view parameter to _custom_case and set the 0x6a case to compare_view=False.

  1047. Reported: “The harness now supports a no-composed-view expectation for special display states, and the 0x6a case uses it. I’m pinning that in the unit test before rerunning QEMU.”

    Action/result: Added a unit assertion that the 0x6a case does not compare the usual composed view.

  1048. Reported: “The unit expectation now records that 0x6a is a surface-only comparison. I’m rerunning the focused tests and then the two QEMU cases.”

    Action/result: Reran the focused unit tests and started QEMU batch text_enable_attr_behaviour_002.

  1049. Reported: “The focused tests passed again and QEMU has re-confirmed the 0x78 case. I’m waiting on the revised 0x6a comparison.”

    Action/result: Waited for the second case in text_enable_attr_behaviour_002.

  1050. Reported: “The revised QEMU batch matched both cases. I’m promoting 0x78 and 0x6a in the generated evidence map now, while leaving 0x6c, 0x6b, and 0x6d at their current partial levels.”

    Action/result: Re-read evidence, symbolic-label, opcode, compatibility, and clean-room note context before promotion.

  1051. Reported: “The evidence source now promotes 0x6a and 0x78. I’m regenerating the evidence chapter and updating the progress counts from 158 to 160 covered action opcodes.”

    Action/result: Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  1052. Reported: “The generated evidence chapter is refreshed. I’m aligning PROGRESS.md with two more [x] promotions: 0x6a and 0x78.”

    Action/result: Updated PROGRESS.md to 160/176 action opcodes at [x] level, with 16 partial action opcodes remaining.

  1053. Reported: “I’m updating the narrative docs next. The key wording is: 0x78 is a normal input-row redraw/clear, while 0x6a is alternate text-attribute mode, where the visible surface clears and the usual composed object check is intentionally not expected.”

    Action/result: Updated docs/src/logic_bytecode.md rows and behavior fixture narrative for 0x6a and 0x78.

  1054. Reported: “The opcode chapter is updated with the new behavior fixture. I’m adding the same command/result to the compatibility chapter and adjusting the runtime model’s state-machine notes.”

    Action/result: Updated docs/src/compatibility_testing.md and docs/src/runtime_model.md.

  1055. Reported: “I’m also tightening the symbolic-label notes for the two helpers implicated here, so later cross-version work has the behavioral evidence attached to the label map.”

    Action/result: Updated docs/src/symbolic_labels.md entries for code.text.enter_attr_mode and code.input.redraw_input_line.

  1056. Reported: “I’m correcting one older compatibility paragraph too, so it remains historically true without implying 0x6a and 0x78 are still only smoke-covered.”

    Action/result: Revised the text/status smoke-batch wording in docs/src/compatibility_testing.md.

  1057. Reported: “The compatibility history is now clearer. I’m appending the clean-room notes for the 0x78/0x6a disassembly-backed QEMU pass, including the first failed expectation for 0x6a.”

    Action/result: Added the clean-room notes section for text_enable_attr_behaviour_001 and text_enable_attr_behaviour_002.

  1058. Reported: “The clean-room notes now have the command/result trail. I’m doing a consistency scan for stale counts and smoke labels before the final validation pass.”

    Action/result: Ran stale-reference scans, checked PROGRESS.md, docs/src/logic_opcode_evidence.md, status, and diff stat.

  1059. Reported: “The current stale-count hits are only historical notes from the just-committed previous section, so I’m leaving them intact. I’m appending the progress-log entries for this commit-and-continue pass now, then I’ll run validation.”

    Action/result: Began appending this progress-log batch.

  1060. Reported: “I’m adding a fairly long progress-log batch because this turn included the requested commit, a sandbox retry, a failed hypothesis for 0x6a, and the corrected validation. This is exactly the sort of trail future-us will be grateful not to reconstruct.”

    Action/result: Appended this progress-log batch before final validation.

  1061. Reported: “The progress log is caught up through the corrected 0x6a/0x78 work. I’m running the full validation set now: unit tests, evidence check, mdBook, and whitespace.”

    Action/result: Ran python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check.

  1062. Reported: “All four validations passed: 103 unit tests, evidence --check, mdBook, and whitespace. I’m recording that and then doing a final status/diff pass.”

    Action/result: Recorded the successful validation result before final status and diff inspection.

  1063. Reported: “Because the progress log changed after validation, I’m rerunning the lightweight checks and grabbing the final status summary.”

    Action/result: Reran mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, git status --short, and git diff --stat.

  1064. Reported: “One last log entry was added for the lightweight rerun, so I’m doing a quiet final check now and then I’ll stop the commentary loop.”

    Action/result: Recorded the final bookkeeping note before quiet final validation and status checks.

  1065. Reported: “I’ll commit the current 0x6a/0x78 behavior work first, keeping build/ and the DOS installer images untracked. Then I’ll continue with the next partial opcodes from PROGRESS.md, again starting from the disassembled source and using QEMU only to validate a concrete hypothesis.”

    Action/result: Re-read status, diff stat, and the remaining partial opcode list in PROGRESS.md.

  1066. Reported: “The uncommitted set is the expected 11 tracked files from the 0x6a/0x78 pass. I’m staging just those tracked files now and leaving generated inputs alone.”

    Action/result: Staged the tracked 0x6a/0x78 behavior-validation files.

  1067. Reported: “Staging succeeded. I’m committing the input-enable/text-attribute behavior validation now; commit writes to .git, so I’m using escalation for that part.”

    Action/result: Created commit 1229d68 (Validate input enable and text attribute mode).

  1068. Reported: “Commit 1229d68 is in. I’m continuing from the remaining 16 partial action opcodes; next I’m looking for another source-backed visible text/input behavior, especially status redraw 0x70, prompt marker 0x6c, and the 0x89/0x8a refresh/erase pair.”

    Action/result: Confirmed the post-commit status and re-read the remaining text/input partial opcode notes and disassembly.

  1069. Reported: “The disassembly suggests a clean way to validate 0x6c without implementing font glyphs: set a non-empty prompt marker, then set it to an empty message and redraw the input row. If 0x6c really stores the first message byte, the redraw stays all black; otherwise a marker glyph appears and the capture mismatches.”

    Action/result: Re-read prompt-marker and input-line helper notes, existing cases, and focused disassembly around 0x38b4 and 0x37f7.

  1070. Reported: “I’m adding two targeted cases now: one for 0x6c clearing a previously non-empty prompt marker by loading an empty message, and one for 0x6b leaving alternate text mode so the normal picture/object draw becomes visible again. Both are small enough to be decisive.”

    Action/result: Added text_attribute_disable_restores_picture_draw and input_prompt_empty_message_suppresses_marker to the probe catalog.

  1071. Reported: “The two new probe cases are in the catalog. I’m adding unit assertions for their presence and the 0x6c expected row-clear rectangle.”

    Action/result: Updated tests/test_logic_interpreter_probe.py for the two new case ids and expected rectangle.

  1072. Reported: “The unit assertions are updated. I’m running the focused catalog tests, then I’ll run the two new cases through QEMU if those pass.”

    Action/result: Ran the two focused logic-interpreter probe unit tests; both passed.

  1073. Reported: “The focused tests passed. I’m running these two cases against the original interpreter now; if they match, they should promote 0x6b and 0x6c from smoke to behavior-level coverage.”

    Action/result: Started QEMU batch text_prompt_attr_behaviour_001.

  1074. Reported: “The 0x6b case matched: leaving alternate text mode restores the normal picture/object draw path. I’m waiting on the 0x6c prompt-marker suppression case now.”

    Action/result: Waited for the second case in text_prompt_attr_behaviour_001.

  1075. Reported: “Both cases matched in QEMU, so 0x6b and 0x6c can move up to behavior-level evidence. I’m updating the generated evidence map and the tracker, then I’ll add the source/QEMU notes.”

    Action/result: Re-read evidence, opcode, compatibility, runtime, and symbolic-label context before promotion.

  1076. Reported: “I’m promoting 0x6b and 0x6c in the evidence source now. The docs will distinguish the 0x6c evidence as an empty-message prompt-marker behavior, not a full glyph-rendering spec.”

    Action/result: Updated tools/logic_opcode_evidence.py.

  1077. Reported: “The evidence source is updated. I’m regenerating the evidence chapter and moving the tracker to 162 covered action opcodes.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md and updated PROGRESS.md to 162/176 action opcodes at [x] level.

  1078. Reported: “PROGRESS.md now reflects 162 covered actions and 14 partials. I’m updating the opcode chapter and runtime docs with the specific contracts we just validated.”

    Action/result: Updated docs/src/logic_bytecode.md.

  1079. Reported: “The opcode chapter now names the new QEMU fixture and narrows the remaining smoke list. I’m adding the compatibility command/result and then updating the runtime/symbolic summaries.”

    Action/result: Updated docs/src/compatibility_testing.md.

  1080. Reported: “The compatibility chapter has the new text_prompt_attr_behaviour_001 batch. I’m adding the implementation-facing notes now.”

    Action/result: Updated docs/src/runtime_model.md.

  1081. Reported: “The runtime model now captures prompt-marker suppression and text-mode exit. I’m attaching the same evidence to the stable symbolic-label map.”

    Action/result: Updated docs/src/symbolic_labels.md.

  1082. Reported: “I’m appending the clean-room notes for the prompt-marker and text-mode-exit probes, including the source commands and the exact QEMU batch.”

    Action/result: Added the dated clean-room notes section for text_prompt_attr_behaviour_001.

  1083. Reported: “The clean-room notes are updated. I’m adding the progress-log batch for this turn now, covering the commit, the two new probes, the QEMU results, and the documentation updates.”

    Action/result: Began appending this progress-log batch.

  1084. Reported: “I’m keeping the log concise but complete: every progress update gets an entry, while command details live in the clean-room notes. After this I’ll run the full validation suite.”

    Action/result: Appended this progress-log batch before final validation.

  1085. Reported: “The progress log is caught up through the 0x6b/0x6c work. I’m running the full validation pass now: unit suite, generated evidence check, mdBook, and whitespace.”

    Action/result: Ran python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check.

  1086. Reported: “All four validations passed: 103 unit tests, evidence --check, mdBook, and whitespace. I’m recording that result and then doing the final status/diff pass.”

    Action/result: Recorded the successful validation result before final status and diff inspection.

  1087. Reported: “The progress log changed after validation, so I’m rerunning the lightweight checks and collecting the final status summary.”

    Action/result: Reran mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, git status --short, git diff --stat, and git log -2 --oneline.

  1088. Reported: “The lightweight checks are clean and the final status shows the expected uncommitted 0x6b/0x6c work plus the existing untracked generated/input directories. I’m recording that last status note and doing one quiet final check.”

    Action/result: Recorded the final status note before quiet final validation and status checks.

  1089. Reported: “One last note was appended, so I’ll run the final quiet check now and then hand this back without another progress-log loop.”

    Action/result: Recorded the final bookkeeping note before quiet final validation and status checks.

  1090. Reported: “I’ll commit the current 0x6b/0x6c validation work, keeping the generated build/ artifacts and DOS installer images untracked. I’ll add a tiny progress-log note for this commit turn, then stage only the tracked project files.”

    Action/result: Re-read status, diff stat, and the tail of this progress log before staging the commit.

  1091. Reported: “The commit candidate is the expected 11 tracked files, and the previous validation is already recorded. I’m appending the commit-turn log entry, then I’ll do a lightweight check and commit.”

    Action/result: Appended the commit-turn progress-log entries before final lightweight checks and staging.

  1092. Reported: “I’ll continue from the freshly committed state and target the remaining partial opcodes in PROGRESS.md. I’ll start by rereading the tracker and source notes, then look for the next behavior that can be proven from disassembly and validated with a narrow QEMU fixture.”

    Action/result: Re-read repository status, PROGRESS.md, and current opcode/probe references.

  1093. Reported: “I’m going to take the next partial opcode that looks most tractable from the disassembly: 0x6d, the text attribute pair setter. The goal is to turn it from “dispatch-smoked” into a small behavior spec with an original-engine confirmation, then update the tracker and docs.”

    Action/result: Selected 0x6d as the next promotion target.

  1094. Reported: “The first pass confirms the remaining tracker entries are mostly “partial because we only proved dispatch.” I’m drilling into the existing probe harness now so the next test is shaped like the engine code, not just a random observation.”

    Action/result: Re-read the logic interpreter probe harness and existing text-attribute cases.

  1095. Reported: “The existing notes already identify the helper chain (0x77d5 -> 0x7803/0x78a1/0x78ad). I’m going back to that disassembly now to pin down which operand combinations should produce a visible, stable EGA result.”

    Action/result: Re-read disassembly around 0x76ca, 0x77af, and 0x77d5.

  1096. Reported: “The disassembly supports a good controlled case: set the pair to foreground 0, background-ish operand 1, then enter attribute mode. Because 0x6a recomputes with [0x1757] = 1, the fill attribute should become 0xf0, which ought to show as a full white background in the EGA capture.”

    Action/result: Added planned case text_attribute_pair_changes_attr_mode_clear_color.

  1097. Reported: “I’ve added that as a real fixture case rather than a one-off script. Now I’m wiring the unit expectations around it, then I’ll let QEMU tell us whether the source-derived 0xf0 prediction matches the original engine.”

    Action/result: Updated tools/logic_interpreter_probe.py and tests/test_logic_interpreter_probe.py.

  1098. Reported: “The fixture is in place with an explicit “no sprite expected” comparison, since active attribute mode hides the normal validation draw. I’m running the local probe tests before spending a QEMU boot on it.”

    Action/result: Ran the focused unit tests for case coverage and expected visual rectangles; both passed.

  1099. Reported: “The harness tests are clean. Next I’m running just this new 0x6d case through the original interpreter; if it matches, we can promote the opcode’s evidence level instead of leaving it as dispatch-only.”

    Action/result: Ran the one-case QEMU batch text_attr_pair_behaviour_001.

  1100. Reported: “QEMU matched exactly: the original engine clears the whole visible surface to color 15 after 0x6d(0,1) followed by 0x6a. I’m updating the evidence tables and prose now so the source-derived helper behavior and the dynamic confirmation line up.”

    Action/result: Confirmed the original-engine capture matched the local expected full-screen color-15 surface.

  1101. Reported: “The match gives us a nice high-level rule: 0x6d stores the logical pair, and 0x6a reinterprets that saved pair as a packed text attribute once attr mode is enabled. I’m promoting 0x6d in the generated evidence source, then I’ll regenerate the mdBook page from it.”

    Action/result: Moved 0x6d from dispatch-smoke to behavior-level evidence in tools/logic_opcode_evidence.py and regenerated the evidence chapter.

  1102. Reported: “The generated opcode matrix now marks 0x6d as behavior-level QEMU evidence. I’m folding the same result into PROGRESS and the implementation-oriented text sections so the tracker, opcode chapter, and compatibility notes all tell the same story.”

    Action/result: Began updating tracker and documentation prose.

  1103. Reported: “One small bookkeeping wrinkle: docs/src/progress_log.md is long enough that a narrow sed range landed in an older section, so I’m using the tail for the current append point. The substantive 0x6d changes are straightforward.”

    Action/result: Located the current progress-log append point.

  1104. Reported: “I found the append point. Before final validation I’ll add the current-turn progress entries there, including the QEMU result and the docs updates, so the historical log stays complete.”

    Action/result: Prepared to append the current-turn progress-log entries.

  1105. Reported: “PROGRESS.md is bumped to 163/176 action opcodes at full coverage, with 13 partials left. I’m updating the opcode prose next so implementers can see the exact pair-to-attribute behavior we proved.”

    Action/result: Updated PROGRESS.md and docs/src/logic_bytecode.md.

  1106. Reported: “The opcode row now carries both the storage globals and the visible consequence. I’m adding the same rule to the runtime model because this is really part of the text-surface state machine, not just a bytecode footnote.”

    Action/result: Updated docs/src/runtime_model.md.

  1107. Reported: “Runtime prose is updated. I’m attaching the new case name and the 0xf0 observation to the symbolic label map, since that file is the cross-version anchor we’ll need when addresses move in other AGI builds.”

    Action/result: Updated docs/src/symbolic_labels.md.

  1108. Reported: “The symbolic map now links code.text.set_attribute_pair to the new validation case. I’m adding the reproducible QEMU command/result to the compatibility chapter so future runs can replay this exact check.”

    Action/result: Updated docs/src/compatibility_testing.md.

  1109. Reported: “Compatibility docs have the exact TA batch command and result. I’m appending the clean-room notes section now with the disassembly evidence, unit/QEMU commands, and what changed in the model.”

    Action/result: Updated docs/src/clean_room_executable_notes.md.

  1110. Reported: “The notes already have the previous text-mode batches, so I’m appending this as a small follow-on section rather than rewriting history. That keeps the trail readable: source helper, new fixture, QEMU match, tracker promotion.”

    Action/result: Appended the dated clean-room notes section for the 0x6d text-attribute-pair behavior probe.

  1111. Reported: “The clean-room notes now include the 0x6d pass. I’m catching the progress log up through this point before validation; after the checks run, I’ll append a short validation-result entry too.”

    Action/result: Appended the current progress-log batch before validation.

  1112. Reported: “The progress log is caught up through the documentation work. I’m running the full validation pass now: unit suite, generated evidence check, mdBook build, and whitespace check.”

    Action/result: Ran python3 -B -m unittest discover -s tests, python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check.

  1113. Reported: “All four validations passed: 103 unit tests, evidence --check, mdBook, and whitespace. Because I’m adding that validation note to the log, I’ll rerun the lightweight docs/evidence checks afterward so the final state is clean.”

    Action/result: Recorded the successful validation result before lightweight final checks.

  1114. Reported: “The validation result is recorded. I’m doing the final lightweight pass now: rebuild the book after the log edit, confirm generated evidence is still current, and inspect the final diff/status.”

    Action/result: Ran final lightweight checks and inspected the uncommitted diff/status.

  1115. Reported: “I spotted one more user-facing update from this validation loop that needs to be logged. I’m adding that final progress-log entry and rerunning the docs/evidence/whitespace checks once more, quietly after this.”

    Action/result: Recorded the final progress-log update before quiet validation checks.

  1116. Reported: “I’ll keep moving from the current uncommitted 0x6d state and choose the next partial opcode(s) from PROGRESS.md. I’ll keep the updates concise, but I’ll work in a longer continuous pass and only stop if validation fails in a way that needs a decision.”

    Action/result: Resumed from the uncommitted text-attribute state and selected the next partial opcodes from PROGRESS.md.

  1117. Reported: “I’m looking at the remaining partial list now. The next best candidate looks like the input-line pair 0x89/0x8a, because the disassembly already exposes visible buffer globals and there may be a narrow display-surface probe hiding in the existing text harness.”

    Action/result: Chose 0x89/0x8a as the next behavior-level probe target.

  1118. Reported: “The input-line path is a little more awkward than 0x6d: proving it exactly would require either text glyph rendering or a targeted predicate comparison. I’m checking the helper code first to avoid building a test that merely “sees something changed” without knowing why.”

    Action/result: Re-read input-line helpers before designing a fixture.

  1119. Reported: “The helper confirms a useful exact sequence: 0x8a repeatedly sends backspace through the same input character helper until visible length reaches zero, while 0x89 repopulates visible buffer 0x0fa4 from source buffer 0x0fce. I’m going to add focused typed-input cases plus a small rectangle-signal comparator for the one path where exact glyph pixels are not yet modeled.”

    Action/result: Planned typed-input probes and a bounded rectangle comparator for UI text rows.

  1120. Reported: “I’m editing the probe harness in a contained way: exact full-frame comparisons stay the default, and the new rectangle checks are only for UI text rows where the source behavior is known but glyph rendering is not part of the local expected renderer yet.”

    Action/result: Added optional rectangle checks without changing default exact frame comparison behavior.

  1121. Reported: “The new cases separate three things: a baseline that typed input really appears, 0x8a making that row black again, and 0x8a followed by 0x89 bringing the typed buffer back. I’m adding the rectangle-check evaluator now so only the baseline/refresh glyph-bearing cases use it.”

    Action/result: Added typed-input baseline, erase, and refresh candidate cases.

  1122. Reported: “Rectangle checks are now a narrow alternate comparison path: they count specific colors inside a bounded logical row and return a normal match/mismatch report. I’m wiring unit tests around the new cases and the comparator before running QEMU.”

    Action/result: Implemented compare_rect_checks and began focused unit coverage.

  1123. Reported: “The unit tests now assert the new cases exist and that rectangle checks both match and fail when they should. I’m running the focused probe tests before the original-engine batch.”

    Action/result: Added unit assertions for the new cases and comparator.

  1124. Reported: “Focused tests pass. Next I’m running a four-case QEMU batch: status show, typed-input baseline, 0x8a erase, and 0x8a plus 0x89 refresh.”

    Action/result: Focused unit tests passed and the first mixed QEMU batch was launched.

  1125. Reported: “The batch is running from a single DOS snapshot now. It built the selected cases in base-case order, which is fine; I’m waiting for QEMU to finish and then I’ll inspect any mismatch rather than guessing.”

    Action/result: Waited for the snapshot-based input/status QEMU run.

  1126. Reported: “QEMU matched the typed-input baseline and the 0x8a erase case, so that part is solid. The 0x8a+0x89 refresh case mismatched, which likely means my assumed source-buffer persistence is wrong or the refresh timing/path differs; I’m inspecting the report and capture before changing the model.”

    Action/result: First QEMU run matched baseline/erase and exposed a refresh mismatch.

  1127. Reported: “The mismatch is informative: 0x8a and 0x8a+0x89 produce the same black-row capture, so the refresh did not restore from the source buffer as I predicted. I’m doing a small pixel-count check on the captured rows now to distinguish “text glyph” from “whole-row/ background color.””

    Action/result: Inspected the failed refresh capture and began row color counts.

  1128. Reported: “That quick row-count script is still running a moment longer than expected, so I’m polling it instead of starting another image read.”

    Action/result: Waited for the row-count inspection to complete.

  1129. Reported: “The row counts explain the failure: before Enter, typed characters live in the visible/edit buffer, not the source string that 0x89 replays from. I’m correcting the 0x89 case to type look plus Enter first, so the source buffer at 0x0fce should be populated before refresh.”

    Action/result: Corrected the refresh fixture to use accepted input rather than unaccepted live-edit text.

  1130. Reported: “The case is corrected to use the entered-command path instead of the live edit buffer. I’m rerunning the focused tests and then a smaller QEMU batch for the three input-line cases.”

    Action/result: Renamed/corrected the 0x89 case and reran focused tests.

  1131. Reported: “The corrected input batch is running. If it passes, 0x8a is exact-row validated and 0x89 is validated as a source-buffer repaint, with the entered-command detail documented.”

    Action/result: Launched the corrected input-line QEMU batch.

  1132. Reported: “The corrected input batch passed: baseline visible typed text, 0x8a erasing to a black row, and 0x89 repainting after Enter all matched. I’m running the status-line show case separately now, since the first mixed batch stopped before it.”

    Action/result: Corrected input-line batch matched and the status-line QEMU case was launched.

  1133. Reported: “With those matches, I can promote 0x70, 0x89, and 0x8a: 0x70 as a status-row draw signal, 0x8a as an exact typed-row erase, and 0x89 as repainting from the entered source buffer. I’m updating the evidence generator and tracker first.”

    Action/result: Promoted 0x70, 0x89, and 0x8a in the evidence plan.

  1134. Reported: “The generator now points those opcodes at their behavior cases instead of smoke cases. I’m regenerating the opcode evidence page and then updating PROGRESS.md counts.”

    Action/result: Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md.

  1135. Reported: “The generated evidence page is refreshed. I’m changing the tracker from 163 to 166 covered action opcodes, leaving 10 partial action rows.”

    Action/result: Prepared the PROGRESS.md coverage-count update.

  1136. Reported: “PROGRESS.md is updated. I’m tightening the opcode prose next, including the correction that 0x89 replays the entered command buffer, not the unaccepted live edit buffer.”

    Action/result: Updated PROGRESS.md for 166/176 action opcode coverage.

  1137. Reported: “The opcode chapter now records both the successful cases and the failed pre-Enter hypothesis for 0x89. I’m adding that higher-level distinction to the runtime model so an implementation knows which input buffer is replayed.”

    Action/result: Updated docs/src/logic_bytecode.md for 0x70, 0x89, and 0x8a.

  1138. Reported: “The runtime model now distinguishes live edit text from the entered source buffer. I’m updating the symbolic label map with the newly validated input/status helpers and their SQ2 addresses.”

    Action/result: Updated docs/src/runtime_model.md with the live/source input-buffer distinction.

  1139. Reported: “The symbolic map has new stable labels for the input character helper, erase, refresh, and source-to-visible append helper. I’m adding the QEMU batch commands/results to the compatibility chapter now.”

    Action/result: Updated docs/src/symbolic_labels.md with input/status helper labels.

  1140. Reported: “The compatibility chapter has the earlier text batches in the right spot, so I’m inserting the new input-refresh/status-show evidence alongside them and marking the old smoke batch as superseded for three more opcodes.”

    Action/result: Added input-refresh and status-show QEMU commands/results to docs/src/compatibility_testing.md.

  1141. Reported: “Compatibility docs now have both new batch commands and the failed 0x89 pre-Enter correction. I’m checking for any older “still smoke-only” wording that needs to be superseded.”

    Action/result: Searched for stale smoke-only wording.

  1142. Reported: “I found two stale “dispatch-smoke” summaries that predate the new behavior cases. I’m revising them to say which parts are now superseded instead of letting the docs contradict the tracker.”

    Action/result: Revised stale text/status smoke summaries.

  1143. Reported: “The stale summaries now point forward to the focused behavior probes. I’m making the high-level progress areas a little less stale as well, especially the input/text subsystem summary.”

    Action/result: Updated high-level subsystem tracker wording in PROGRESS.md.

  1144. Reported: “The subsystem tracker now reflects that visible input-line refresh/erase is no longer just smoke coverage. I’m appending the clean-room evidence section with the exact failed and successful QEMU batches, since the failed pre-Enter hypothesis is part of the useful history.”

    Action/result: Prepared the clean-room notes append for input/status.

  1145. Reported: “The notes file ends with the 0x6d section, so I’m appending a new dated input/status section after it. This will preserve the row-count correction and the final promotion result.”

    Action/result: Appended the input-line refresh/erase and status-line show notes section.

  1146. Reported: “I’m going to keep pushing into the remaining partials. The trace-window pair 0x95/0x96 looks like the next feasible source-first target: the existing smoke case deliberately leaves flag 10 clear, but the disassembly says setting flag 10 should draw a boxed trace area.”

    Action/result: Selected trace-window enable/configure as the next partial opcode pair.

  1147. Reported: “I’m picking up exactly where the trace-window disassembly left off. The key thing I’m checking now is whether the visible effect of 0x95 can be validated without getting tangled in the trace pause mechanism that may intercept the very next opcode.”

    Action/result: Resumed trace-window validation after context compaction.

  1148. Reported: “The static trace code confirms two separate responsibilities: 0x96 only writes trace configuration globals, while 0x95 consults flag 10 and then draws the window through the normal boxed-text helper. I’m going to make the QEMU case line up with that split rather than treating it as a generic “some pixels changed” test.”

    Action/result: Re-read trace docs/source and planned a focused enabled trace-window case.

  1149. Reported: “There’s already an older note saying an enabled trace attempt showed a box but wasn’t in the comparison suite. I’m turning that into a repeatable case now, with a bounded rectangle predicate attached to the exact configured rows.”

    Action/result: Located the prior trace-window attempt and began converting it into a stable fixture.

  1150. Reported: “The case should end right after enabling trace, or use a one-shot branch, because the disassembly says an already-active 0x95 consumes an extra byte. That extra-byte behavior is precisely the kind of footgun I want the test to avoid while we’re validating the window draw itself.”

    Action/result: Chose a one-shot trace fixture shape to avoid repeated active-state byte consumption.

  1151. Reported: “I’ve confirmed the fixture harness can already express this cleanly: compare_view=False plus a rectangle check lets us validate the trace text/window pixels without pretending we have a full glyph model. Next I’m adding the enabled-trace case and a unit assertion for its expected bounds.”

    Action/result: Prepared to add the trace case and unit assertion.

  1152. Reported: “For the trace window’s vertical position, I’m using the source formula directly: top row is the configured input/status base row plus the trace row offset plus one. With 0x6f(0,0,5) and 0x96(...,1,2), the box should land around logical rows 48..63.”

    Action/result: Formed an initial trace rectangle hypothesis.

  1153. Reported: “Small correction from the source formula: the trace vertical base is the first 0x6f operand ([0x05dd]), not the status-row operand. I’m setting that deliberately in the fixture so the expected rectangle is derived from the actual globals the handler reads.”

    Action/result: Corrected the vertical-position hypothesis before adding the fixture.

  1154. Reported: “I’m making the trace case one-shot and omitting the validation sprite, because the trace dispatcher can pause before later bytecode. The observable we need is the boxed trace area itself, so the case will compare that surface directly.”

    Action/result: Added trace_window_enable_draws_box_when_flag10_set and a unit assertion for its initial rectangle checks.

  1155. Reported: “The fixture and unit assertion are in place. I’m running the focused tests first, then I’ll let QEMU tell us whether the exact row window is right or whether the text helper’s border math needs a tighter bound.”

    Action/result: Ran focused unit tests for the trace fixture and rectangle comparator.

  1156. Reported: “Focused tests pass. I’m running the enabled trace-window case through the original engine now; this one may pause inside the trace UI, but that’s acceptable because the screenshot is the evidence we need.”

    Action/result: Focused tests passed and QEMU trace batch trace_window_enable_001 was launched.

  1157. Reported: “The enabled trace case matched on the first pass, so the source-derived row band is correct enough for automation. I’m inspecting the capture/report anyway before promoting the opcodes, because a match count alone doesn’t tell us whether we’re seeing the border, trace text, or both.”

    Action/result: First trace QEMU run matched and report/capture inspection began.

  1158. Reported: “The report path points to the fixture capture rather than the batch directory; I asked the quick inspector for the wrong copy. I’m rerunning that inspection against the actual saved PPM now, then I’ll update the evidence mappings.”

    Action/result: Re-ran capture inspection against the correct fixture PPM.

  1159. Reported: “The capture is stranger than a small bordered box: most of the upper logical rows are white, with a few trace-text/background pixels inside. That may be the boxed text helper filling a large text area, so I’m checking the image visually and then I’ll keep the automated predicate honest by describing exactly what it proves.”

    Action/result: Inspected downsample color counts and prepared visual inspection.

  1160. Reported: “The image viewer could not read that PPM directly, so I’m converting a PNG copy under build/ for inspection. This is just for local sanity checking; the test continues to use the saved PPM.”

    Action/result: Converted the trace PPM capture to PNG for visual inspection.

  1161. Reported: “The visual check is useful: the helper draws a red-bordered, white-filled trace box with black trace text like 0: 12(94). I’m tightening the check to look for both the white fill and red border, so it’s less likely to pass on an unrelated white screen.”

    Action/result: Identified the visible trace-window components from the capture.

  1162. Reported: “The visual mapping says my first rectangle was broad enough but not ideal: the red border is around logical row 5 in the downsampled frame, while the white fill spans below it. I’m checking the local disassembly helpers briefly before tightening the predicate, because the helper’s coordinate system clearly is not the same as the 8-pixel text-row helpers.”

    Action/result: Compared observed trace-window pixel rows against the source-derived coordinate expectations.

  1163. Reported: “I’m revising the predicate into three signals: red border pixels, a large white fill area, and black trace text inside it. That matches the observed UI without requiring exact glyph reproduction.”

    Action/result: Tightened the trace rectangle checks to require red, white, and black visible signals.

  1164. Reported: “The trace predicate is now tighter. I’m rerunning the focused tests and then the same original-engine capture to make sure the stricter checks still pass.”

    Action/result: Re-ran focused unit tests after tightening the predicate.

  1165. Reported: “The stricter unit checks pass. I’m rerunning the QEMU trace case; if it matches again, I’ll promote 0x95 and 0x96 from dispatch-smoke to QEMU-validated in the evidence and tracker.”

    Action/result: Launched final trace QEMU batch trace_window_enable_002.

  1166. Reported: “The stricter QEMU trace capture matched. I’m updating the generated evidence mapping now so the opcode table points at the enabled trace-window case instead of the older flag-clear smoke case.”

    Action/result: Final trace QEMU batch matched and evidence mapping update began.

  1167. Reported: “0x95 and 0x96 are moving out of the smoke bucket together: the enabled case exercises 0x96’s stored row/height configuration and 0x95’s flag-gated draw path in the original interpreter.”

    Action/result: Updated tools/logic_opcode_evidence.py to map 0x95 and 0x96 to the enabled trace case.

  1168. Reported: “The evidence generator is patched. I’m regenerating the opcode evidence chapter, then I’ll update PROGRESS.md from 166 to 168 covered action opcodes.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  1169. Reported: “The generated evidence page is refreshed. I’m updating the tracker counts and the two trace rows now, then I’ll put the new behavior in the opcode/runtime/compatibility notes.”

    Action/result: Prepared the PROGRESS.md promotion update.

  1170. Reported: “The tracker still only has eight partial action opcodes after this promotion: 0x6e, 0x83, 0x8e, 0xa3, 0xa4, 0xa9, 0xaa, and 0xad. I’m making that count explicit in PROGRESS.md.”

    Action/result: Updated PROGRESS.md to 168/176 action opcode coverage.

  1171. Reported: “I’m updating the spec prose carefully here: the table still needs the disassembled control-flow details, while the paragraph below can carry the QEMU-visible result: red border, white fill, black trace text, and the pause-before-next-action behavior.”

    Action/result: Began updating the trace opcode prose.

  1172. Reported: “That patch missed the paragraph because the file has slightly different wrapping after earlier edits. I’m rereading the local section and applying the update against the exact text.”

    Action/result: Re-read the exact docs/src/logic_bytecode.md trace block after a patch-context miss.

  1173. Reported: “I found the exact block. The update also fixes wording from “input-line row” to “input-line base row,” which matches the 0x6f storage we just rechecked.”

    Action/result: Updated docs/src/logic_bytecode.md for the trace-window behavior and wording correction.

  1174. Reported: “The opcode chapter now carries both the source-backed internals and the new QEMU-visible trace behavior. I’m adding the same higher-level interpretation to the runtime model: this is developer tracing state, not ordinary game logic state.”

    Action/result: Updated docs/src/runtime_model.md.

  1175. Reported: “The runtime model is updated. I’m refreshing the symbolic labels next so the cross-version map says the enabled draw path is now validated, not merely observed as a one-off mismatch.”

    Action/result: Updated docs/src/symbolic_labels.md trace entries.

  1176. Reported: “The symbolic map now points to the new trace batch. I’m adding the exact QEMU command/result to the compatibility chapter and marking the older trace smoke entry as superseded for 0x95/0x96.”

    Action/result: Updated docs/src/compatibility_testing.md.

  1177. Reported: “The compatibility chapter has the old system batch grouped with several follow-ups. I’m adding a small focused trace subsection immediately after it, so future runs can reproduce the promotion without rerunning unrelated dialog cases.”

    Action/result: Inserted the focused enabled trace-window command/result in the compatibility chapter.

  1178. Reported: “Compatibility evidence is now reproducible. I’m appending a clean-room notes section with the source locations, the QEMU command, and the observed pixel/UI shape, then I’ll update the progress log.”

    Action/result: Prepared the enabled trace-window notes section.

  1179. Reported: “The notes section will include the failed/awkward history too: older enabled trace attempts were excluded because they polluted the validation sprite, but the new rectangle-check path lets us validate the trace UI directly.”

    Action/result: Appended the enabled trace-window validation section to docs/src/clean_room_executable_notes.md.

  1180. Reported: “I’m catching the historical progress log up now before doing more opcode work. This will be a longer append because it needs to preserve both the input/status work from before compaction and the trace-window work from this pass.”

    Action/result: Appended progress-log entries for the input/status and trace-window work through this point.

  1181. Reported: “The progress log is caught up through the trace promotion. I’m moving on to the remaining eight partial action opcodes now, starting by rereading their current descriptions so the next probe is driven by disassembly rather than trial-and-error.”

    Action/result: Reviewed the remaining partial opcode descriptions.

  1182. Reported: “Of the remaining eight, 0xa9 looks like the best next target: it has a visible restore path through the same saved-rectangle helper used by text windows, so we may be able to validate it with a modal message/window setup and an exact surface check.”

    Action/result: Selected 0xa9 as the next validation candidate.

  1183. Reported: “0xa9 may not be easiest after all: ordinary message-display actions close their own text-window state before the following opcode, so a plain “show message then 0xa9” would still only cover the inactive path. I’m checking the 0x0d0f/input helper path because 0xa9 also clears that flag unconditionally, which may give us a cleaner behavior probe and cover 0xa3/0xa4 at the same time.”

    Action/result: Redirected the probe design toward the input-width flag path shared by 0xa3, 0xa4, and 0xa9.

  1184. Reported: “The source gives a promising behavioral hook: code.input.handle_input_char caps visible input length at 0x24 when [0x0d0f] is set, but otherwise uses 0x28 - len(string_slot_0). I’m adding long-input probes that make that cap visible, which should cover 0xa3, 0xa4, and the unconditional 0xa9 clear path.”

    Action/result: Began adding long-input probes based on the disassembled input-width calculation.

  1185. Reported: “The three long-input cases are in the harness. I’m adding unit assertions for their presence and expected color-count thresholds before trying them in QEMU.”

    Action/result: Added the initial 0xa3/0xa4/0xa9 input-width cases to the probe harness.

  1186. Reported: “The unit assertions are in place. I’m running the focused tests now; if those pass, I’ll run a three-case QEMU batch to tune the row-count thresholds against the real interpreter.”

    Action/result: Added focused unit tests for the input-width cases.

  1187. Reported: “Focused tests pass. I’m running the original-engine batch for 0xa3, 0xa4, and 0xa9 now; this will tell us whether the source-derived input-width effect is visible enough for stable compatibility checks.”

    Action/result: Started the first QEMU input-width validation batch.

  1188. Reported: “The three-case batch is running from one snapshot. I’m waiting for QEMU to finish rather than launching anything else, since the harness owns the shared VNC/socket while it runs.”

    Action/result: Waited on the snapshot QEMU batch.

  1189. Reported: “The 0xa3 case matched, but the 0xa4 case did not: clearing the flag didn’t reduce visible text as much as my first threshold expected. I’m inspecting the capture and counts before deciding whether this is a threshold issue or a wrong model of when the long slot is consulted.”

    Action/result: Identified the first input-width mismatch and began inspecting capture counts.

  1190. Reported: “The mismatch is large: the 0xa4 case still has 548 white pixels in the input row. That suggests the long string in slot 0 may not be affecting the cap because slot 0 is being cleared or not the same slot 0x3652 measures; I’m waiting for the detailed row-count script and then I’ll adjust based on the source path.”

    Action/result: Captured the large row-count mismatch for the first 0xa4 attempt.

  1191. Reported: “The row counts show 0xa3 and 0xa4 looked identical because my long slot-0 string was visible prompt text, not just an invisible cap. I’m switching that slot to a long run of spaces: same length for the source formula, but no glyph pixels from the prefix itself.”

    Action/result: Changed the fixture string slot to long blank prompt text.

  1192. Reported: “The fixtures now use a long blank string slot, so any white pixels on the row should come from typed live input rather than the slot-0 prefix. I’m rerunning the focused tests, then the same three-case QEMU batch.”

    Action/result: Prepared the second input-width batch.

  1193. Reported: “The focused tests still pass. I’m rerunning the input-width QEMU batch without changing the thresholds yet, so the original engine tells us whether the long-blank-slot model is right.”

    Action/result: Started the second QEMU input-width batch.

  1194. Reported: “The rerun is still inside QEMU. I’m polling it rather than starting another disassembly pass, since these cases depend on the shared snapshot harness completing cleanly.”

    Action/result: Waited on the second snapshot QEMU batch.

  1195. Reported: “Now the 0xa3 case is the one that mismatched, which is good evidence the prompt pollution is gone. I’m checking the exact row count; if it’s just below the threshold we can tune it, but if it is near zero then the blank slot may be suppressing typed input differently than expected.”

    Action/result: Observed that the long blank prompt moved the visible effect rather than removing it.

  1196. Reported: “The source model is still useful, but the visible signal is one row lower: the long blank prefix moves the typed characters into the next text row, so 0xa3 shows glyphs at logical Y 48..55. I’m retargeting the checks there; 0xa4/0xa9 should keep that wrapped row empty if they clear the flag.”

    Action/result: Retargeted input-width checks to the wrapped text row.

  1197. Reported: “The expected checks now target the wrapped row. I’m rerunning the focused unit test and then the three-case QEMU batch again, this time with all three cases able to exercise the same wrapped-row predicate.”

    Action/result: Started the third validation attempt.

  1198. Reported: “The unit check passes. I’m rerunning the QEMU batch; this one should distinguish 0xa3 from both clearing paths if the disassembly-derived cap model is correct.”

    Action/result: Began another snapshot QEMU run for the retargeted checks.

  1199. Reported: “The batch is running. This pass is a good example of QEMU as confirmation rather than discovery: the key distinction came from 0x3652; QEMU is just helping pin down the visible row where the effect lands.”

    Action/result: Waited on the retargeted QEMU batch.

  1200. Reported: “0xa3 now matches on the wrapped row, but 0xa4 still shows wrapped-row pixels. That means either 0xa4 is not being reached before typing, or the long-slot cap is not recomputed the way I expected after the flag clear. I’m checking the report and source-flow details before changing the fixture again.”

    Action/result: Investigated the remaining wrapped-row mismatch.

  1201. Reported: “The 0xa4 mismatch is actually a full white wrapped row, which looks like the blank prefix itself wrapped and painted as a white text rectangle, not necessarily typed characters. I’m waiting on the row breakdown and then I’ll avoid relying on wrapped text rows for this flag test.”

    Action/result: Distinguished white row fill from typed glyph pixels.

  1202. Reported: “A memory-backed check is probably the right tool for these tiny globals. Before adding anything, I’m reading the existing QEMU snapshot helper to see if we can extend it narrowly with monitor xp reads instead of building a separate debugger harness.”

    Action/result: Considered, but did not need, a monitor-backed memory check for this flag.

  1203. Reported: “There is a usable visible signal after all, but it’s the inverse of my first check: the wrapped row is white fill in both paths, while only the 0xa3 path contains black glyph pixels from accepted typed characters. I’m changing the cases to compare black glyph pixels, which aligns with the source distinction and the captures.”

    Action/result: Changed the predicate from white fill to black glyph pixels.

  1204. Reported: “The harness now checks for black glyph pixels in the wrapped row. I’m updating the unit expectations to match that corrected signal, then I’ll rerun all three original-engine cases.”

    Action/result: Updated the harness and test expectations.

  1205. Reported: “The unit expectation is corrected. I’m running the focused unit test and then a final QEMU pass for 0xa3, 0xa4, and 0xa9 with the black-glyph predicate.”

    Action/result: Started focused tests for the corrected predicate.

  1206. Reported: “The focused unit test passes. I’m rerunning the three-case QEMU batch; this should also exercise the 0xa9 clearing path now that 0xa4 is expected to pass.”

    Action/result: Started the final QEMU input-width batch.

  1207. Reported: “The batch is running again. If it passes, I’ll promote 0xa3 and 0xa4 for the input-width flag behavior, and 0xa9 for the unconditional flag clear while leaving its active saved-rectangle restore source-backed.”

    Action/result: Waited on the final input-width batch.

  1208. Reported: “The corrected input-width batch passed all three cases. I’m promoting 0xa3, 0xa4, and 0xa9 now, with the caveat documented that 0xa9’s active saved-rectangle restore remains source-backed while its unconditional [0x0d0f] clear is QEMU-validated.”

    Action/result: Promoted 0xa3, 0xa4, and 0xa9.

  1209. Reported: “I’m updating the evidence generator first so the generated opcode evidence table agrees with the new QEMU batch before I touch the prose tracker.”

    Action/result: Updated tools/logic_opcode_evidence.py.

  1210. Reported: “The generator now points at the input-width behavior cases. I’m regenerating the evidence page and then bumping the tracker to 171/176 covered action opcodes.”

    Action/result: Regenerated the logic opcode evidence chapter.

  1211. Reported: “The evidence chapter is regenerated. I’m updating PROGRESS.md; after this, the only partial action opcodes left should be 0x6e, 0x83, 0x8e, 0xaa, and 0xad.”

    Action/result: Prepared the tracker update.

  1212. Reported: “The tracker is updated to 171/176. I’m revising the opcode prose next so it explains the actual high-level behavior: [0x0d0f] changes the live input width calculation, and 0xa9 always clears that flag even without an active text window.”

    Action/result: Updated the opcode prose for input-width state.

  1213. Reported: “The row updates need a smaller patch because one of the surrounding summaries has been rewrapped by earlier edits. I’m applying the opcode-row changes first, then I’ll adjust the two stale summary paragraphs against their exact text.”

    Action/result: Split the opcode chapter patch after a context miss.

  1214. Reported: “I’ve got the exact paragraphs. I’m replacing the opcode rows and then cleaning the old “smoke-only” summaries so the chapter doesn’t contradict the new QEMU evidence.”

    Action/result: Removed stale smoke-only wording for promoted opcodes.

  1215. Reported: “The opcode chapter is now consistent. I’m updating the runtime model’s text/alternate-mode state row so implementers see why [0x0d0f] matters outside a one-line opcode description.”

    Action/result: Updated the runtime model.

  1216. Reported: “I accidentally triggered an empty web search while switching tools; no external material was consulted or used. I’m continuing with the local runtime-model edit.”

    Action/result: Recorded that the accidental empty search produced no external source material.

  1217. Reported: “The runtime model now includes the input-width state. I’m updating the symbolic label map with stable labels for the input-width flag and the small 0xa3/0xa4 action handlers, so future interpreter versions can be compared by role rather than raw address.”

    Action/result: Began updating symbolic labels for input-width state.

  1218. Reported: “The symbol map already had code.text.close_window_state; I’m expanding it and adding the flag/action labels in the same Text/Input section.”

    Action/result: Added and expanded text/input symbolic labels.

  1219. Reported: “The symbolic labels now include the width flag and its handlers. I’m adding the successful input_width_flag_004 batch to the compatibility chapter, with a short note about what the black-glyph predicate proves.”

    Action/result: Prepared compatibility evidence for the input-width batch.

  1220. Reported: “The compatibility chapter’s text/input section is the right home for this, so I’m placing the input-width batch between the prompt/text-attribute probes and the older text/status smoke batch.”

    Action/result: Inserted the input-width compatibility evidence.

  1221. Reported: “The compatibility chapter now has the reproducible successful batch. I’m appending the clean-room notes with the failed threshold/string attempts too, because those corrections explain why the final black-glyph predicate is the right one.”

    Action/result: Added the input-width observations and corrections to the clean-room notes.

  1222. Reported: “The input-width work is documented. I’m continuing into the last five partials now. The next likely candidate is 0x83, because it writes global [0x0139], and that same global is touched by first-object motion helpers, so there may be a source-driven movement probe available.”

    Action/result: Started the 0x83 investigation from disassembly.

  1223. Reported: “0x83 itself is extremely small in the disassembly: it only writes zero to [0x0139]. I’m looking at consumers of that word now; if no existing script-visible condition depends on it, I’ll keep it source-backed rather than inventing a brittle visual test.”

    Action/result: Confirmed that the action body only clears [0x0139].

  1224. Reported: “The consumer is the main cycle’s direction mirror between object 0 byte +0x21 and global byte [0x000f]. I’m reading that small source block directly; if 0x83 flips which side wins, we may be able to observe it with an existing object field getter and a conditional draw.”

    Action/result: Identified the main-cycle direction-mirror consumer for [0x0139].

  1225. Reported: “I’ll keep moving from PROGRESS.md and avoid waiting for repeated “continue” prompts. First I’m catching the progress log up with the recent input-width and 0x83 investigation updates, then I’ll continue from the disassembly-backed direction-mirror probe.”

    Action/result: Resumed the PROGRESS.md task stream and prepared to update the historical progress log.

  1226. Reported: “The tracker confirms the remaining partial action opcodes are 0x6e, 0x83, 0x8e, 0xaa, and 0xad. I’m adding the recent updates to the historical log now, then I’ll build the 0x83 probe around the main-cycle object0 direction mirror.”

    Action/result: Confirmed the partial opcode list and appended prior progress-log entries through the input-width work.

  1227. Reported: “The progress log is caught up. Now I’m reading the existing object-field probe helpers so the 0x83 case can reuse established fixture patterns instead of adding a one-off script shape.”

    Action/result: Reviewed object field setter/getter fixture helpers.

  1228. Reported: “The existing harness already has clean helpers for setting and reading object byte +0x21, which is exactly what the 0x83 consumer needs. I’m checking the fixture builder now so the new case can span multiple interpreter cycles without relying on timing guesswork.”

    Action/result: Confirmed 0x56/0x57 helper coverage and inspected multi-cycle fixture support.

  1229. Reported: “The case can be cycle-structured with two flags: phase 0 seeds object0 direction and clears [0x0139]; phase 1 lets the main loop copy that into global [0x000f], then sets [0x0139]; phase 2 verifies the global value was copied back into object0. I’m checking the exact helper bytes for nested if and flag conditions before patching it in.”

    Action/result: Designed the first attempted 0x83 multi-cycle fixture.

  1230. Reported: “The phase plan is sound: it observes 0x83 indirectly through 0x84 and 0x57, after the main loop has had a full cycle to perform each mirror direction. I’m going to add this as a narrowly named compatibility case, then run it alone under QEMU.”

    Action/result: Began adding the attempted 0x83 compatibility case.

  1231. Reported: “I found a useful detail: the existing object helpers normally use object 10, but the main-loop mirror is specifically object 0. The new fixture will initialize object 0 as a normal object entry, then use the same +0x21 setter/getter to observe the global mirror.”

    Action/result: Adjusted the attempted fixture to target object 0.

  1232. Reported: “One subtlety: if phase 0 simply sets a flag, phase 1 would run later in the same logic pass. I’m using an explicit end inside each successful phase so the handoff happens at the next main cycle, where the disassembled mirror code actually runs.”

    Action/result: Added phase-ending end actions to the attempted fixture design.

  1233. Reported: “The probe case is in the harness. I’m adding a unit assertion for it now, plus a small structural test that its bytecode includes the phase-ending end actions and both 0x83/0x84, so future edits do not accidentally collapse it back into a same-cycle test.”

    Action/result: Added temporary unit assertions for the attempted fixture.

  1234. Reported: “The focused structural test is added. I’m running just the probe tests that cover the case list and this new multi-cycle shape before I let QEMU execute it.”

    Action/result: Ran focused unit tests for the attempted fixture; they passed.

  1235. Reported: “The unit checks pass. I’m running the single 0x83 original-engine case now; if it matches, we’ll have a behavior-level validation of the selector bit rather than only a dispatch smoke.”

    Action/result: Started QEMU batch object0_direction_mirror_001.

  1236. Reported: “That command bounced because this particular harness doesn’t use a boolean --snapshot; it has snapshot image path options and otherwise runs its snapshot path by default. I’m rerunning with the harness’s actual CLI.”

    Action/result: Corrected the logic probe command after the ambiguous --snapshot option.

  1237. Reported: “The first QEMU run completed but did not match. I’m reading the generated comparison report and capture summary before changing anything; the key question is whether the phase logic failed to reach phase 2 or whether the mirror copied a different value than expected.”

    Action/result: Inspected QEMU report object0_direction_mirror_001.

  1238. Reported: “The mismatch is interesting, not a total failure: the validation sprite did draw, but its pixels are a different cel than the default expected one. That suggests the probe reached phase 2 and changed the object/direction state, but the transient validation draw is being influenced by direction-related selection. I’m going to identify the actual cel before deciding whether the behavioral check should expect that variant.”

    Action/result: Compared the capture against local view/cel renders.

  1239. Reported: “The mismatch is only inside the validation sprite, which is reassuring: the draw condition fired. I’m going to use a quick local comparison against available view/group/frame combinations to identify what the real engine drew.”

    Action/result: Determined that an exact transient/persistent marker was not a clean signal.

  1240. Reported: “The real engine drew view 11 frame 7, even though the validation helper requested frame 0, because the transient draw path is also seeing the direction state. That is useful evidence, but for this opcode probe I want the pass/fail signal to be just the object0 mirror, so I’m switching the conditional marker to a persistent object with explicit setup instead of the transient validation helper.”

    Action/result: Reworked the attempted fixture marker to use a persistent object.

  1241. Reported: “The marker now uses object 10 with bit 0x2000 set, so its cel choice should stay fixed while the condition still depends on object0 byte +0x21. I’m rerunning the focused structural test, then the same single QEMU case.”

    Action/result: Ran the updated attempted fixture; it still mismatched.

  1242. Reported: “It still reaches a mismatch, so I’m inspecting the new report. If this is another cel/priority difference we can correct the expected marker; if there is no marker, the phase logic needs adjustment.”

    Action/result: Inspected object0_direction_mirror_002.

  1243. Reported: “Still mismatched. I’m checking whether 0x46 actually stabilizes the marker in this setup or whether persistent-object activation sets the animation bit after the clear.”

    Action/result: Investigated persistent marker frame advancement.

  1244. Reported: “The remaining mismatch is animation timing: the persistent marker advances to frame 7 during the draw wait. Since this probe is about 0x83, I’m using the already-documented 0x46 action to clear the animation bit on the marker so the expected cel stays frame 0.”

    Action/result: Added 0x46 to the attempted marker setup.

  1245. Reported: “The marker now suppresses automatic group selection and frame cycling before activation. I’m doing one more focused unit check and QEMU run for 0x83.”

    Action/result: Ran focused unit check and QEMU batch object0_direction_mirror_003; it still mismatched.

  1246. Reported: “Still mismatched. I’m checking whether 0x46 actually stabilizes the marker in this setup or whether persistent-object activation sets the animation bit after the clear.”

    Action/result: Confirmed the marker still advanced.

  1247. Reported: “The marker still advances to frame 7, so activation probably sets the animation bit after my clear. I’m moving 0x46 after activation; that should leave the marker active but non-advancing during the subsequent frame update.”

    Action/result: Reordered the marker actions.

  1248. Reported: “I’ve reordered the marker actions. I’m rerunning the single QEMU case; if this still advances, I’ll just set the expected frame to the observed stable frame and document why.”

    Action/result: Ran QEMU batch object0_direction_mirror_004; it still mismatched.

  1249. Reported: “The marker remains animated, so an exact-frame comparison is the wrong assertion for this probe. I’m switching the case to a rectangle predicate that only asks “did the conditional marker place non-background sprite pixels here?”, which keeps the test about the 0x83 mirror instead of frame timing.”

    Action/result: Reconsidered the validation predicate after confirming the capture had no marker.

  1250. Reported: “The capture shows no marker at all, so the phase condition is false. I’m generating a disposable diagnostic fixture under build/ that draws a marker at different X positions for object0 direction values 0..8; this will tell us what 0x57 reads after the mirror sequence without adding a speculative permanent test.”

    Action/result: Generated disposable diagnostic manual_direction_value_cases.json.

  1251. Reported: “The disposable case is generated. I’m running it through QEMU with comparison against a blank picture, so any drawn marker will show up as a mismatch bbox and we can map that X position back to the value.”

    Action/result: Ran object0_direction_value_diag_001; it matched blank, showing no value marker in 0..8.

  1252. Reported: “The value diagnostic stayed blank, which means either phase 2 is not being reached or +0x21 is outside 0..8. I’m generating one more disposable variant with an unconditional phase-2 marker, so we can separate phase-control failure from an unexpected direction byte.”

    Action/result: Generated disposable diagnostic manual_direction_phase_cases.json.

  1253. Reported: “The second diagnostic uses high-numbered flags as well, just in case the earlier phase flags were touched by interpreter/game state. I’m running it now.”

    Action/result: Ran object0_direction_phase_diag_001.

  1254. Reported: “The phase-2 marker did draw, so the multi-cycle flag scaffolding is fine. I’m reading the bbox and pixel locations now to infer which value-specific marker, if any, appeared alongside the unconditional one.”

    Action/result: Mapped the value marker to direction value 0.

  1255. Reported: “The phase-2 getter reads direction 0, not 6. That suggests the first object entry’s direction is being cleared before the next top-of-cycle mirror can seed [0x000f], likely because the object is inactive/stationary. I’m testing a source-driven variant that activates object 0 during the seed phase so its direction byte survives long enough to be mirrored.”

    Action/result: Generated active-object disposable diagnostic.

  1256. Reported: “The active-object diagnostic is generated. I’m running it now; the value marker should tell us whether activation preserves the seed direction.”

    Action/result: Ran object0_direction_active_diag_001.

  1257. Reported: “The active-object diagnostic produced a marker. I’m extracting the X buckets now to map it back to the direction value.”

    Action/result: The active-object variant still mapped to direction value 0.

  1258. Reported: “Activating object 0 still yields value 0, so the issue is not just inactive-object cleanup. I’m going back to the disassembly references for [0x0139] and [0x000f]; something after the action may be restoring the selector or clearing the global before the next cycle.”

    Action/result: Re-read references to [0x0139] and [0x000f].

  1259. Reported: “The main-cycle source explains the failed fixture: after logic returns, the cycle writes global [0x000f] back into object0 +0x21 unconditionally. So a script-set object0 direction after the top-of-cycle mirror is clobbered before the next cycle. I’m reading the neighboring code and references now to document 0x83 precisely rather than forcing a misleading QEMU test.”

    Action/result: Identified the cycle-end global-to-object restore.

  1260. Reported: “At this point the clean result is source-backed, not QEMU-promoted: 0x83 is a selector write whose effect occurs before logic, while our script-level attempts happen after that point and are clobbered by the cycle-end global-to-object copy. I’m going to document that timing explicitly and mark 0x83 covered as source-backed rather than pretending the failed fixture was a useful compatibility test.”

    Action/result: Decided to promote 0x83 as source-backed.

  1261. Reported: “I’m updating the evidence generator so 0x83 is recorded as source-backed with a timing caveat, not as a QEMU smoke. Then I’ll update the opcode prose and progress tracker to reduce the partial list to four.”

    Action/result: Prepared the evidence-generator update.

  1262. Reported: “Since the script-level 0x83 fixture does not prove the selector behavior, I’m removing it from the reusable probe registry rather than leaving a misleading failing case around. The failed attempts will stay documented in the notes as evidence about cycle timing.”

    Action/result: Removed the attempted reusable helper from tools/logic_interpreter_probe.py.

  1263. Reported: “The helper is removed. I’m removing the corresponding registry entry and unit assertions next, then the evidence generator will carry 0x83 as source-backed.”

    Action/result: Removed the attempted base-case entry.

  1264. Reported: “The failing 0x83 registry case is gone. I’m cleaning up the unit test references that were only there to protect that abandoned fixture.”

    Action/result: Removed the temporary unit assertions.

  1265. Reported: “The abandoned fixture is cleaned out. I’m running the case-list unit test once to make sure the registry is back to a consistent shape before changing the docs.”

    Action/result: The case-list unit test passed.

  1266. Reported: “The registry test passes. I’m changing the generated evidence matrix so 0x83 has its own source-backed note about the main-cycle mirror and the failed script-level probe timing.”

    Action/result: Added SOURCE_BACKED_ACTIONS evidence for 0x83.

  1267. Reported: “The generator now treats 0x83 as a source-backed timing primitive rather than a smoke. I’m regenerating the evidence chapter.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  1268. Reported: “The evidence chapter is regenerated. I’m updating PROGRESS.md now: 0x83 is covered as source-backed, leaving 0x6e, 0x8e, 0xaa, and 0xad partial.”

    Action/result: Updated PROGRESS.md to 172/176 covered action opcodes.

  1269. Reported: “The tracker is updated. I’m expanding the opcode prose next, because implementers need the timing rule: 0x83 affects the next cycle’s pre-logic mirror, but logic-script attempts to seed object0 direction after that point are overwritten at cycle end.”

    Action/result: Began updating docs/src/logic_bytecode.md.

  1270. Reported: “The top-level cycle section already mentions the mirror, but it doesn’t say which branch corresponds to [0x0139] == 0 or mention the cycle-end copy. I’m adding that there and in the 0x83 opcode row.”

    Action/result: Added branch direction and cycle-end restore prose.

  1271. Reported: “I found two older smoke summaries that still list opcodes we’ve since promoted or source-backed. I’m cleaning those alongside the 0x83 timing prose so the book stays internally consistent.”

    Action/result: Updated old smoke summaries in the bytecode chapter.

  1272. Reported: “The logic bytecode chapter now carries the exact branch direction and the failed-probe timing reason. I’m updating the compatibility chapter’s old smoke summary to stop claiming 0x83 is only smoke-covered.”

    Action/result: Updated docs/src/compatibility_testing.md.

  1273. Reported: “Compatibility notes are consistent now. I’m adding the same high-level timing rule to the runtime model’s motion/object lifecycle, since that’s where an implementer is likely to look when modeling object0 direction and keyboard/global direction state.”

    Action/result: Added an object0/global direction mirror row to docs/src/runtime_model.md.

  1274. Reported: “The runtime model now has the special mirror row. I’m updating the symbolic label map with stable data labels for [0x0139] and [0x000f], plus a clearer main-cycle note for cross-version comparison.”

    Action/result: Began updating docs/src/symbolic_labels.md.

  1275. Reported: “The label file already has the first-object pointer, so I’m placing the direction mirror globals in that same data section and sharpening the main-cycle description.”

    Action/result: Added data.motion.global_direction_000f and data.motion.direction_mirror_selector_0139.

  1276. Reported: “The symbolic labels now include the two mirror globals. I’m appending a clean-room notes section with the disassembly commands, the failed/diagnostic QEMU attempts, and the conclusion that 0x83 is source-backed because its branch point is outside script control.”

    Action/result: Prepared the clean-room notes section.

  1277. Reported: “The notes currently end at the input-width promotion, so I’m adding a new dated section immediately after it for the 0x83 source pass and diagnostics.”

    Action/result: Appended the 0x83 direction-mirror timing section to docs/src/clean_room_executable_notes.md.

  1278. Reported: “The clean-room notes now capture the 0x83 investigation. I’m catching the progress log up again before moving to the next remaining partial opcode, since this pass had several diagnostic detours worth preserving.”

    Action/result: Prepared to append the 0x83 investigation progress-log entries.

  1279. Reported: “The progress log is caught up through the 0x83 source-backed promotion. I’m moving on to the four remaining partials now; I’ll start with 0xad because it is small, source-local, and likely another timing/global side effect we can either document cleanly or validate with a narrow probe.”

    Action/result: Began the 0xad source pass.

  1280. Reported: “0xad is a tiny handler too: inc byte [0x1530] and return. I’m reading the keyboard-interrupt consumer around 0x6036 because that byte is only useful if we can explain when the nonzero value changes observable behavior.”

    Action/result: Disassembled the action body and keyboard IRQ consumer.

  1281. Reported: “The consumer is clearer now: [0x1530] is a nonzero gate in the keyboard IRQ path. On tracked key release, if the per-key pressed latch was set and [0x1530] is nonzero, the ISR calls the same event enqueue/display-input helper path. I’m checking where the tracked-key table is initialized before turning this into a source-backed spec row.”

    Action/result: Identified the tracked-key release gate behavior.

  1282. Reported: “The only static hits for [0x1530] are the action increment and the keyboard ISR test, so this looks like an enable/count gate rather than ordinary game state. I’m checking the existing event-helper labels so the docs can name the called helper symbolically instead of using a raw address.”

    Action/result: Reused code.input.enqueue_event as the symbolic helper name for the IRQ enqueue call.

  1283. Reported: “I’m treating 0xad as source-backed: the action body and the only observed consumer are both local and compact, while building a QEMU fixture around raw IRQ key-release timing would be brittle. I’m updating the evidence matrix and docs to call it a key-release enqueue gate.”

    Action/result: Decided to promote 0xad as source-backed.

  1284. Reported: “The generator has a specific source-backed entry for 0xad. I’m removing it from the smoke-only set and regenerating the evidence page.”

    Action/result: Added 0xad to source-backed evidence and prepared to remove its smoke classification.

  1285. Reported: “0xad is no longer classified as smoke in the generator. I’m regenerating the matrix now.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  1286. Reported: “The evidence page is regenerated. I’m updating PROGRESS.md so 0xad is covered as source-backed, leaving only 0x6e, 0x8e, and 0xaa partial.”

    Action/result: Updated PROGRESS.md to 173/176 covered action opcodes.

  1287. Reported: “The tracker is updated. I’m expanding the raw event queue section and the 0xad opcode row so the source-backed status has real semantics, not just “increments a byte.””

    Action/result: Updated docs/src/logic_bytecode.md for the IRQ release-event gate.

  1288. Reported: “The bytecode chapter now links 0xad to the IRQ release path. I’m updating the compatibility chapter so the old diagnostics batch is clearly just smoke for 0x8e/0xaa, while 0xad is source-backed.”

    Action/result: Updated docs/src/compatibility_testing.md.

  1289. Reported: “The compatibility chapter is consistent. I’m adding the key-release gate to the runtime model’s input/event lifecycle so implementation readers know 0xad is an input subsystem detail.”

    Action/result: Prepared the runtime-model update.

  1290. Reported: “The event/edit loop row is the right place for this; I’m extending its contract rather than adding another separate section.”

    Action/result: Updated docs/src/runtime_model.md.

  1291. Reported: “The runtime model now mentions the release-event gate. I’m adding symbolic labels for the IRQ hook and its small data tables so future version comparisons can find this feature without relying on raw addresses.”

    Action/result: Prepared symbolic-label additions for the IRQ hook and tables.

  1292. Reported: “The input data table section already has the queue and key-map entries, so the release-tracking tables fit cleanly there. I’m patching both code and data labels together.”

    Action/result: Added code.input.keyboard_irq_hook and related data labels.

  1293. Reported: “The symbol map now names the IRQ hook and its latch/gate tables. I’m appending a short clean-room note for 0xad, including the disassembly evidence and why it stays source-backed instead of QEMU-validated.”

    Action/result: Appended the 0xad key-release enqueue gate section to docs/src/clean_room_executable_notes.md.

  1294. Reported: “The 0xad clean-room note is in place. I’m logging the 0xad work in progress_log.md before moving on to the last three partial opcodes.”

    Action/result: Appended the 0xad progress-log entries.

  1295. Reported: “0xad is logged. I’m moving to 0x8e next, since it sits in the resource-event replay system we already documented; the goal is to tighten its exact capacity/reset behavior from disassembly and see whether existing replay probes already cover the observable contract.”

    Action/result: Began the 0x8e source pass.

  1296. Reported: “0x8e is already well supported by the resource-event source pass: it writes data.event.pair_capacity, wraps code.event.reset_pair_buffer with flush/rebuild helpers, and the reset helper’s capacity/count semantics are documented. I’m going to promote it as source-backed rather than manufacture a replay fixture that would mostly re-test the larger 0x8c replay machinery.”

    Action/result: Decided to promote 0x8e as source-backed.

  1297. Reported: “That patch missed because the smoke table has already collapsed to a shorter shape after the previous edits. I’m re-reading the exact generator block and applying a narrower patch.”

    Action/result: Re-read tools/logic_opcode_evidence.py after a patch-context miss.

  1298. Reported: “The generator block is straightforward. I’m adding 0x8e to the source-backed map and leaving only 0x6e in the smoke map.”

    Action/result: Updated the evidence generator for 0x8e.

  1299. Reported: “0x8e is now source-backed in the generator. I’m regenerating the evidence page and updating the tracker to 174/176.”

    Action/result: Regenerated the logic opcode evidence chapter.

  1300. Reported: “The evidence page is regenerated. I’m updating PROGRESS.md; after this only 0x6e and 0xaa remain partial in the action table.”

    Action/result: Updated PROGRESS.md to 174/176 covered action opcodes.

  1301. Reported: “The tracker now leaves two partial actions. I’m expanding the 0x8e row and cleaning the old smoke paragraph so it no longer treats event capacity reset as merely dispatch-smoked.”

    Action/result: Updated docs/src/logic_bytecode.md.

  1302. Reported: “The bytecode chapter now treats 0x8e as the source-backed event log capacity reset. I’m updating the compatibility chapter’s diagnostics summary to leave only 0xaa as smoke-covered.”

    Action/result: Updated docs/src/compatibility_testing.md.

  1303. Reported: “Compatibility notes now align with the source-backed promotion. I’m checking the runtime model’s resource-event section; it likely already has the necessary capacity/reset contract, but I’ll add a line if it’s missing.”

    Action/result: Reviewed the runtime model resource lifecycle.

  1304. Reported: “The resource lifecycle table mentions replay/recording but not the capacity reset itself. I’m adding a small paragraph after the table so 0x8e is visible in the high-level model.”

    Action/result: Updated docs/src/runtime_model.md.

  1305. Reported: “The runtime model now has a direct 0x8e capacity-reset paragraph. I’m tightening the existing symbolic label note for code.event.set_pair_capacity_action with the same semantics.”

    Action/result: Updated docs/src/symbolic_labels.md.

  1306. Reported: “The symbolic map is updated. I’m adding a short 0x8e clean-room note that ties the handler, reset helper, and existing replay evidence together.”

    Action/result: Appended the 0x8e event-pair capacity reset section to docs/src/clean_room_executable_notes.md.

  1307. Reported: “0x8e is documented. I’m moving to 0xaa, the save-description copy action. There was an earlier failed attempt to force the buffer; I’ll re-read the save selector/source path first so we can decide whether it is cleanly source-backed or worth a new QEMU fixture.”

    Action/result: Began the 0xaa source pass.

  1308. Reported: “The 0xaa source path is compact and the earlier QEMU attempt patched the wrong layer: the action copies from the interpreter’s runtime buffer at DS:0x0e72, not from the fixture’s static AGIDATA bytes. I’m going to promote this as source-backed and document that proper dynamic validation would need to drive the save/restore selector to populate that runtime buffer.”

    Action/result: Decided to promote 0xaa as source-backed.

  1309. Reported: “The generator now has source-backed evidence for 0xaa. I’m regenerating the matrix and updating the tracker; after this, only the screen-shake-like 0x6e remains partial.”

    Action/result: Updated the evidence generator and regenerated the matrix.

  1310. Reported: “The evidence matrix is regenerated. I’m bumping PROGRESS.md to 175/176 covered action opcodes.”

    Action/result: Updated PROGRESS.md to 175/176 covered action opcodes.

  1311. Reported: “The tracker now leaves only 0x6e partial. I’m updating the 0xaa opcode prose and retiring the last old “dispatch-smoke” wording in the diagnostics/system paragraph.”

    Action/result: Updated docs/src/logic_bytecode.md.

  1312. Reported: “The bytecode chapter now explains the static-patch failure correctly. I’m updating the compatibility chapter’s save-description section so it records that failed QEMU attempt as a fixture-shape issue, not an unresolved opcode.”

    Action/result: Updated docs/src/compatibility_testing.md.

  1313. Reported: “The compatibility note now distinguishes static fixture bytes from the runtime buffer. I’m adding a brief runtime-model note under save/restore so 0xaa shows up at the subsystem level too.”

    Action/result: Checked for an existing save/restore runtime section.

  1314. Reported: “The runtime model doesn’t yet have a save/restore subsection, so I’m adding a compact one rather than scattering 0xaa into unrelated text/input state.”

    Action/result: Added a save/restore data model table to docs/src/runtime_model.md.

  1315. Reported: “The runtime model now has a save/restore data model and names the 0xaa contract. I’m adding symbolic labels for the action handler and runtime description buffer.”

    Action/result: Prepared save-description symbolic labels.

  1316. Reported: “The save code labels are grouped already. I’m placing 0xaa there and adding the buffer in the data section near other runtime globals.”

    Action/result: Added code.save.copy_description_to_string_action and data.save.description_buffer.

  1317. Reported: “The symbol map now includes the 0xaa handler and runtime save-description buffer. I’m appending a clean-room note for 0xaa so the failed static-patch QEMU attempt and the source-backed conclusion are both in the evidence trail.”

    Action/result: Appended the 0xaa save-description buffer copy section to docs/src/clean_room_executable_notes.md.

  1318. Reported: “0xaa is now source-backed and logged in the notes. I’m turning to the final partial action, 0x6e, the screen-shake-like handler; I’ll inspect the disassembly first and decide whether a visual QEMU validation is practical or whether the effect is hardware-timing-specific.”

    Action/result: Began the 0x6e source pass.

  1319. Reported: “The first disassembly window caught the neighboring input-line config code, not the shake handler. The opcode table says 0x6e starts around 0x7a00, so I’m reading that exact region next.”

    Action/result: Re-read the exact 0x6e handler region.

  1320. Reported: “0x6e is the last partial and the source is now clear. It is a hardware/display timing effect: for normal EGA-like modes it walks a small byte table and writes CRT controller registers 0x02 and 0x07, waiting one timer tick between offsets; other display modes delegate to overlay helpers. A screenshot-after-return fixture can only prove dispatch, so I’m promoting this as source-backed.”

    Action/result: Decided to promote 0x6e as source-backed.

  1321. Reported: “The generator now treats 0x6e as source-backed with a note that QEMU only dispatch-smoked it. I’m regenerating the evidence matrix and then the opcode table will be complete.”

    Action/result: Updated the evidence generator and regenerated the matrix.

  1322. Reported: “The evidence matrix is regenerated. I’m updating PROGRESS.md to show all 176 action opcodes covered at the current spec target.”

    Action/result: Updated PROGRESS.md to all 176 action opcodes covered.

  1323. Reported: “The action opcode tracker is now complete. I’m checking the remaining-work section to make sure it no longer implies opcode coverage is still the highest-value gap.”

    Action/result: Reviewed PROGRESS.md remaining-work section.

  1324. Reported: “The highest-value list still has the old “promote remaining dispatch-smoke opcodes” item. I’m replacing that with the next useful opcode-adjacent work: hardening source-backed edge cases and moving back into subsystem specs/tests.”

    Action/result: Updated PROGRESS.md highest-value remaining work.

  1325. Reported: “The remaining-work list now reflects that opcode coverage is complete at the current target. I’m expanding the 0x6e row with the exact normal-path register loop and display-mode branch behavior.”

    Action/result: Updated the 0x6e opcode prose.

  1326. Reported: “The opcode row now has the source-level shake loop. I’m updating the text/status compatibility section so it says QEMU smoke only proves return, while the shake semantics are source-backed.”

    Action/result: Updated docs/src/compatibility_testing.md.

  1327. Reported: “The compatibility chapter now classifies 0x6e correctly. I’m adding a small display-hardware note to the runtime model because this opcode is really a hardware timing operation, not a logical surface mutation.”

    Action/result: Prepared the runtime-model display note.

  1328. Reported: “There’s a short miscellaneous action list at the end of the runtime model, so I’m adding 0x6e there with its “transient register effect” contract.”

    Action/result: Updated docs/src/runtime_model.md.

  1329. Reported: “The runtime model now states the portable contract for shake: short display offset animation, no persistent buffer change. I’m adding labels for the shake handler and its offset table/base bytes.”

    Action/result: Prepared symbolic labels for the shake path.

  1330. Reported: “The display labels are grouped near the graphics helpers and display globals. I’m adding the 0x6e action label there, plus the three data globals/table it uses in the normal CRT path.”

    Action/result: Added code.display.shake_screen_action and related display data labels.

  1331. Reported: “The symbol map now has the shake action and data labels. I’m adding the final clean-room note for 0x6e, then I’ll run the verification suite.”

    Action/result: Appended the 0x6e screen-shake source pass section to docs/src/clean_room_executable_notes.md.

  1332. Reported: “I’ve picked the thread back up at the verification stage. First I’m going to finish checking the opcode-coverage edits, then I’ll roll straight into the next PROGRESS.md item instead of waiting for another nudge.”

    Action/result: Resumed from the opcode-coverage verification pass.

  1333. Reported: “mdbook build docs is clean. I’m doing the last mechanical checks now, including whitespace/diff hygiene and a quick scan for stale “partial opcode” wording before moving on.”

    Action/result: Confirmed mdbook build docs succeeds and started final hygiene checks.

  1334. Reported: “The verification pass surfaced only expected historical mentions of earlier “173/174/175” milestones, not current stale status. I also see two probe/test files modified, so I’m going to inspect those before deciding whether they are intentional work to carry forward or leftovers from the opcode push.”

    Action/result: Inspected the probe and unit-test diffs.

  1335. Reported: “I’m going to preserve these updates in the historical progress log now. The extra probe edits look aligned with the next PROGRESS.md item, namely text/input UI semantics, so I’ll treat them as in-flight work to validate and document rather than discard.”

    Action/result: Began documenting the verification handoff and selected text/input UI semantics as the next workstream.

  1336. Reported: “Text/input coverage is more advanced than the high-level tracker implies: the newer batches all report match with zero mismatches. I’m going to tighten the tracker around that and then use the next gap in the same area, menu navigation, as the next source-first target.”

    Action/result: Checked recorded QEMU batch outputs for the text/input cases and selected menu navigation as the next gap.

  1337. Reported: “There is already a menu label cluster and a couple of QEMU probes, but the “remaining” risk is navigation behavior. I’m rereading the menu interaction routine at 0x93d1 and the event normalizer paths around 0x4529..0x46e8 so the next test cases come from the control flow rather than guesswork.”

    Action/result: Re-read code.menu.interact and the shared input/event helpers from disassembly.

  1338. Reported: “The navigation table is clear in the disassembly: movement values 1..8 jump to eight small branches at 0x9492, 0x94a6, 0x94b2, 0x94cb, 0x94da, 0x94e5, 0x94f6, and 0x9509. I’m going to inspect the menu node construction now so I can name those branches correctly in terms of next/previous item and heading movement.”

    Action/result: Decoded the menu movement dispatch table and inspected menu node construction.

  1339. Reported: “The menu construction confirms both heading and item lists are circular doubly-linked lists: offset +0 is next and +2 is previous; headings point at their item list at +0c, and item enable/selection id live at +0a/+0c. I’m going to add a compact source-backed navigation table to the docs and tracker, then consider whether a non-keyboard event-injection probe is worth adding.”

    Action/result: Prepared source-backed menu navigation documentation.

  1340. Reported: “I’m making a source-backed documentation pass now: correcting the old “enabled item navigation” wording, adding the exact movement-code table, and recording the branch targets as symbolic labels so this survives future interpreter-version comparisons.”

    Action/result: Added the menu navigation source table and updated symbolic labels, runtime model, compatibility notes, clean-room notes, and PROGRESS.md.

  1341. Reported: “That patch didn’t apply because the clean-room note heading text has drifted slightly. No files were changed by that failed patch; I’m locating the exact anchors and I’ll apply it in smaller chunks.”

    Action/result: Located the exact clean-room and progress-log anchors.

  1342. Reported: “I found the exact anchors. I’m applying the menu-navigation docs in smaller patches now so each change has a clean landing point.”

    Action/result: Applied the logic-bytecode menu navigation table.

  1343. Reported: “The logic bytecode chapter now has the exact movement table and the corrected disabled-item semantics. I’m carrying the same model into the runtime overview and symbolic label map.”

    Action/result: Updated the runtime model and symbolic label map.

  1344. Reported: “The runtime model now has a portable menu state description, and the symbol map has separate root/current pointers plus the navigation jump table. Next I’m updating the tracker and compatibility notes so the remaining gap is accurately framed as dynamic movement-event validation, not unknown semantics.”

    Action/result: Updated PROGRESS.md and compatibility notes.

  1345. Reported: “The tracker now says menu navigation is source-backed and names the remaining validation gap precisely. I’m adding the clean-room evidence note and progress-log entries for this source pass.”

    Action/result: Appended the menu-navigation clean-room section and this progress-log catch-up.

  1346. Reported: “The menu source pass is documented. I’m going to run the quick verification checks now, then I’ll move into the next PROGRESS.md item: picture/view renderer compatibility, using the existing renderer and QEMU captures as confirmation tools.”

    Action/result: Ran mdBook, diff whitespace, and full unittest checks.

  1347. Reported: “Verification is clean: mdBook builds, whitespace checks pass, and the full unittest suite still runs green. I’m switching into the picture/view renderer area now and reading the current renderer/test coverage before picking a narrow edge case.”

    Action/result: Began reviewing renderer, view-batch, and graphics tests.

  1348. Reported: “The graphics test file I guessed at doesn’t exist; the renderer coverage is split across other test modules. I’m locating the actual graphics tests and then I’ll pick an edge case that lines up with both the disassembly and the current harness.”

    Action/result: Located tests/test_graphics_rendering.py and related overlay/movement tests.

  1349. Reported: “The renderer surface is clearer now: picture tests live in tests/test_graphics_rendering.py, while object-overlay and movement probes exercise composed view cels. I’m reading the remaining edge docs around transparent/mirroring/clipping to choose a useful, source-backed next probe.”

    Action/result: Reviewed graphics object-pipeline notes and object overlay case coverage.

  1350. Reported: “A good next edge is the object placement/search helper around 0x593a, especially the weird right-edge result that currently lives as an observation. I’m going to read that disassembly before adding any new QEMU probes, because the existing behavior probably explains itself in the placement code.”

    Action/result: Disassembled code.object.place and code.object.control_acceptance.

  1351. Reported: “The placement helper explains the odd right-edge capture: 0x593a first tries the requested position, then does a spiral search with growing segment lengths: left 1, down 1, right 2, up 2, left 3, down 3, and so on. I’m checking the current prose because it may already have part of this but not the full search contract.”

    Action/result: Found the existing placement prose and identified the missing exact spiral contract.

  1352. Reported: “I’m going to codify the spiral search locally rather than keep right-edge placement as a magic expected coordinate. First I’m checking the derived sequence against the known QEMU result; if it lands on (140,67), that gives us a much better compatibility oracle.”

    Action/result: Simulated the source sequence and confirmed it reaches (140, 67) for the known right-edge case.

  1353. Reported: “The derived spiral lands exactly on the known right-edge QEMU placement (140,67), which is a nice little moment. I’m going to turn that into a reusable helper and unit tests, then update the object-overlay comparison to use the source-derived placement instead of hard-coded coordinates where possible.”

    Action/result: Prepared the placement-search helper and tests.

  1354. Reported: “I’m editing the local graphics model now: adding a 0x593a-style placement search helper, switching the overlay comparison to use it for ordinary bounds adjustment, and replacing the old hard-coded edge-position tests with source-derived expectations.”

    Action/result: Added search_object_placement() and updated overlay comparisons/tests.

  1355. Reported: “The placement helper is in place and the edge cases now derive their expected positions from that source model. I’m running the focused graphics/object-overlay tests before I touch the docs.”

    Action/result: Ran focused graphics, object-overlay, and view-batch tests successfully.

  1356. Reported: “The focused Python tests pass. Before running a fresh emulator batch, I’m checking the existing QEMU captures for the top/right edge cases against the new source-derived placement model; that gives a quick original-engine sanity check without burning another boot.”

    Action/result: Existing top/right QEMU captures matched the new source-derived placement model with 0 mismatches.

  1357. Reported: “The existing top/right QEMU captures both match with the new source-derived placement helper. I’m documenting the exact spiral search now and tightening the compatibility notes so future work treats this as a modeled routine, not a one-off captured coordinate.”

    Action/result: Began updating the object-placement documentation.

  1358. Reported: “The docs anchor moved again in that paragraph, so I’m doing the same smaller-patch treatment here. No changes landed from that failed patch.”

    Action/result: Located exact placement documentation anchors.

  1359. Reported: “I’ve got the exact paragraphs. I’m applying the placement-search documentation separately now.”

    Action/result: Added the exact 0x593a spiral and acceptance tests to the graphics object-pipeline chapter.

  1360. Reported: “The placement section now states the exact spiral and acceptance tests. I’m replacing the older “weird right edge” prose with the source-derived explanation.”

    Action/result: Rewrote the top/right edge placement explanation.

  1361. Reported: “The object-pipeline chapter now treats the top/right placements as outputs of the 0x593a spiral. I’m updating the compatibility and tracker notes, plus the symbol map, to match the new model.”

    Action/result: Updated compatibility notes, symbolic labels, and PROGRESS.md.

  1362. Reported: “The compatibility/tracker docs are aligned. I’m adding the clean-room evidence entry for the placement-source pass and logging the recent work updates.”

    Action/result: Appended the object-placement source-pass notes and this progress-log catch-up.

  1363. Reported: “Full verification is clean: 109 tests now pass, opcode evidence check passes, mdBook builds, and diff whitespace is clean. I’ll continue with renderer compatibility; next I’m looking for picture-fuzz or view-corpus gaps that are still only lightly covered.”

    Action/result: Verified the full local suite, opcode-evidence check, mdBook build, and whitespace check, then moved to renderer-compatibility gaps.

  1364. Reported: “The next picture-side risk the docs call out is seed-fill traversal: the local renderer uses a queue, while the original appears to have its own stack/span helper. I’m going to inspect the seed-fill routine around 0x533b and see whether the observable contract can be made more implementation-ready without overfitting internals.”

    Action/result: Began source-first inspection of picture seed fill around code.picture.seed_fill (0x533b).

  1365. Reported: “I’m picking up at the seed-fill source pass. I’ll first write the two recent progress updates into the log, then tighten the docs around the span-stack fill behavior before deciding whether new compatibility cases are worth adding.”

    Action/result: Resumed from the seed-fill source pass and checked current tracker/docs state.

  1366. Reported: “The repo is in the expected in-progress state, with the object placement pass already documented and tested. I’ve found the outstanding picture-renderer note: seed fill is documented as span-based, but the exact source-derived contract can be sharper.”

    Action/result: Identified seed-fill traversal documentation as the next high-value picture-renderer refinement.

  1367. Reported: “I’m going back to the actual 0x533b disassembly rather than just polishing prose from memory. The goal is to describe what the original routine commits to observably, and keep implementation guidance portable.”

    Action/result: Re-read the seed-fill helper disassembly and the current picture-renderer compatibility notes.

  1368. Reported: “The seed-fill routine confirms the docs’ broad statement, and adds useful specifics: it chooses exactly one expansion channel, rejects no-op fills before scanning, writes spans with the normal odd/even masks, and uses stack-pushed span state for branches. I’m turning that into a clearer spec paragraph now.”

    Action/result: Prepared a source-backed seed-fill contract update for the graphics pipeline documentation.

  1369. Reported: “I’m going to keep the seed-fill labels conservative: the scratch bytes around 0x126c clearly form span state, but several fields change roles during branch handling. A single “span scratch block” label is more honest than naming every byte too confidently.”

    Action/result: Reviewed existing picture symbolic labels and chose a conservative scratch-block label strategy for seed-fill state.

  1370. Reported: “There’s already an older seed-fill refinement note, so I’m not going to pretend this is a first discovery. This pass will add the missing traversal details and separate “observable behavior” from “how SQ2’s executable happens to organize its stack state.””

    Action/result: Compared the older seed-fill notes with the new disassembly pass to avoid duplicating or overstating evidence.

  1371. Reported: “PROGRESS.md points next at picture/view compatibility and then implementation-ready subsystem state machines. I’ll finish the seed-fill spec cleanup as part of that renderer work, then use the tracker to choose the next concrete gap rather than wandering.”

    Action/result: Re-read the high-value tracker section and kept the next work item aligned to renderer compatibility.

  1372. Reported: “I’m editing the picture pipeline chapter now. The important distinction is: original traversal order is source-backed, but clean implementations can use a different flood-fill algorithm if they preserve target selection and pixel-write semantics on valid resources.”

    Action/result: Updated the 0xf8 seed-fill section with explicit channel selection, early exits, span-stack traversal, and portable final-buffer semantics.

  1373. Reported: “The main seed-fill chapter now separates channel selection, no-op rejection, span traversal, and the portable observable contract. I’m adding the scratch-block label and evidence note so future cross-version work has something stable to map.”

    Action/result: Added data.picture.seed_fill_span_scratch to the symbolic label map.

  1374. Reported: “The compatibility notes still say the traversal itself is an open risk. I’m narrowing that: traversal class is now source-backed; what still needs broader proof is edge-case parity across barriers, masks, and multiple seeds.”

    Action/result: Reframed the seed-fill compatibility risk around expanded parity coverage instead of unknown traversal class.

  1375. Reported: “I’m appending the clean-room evidence entry for this seed-fill pass, including the commands that were useful and the small correction to the previous “queue versus span” uncertainty.”

    Action/result: Appended the picture seed-fill span-stack source-pass note to the clean-room evidence log.

  1376. Reported: “The evidence note will also capture a minor tooling lesson: one of the broad disassembly windows was noisy, but the useful evidence is still the focused helper body and the existing renderer tests.”

    Action/result: Recorded the noisy broad disassembly as limited evidence and documented the source-backed result.

  1377. Reported: “I’m updating PROGRESS.md now so the tracker reflects the narrower seed-fill status: source-backed traversal, still needing broader parity cases. That should make the next renderer step clearer.”

    Action/result: Updated the picture-decoding tracker with source-backed seed-fill span traversal and the remaining coverage targets.

  1378. Reported: “The tracker currently treats picture decoding as one broad bucket. I’ll add the seed-fill evidence there without marking the whole renderer done; the remaining work is still real, just better bounded.”

    Action/result: Kept picture decoding at partial status while adding the refined seed-fill evidence.

  1379. Reported: “I noticed one wording detail worth tightening before moving on: the control fill value lives as high-nibble 4, which appears as 0x40 in the buffer byte. I’m making that explicit so a future implementation doesn’t compare the wrong byte value.”

    Action/result: Clarified the control-channel seed-fill target and no-op value as high-nibble 4 / buffer byte 0x40.

  1380. Reported: “The seed-fill docs and tracker now agree: traversal is source-backed, final-buffer equivalence remains the portable contract, and the remaining risk is coverage depth. I’m going to run the lightweight checks for these doc-only changes before choosing the next PROGRESS.md task.”

    Action/result: Started verification for the seed-fill documentation pass.

  1381. Reported: “The doc build and whitespace check are clean after the seed-fill update. Next I’m going to turn that remaining seed-fill coverage list into concrete valid synthetic picture cases, then decide whether a small original-engine QEMU batch is worthwhile.”

    Action/result: Verified the seed-fill documentation changes and began adding curated picture fuzz cases.

  1382. Reported: “The fuzz base list already has two seed-fill cases, but they don’t cover multiple seeds or a narrow barrier shape. I’m reading the local renderer shape helpers before adding cases, so the fixtures stay valid and intentional.”

    Action/result: Reviewed picture renderer command handling and existing seed-fill tests before adding new valid cases.

  1383. Reported: “The local renderer’s write_cell() already models the odd/even row mask path, so a good seed-fill case can exercise that without new renderer machinery. I’m adding a few curated safe base cases rather than relying on random generation to stumble into them.”

    Action/result: Chose curated seed-fill fixture shapes for deterministic fuzz coverage.

  1384. Reported: “For QEMU screenshots, visual-channel cases are the most useful because the batch comparator sees the rendered EGA image. I’ll still include one control-channel case in local tests, but the curated QEMU candidates should mostly produce visible differences.”

    Action/result: Balanced visible QEMU cases with one local control-channel semantic test.

  1385. Reported: “I’m making three small curated fixtures: a full-height one-pixel visual barrier, two separate boxes filled by one multi-seed command, and a control-channel fill that crosses a visual-only barrier. They each target a specific seed-fill edge without malformed data.”

    Action/result: Added base_021, base_022, and base_023 to tools/picture_fuzz.py.

  1386. Reported: “The new base cases are in the fuzz catalog. I’m adding direct renderer assertions next, because these cases encode semantics that are clearer to test as cell values than as screenshot hashes alone.”

    Action/result: Added local renderer tests for the full-height barrier, multi-seed boxes, and control-channel barrier-crossing case.

  1387. Reported: “The local semantic tests are in place. I’m running the renderer and fuzz-tool tests now; if they pass, I’ll regenerate a small corpus and consider a targeted QEMU batch for the visible fill cases.”

    Action/result: Ran targeted renderer/fuzz tests and generated a small corpus containing the new curated cases.

  1388. Reported: “The new tests pass, and a small regenerated corpus reports 24 curated base cases plus randoms. I’m checking the batch command path before running QEMU so I use the snapshot harness correctly and don’t waste a boot cycle.”

    Action/result: Confirmed the new cases render locally and are marked safe for QEMU.

  1389. Reported: “I’m going to run the three new safe seed-fill fixtures through the original engine using the snapshot batch harness. The first two should validate visible fill geometry; the third is mostly a guard that control-only fill does not disturb the visible barrier.”

    Action/result: Started a QEMU snapshot batch for base_021, base_022, and base_023.

  1390. Reported: “The snapshot batch built its disk, then QEMU was blocked by the sandbox from binding the local VNC socket. That’s a permissions issue rather than a renderer result, so I’m rerunning the same harness with escalation.”

    Action/result: Re-ran the same seed-fill QEMU batch with permission to bind the local VNC socket.

  1391. Reported: “The escalated QEMU batch is running now from the snapshot disk. I’ll wait for the JSON summary, then fold the result into the compatibility docs and tracker.”

    Action/result: Waited for the original-engine seed-fill batch to finish.

  1392. Reported: “The original-engine batch matched all three new seed-fill cases with 0 mismatches. That gives us QEMU-backed coverage for the full-height barrier, multi-seed fill, and the visible side of the control-channel barrier case.”

    Action/result: Captured the successful QEMU report at build/picture-fuzz/batches/seed_fill_edges_001.json.

  1393. Reported: “I’m updating the compatibility chapter with the new corpus count and the seed-fill batch result. I’ll be careful to say the control-only case’s screenshot validates visible stability, while the local cell test validates the control-channel crossing behavior.”

    Action/result: Began documenting the new seed-fill fuzz cases and QEMU results.

  1394. Reported: “The docs currently say there are 21 curated base cases and that QEMU coverage stops at base_020. I’m bumping that to 24 and adding the seed_fill_edges_001 batch as the new evidence point.”

    Action/result: Updated the compatibility chapter with the new curated case count and QEMU seed-fill batch result.

  1395. Reported: “I’m also revising the “remaining risk” paragraph so it doesn’t keep asking for cases we just added. Multi-seed and narrow-barrier coverage are now present; broader mask and pattern interactions still remain.”

    Action/result: Narrowed the remaining picture-renderer risk to broader mask, pattern, and interleaved operation coverage.

  1396. Reported: “I’m adding the clean-room note for the new fuzz cases now, including the failed non-escalated QEMU attempt as a permissions artifact and the successful escalated run as the evidence.”

    Action/result: Appended the seed-fill fuzz expansion note to docs/src/clean_room_executable_notes.md.

  1397. Reported: “PROGRESS.md needs the same refinement: seed-fill now has specific QEMU-backed edge fixtures, while broader pattern/fill combinations remain open. I’m updating that tracker line next.”

    Action/result: Updated the picture-decoding tracker with the new QEMU-backed seed-fill edge fixtures.

  1398. Reported: “The tracker is updated. I’m regenerating the standard 1,024-random corpus once so the documented counts are backed by the actual command, then I’ll run the local suite/checks.”

    Action/result: Regenerated the standard picture fuzz corpus and reran targeted renderer/fuzz tests.

  1399. Reported: “The standard corpus command now confirms the documented numbers: 1,048 total cases and 1,046 safe for QEMU. The targeted renderer/fuzz tests still pass after adding the three curated cases.”

    Action/result: Confirmed the documented fuzz corpus counts and targeted test pass.

  1400. Reported: “I’ve folded the verified standard corpus count into the evidence note. Next I’ll run the broader project checks, then continue to the next renderer gap if everything is clean.”

    Action/result: Updated the clean-room evidence note with the verified standard corpus generation command.

  1401. Reported: “I’m running the usual verification set now: full unit tests, opcode-evidence consistency, mdBook build, and diff whitespace. After that I’ll continue with the next PROGRESS.md item instead of stopping.”

    Action/result: Ran the full verification set after the seed-fill fuzz expansion.

  1402. Reported: “Verification is clean: 112 tests pass, opcode evidence is consistent, mdBook builds, and diff whitespace is clean. I’m moving to the next renderer gap in PROGRESS.md: view/cel drawing coverage, especially mirroring and transparent-color edges.”

    Action/result: Began the view/cel drawing coverage pass.

  1403. Reported: “The view docs already mention more transparent-color samples than the tracker summary remembered, so I’m checking the actual coverage before adding duplicate cases. The next useful step looks like selecting a few largest or unusual cels from the local corpus and validating them through the existing view batch harness.”

    Action/result: Scanned existing view docs, tests, and the local view corpus for large, mirrored, and transparent-color candidate cels.

  1404. Reported: “The corpus scan gives good candidates: large cels with transparent colors not yet emphasized by the small default view batch, plus a tall 129-row cel. I’m checking the view-batch tests before deciding whether to expand the built-in batch or keep these as a separate stress list.”

    Action/result: Reviewed tests/test_view_batch.py and candidate cel dimensions/control bytes.

  1405. Reported: “I’m going to add an optional stress suite to view_batch.py rather than making every default run heavier. It will be reusable for compatibility work while the default six-case smoke stays quick.”

    Action/result: Chose an optional stress-suite design for view validation.

  1406. Reported: “The stress suite will deliberately cover transparent colors 0,1,2,5,6,7,8,10,13,14,15 and a bit-0x80 transparent-10 frame. That complements the existing smaller probe set without changing the default batch.”

    Action/result: Selected eleven stress cases from the local SQ2 view corpus.

  1407. Reported: “The stress cases are defined. I’m wiring them into the CLI as --include-stress, so scripted runs can opt in without breaking quick smoke runs or JSON case loading.”

    Action/result: Added stress_cases() and --include-stress to tools/view_batch.py.

  1408. Reported: “The CLI flag is in. I’m extending the view-batch unit tests to lock down that the stress suite stays optional and actually includes the broader transparent-color coverage.”

    Action/result: Added unit coverage for optional stress-case loading.

  1409. Reported: “The optional stress suite is covered by tests. I’m running the view-batch unit tests and a quick local frame render check for those stress cases before involving QEMU.”

    Action/result: Verified stress cases render locally and fit on screen; targeted view tests passed.

  1410. Reported: “The stress suite renders locally and all placements fit the screen. I’m running the base plus stress view batch through QEMU snapshot mode now; this is the compatibility check that matters for the new cases.”

    Action/result: Started the 17-case original-engine view stress batch.

  1411. Reported: “The 17-case view stress batch is running from a single QEMU snapshot now. It includes the six existing smoke cases plus eleven larger/transparent/mirrored cels.”

    Action/result: Waited for the view stress QEMU batch to complete.

  1412. Reported: “The view batch is still running; no result lines yet, which is normal while the snapshot harness drives each DOS fixture and captures screens. I’m waiting it out rather than interrupting the emulator.”

    Action/result: Continued waiting for QEMU snapshot results.

  1413. Reported: “Still waiting on QEMU. This longer run is expected for 17 cases, but it should return a compact match/mismatch summary once the harness finishes.”

    Action/result: Continued waiting for QEMU snapshot results.

  1414. Reported: “The emulator batch is taking its time but is still alive. I’m letting it continue, since stopping here would throw away the original-engine evidence we’re trying to collect.”

    Action/result: Continued waiting for QEMU snapshot results.

  1415. Reported: “Still no output from the harness, which means it’s inside the snapshot-run loop rather than comparison/report writing. I’ll give it another interval before deciding whether to inspect for a stuck QEMU process.”

    Action/result: Continued waiting; the batch later returned result lines.

  1416. Reported: “Good, results are coming back: the first 13 view cases all match, including several large transparent-color samples and the tall 129-row cel. I’m waiting for the last four and the JSON summary.”

    Action/result: Observed the first 13 view stress cases matching QEMU.

  1417. Reported: “The full 17-case view batch matched with 0 mismatches and 0 errors. I’m reading the report, then I’ll document the optional stress suite and update the view coverage tracker.”

    Action/result: Completed QEMU view stress validation with all cases matching.

  1418. Reported: “The QEMU report confirms every stress case matched. I noticed the new test import line is a bit ungainly, so I’m tidying that before documenting the result.”

    Action/result: Read view_stress_001.json and formatted the test import cleanly.

  1419. Reported: “I’m updating the docs in two places: command examples for the optional stress suite, and the compatibility evidence list showing the 17-case view_stress_001 result.”

    Action/result: Updated the compatibility-testing chapter with the stress command and QEMU result.

  1420. Reported: “The compatibility chapter now has the stress command and result. I’m adding a shorter note in the graphics pipeline chapter near the view layout section so readers see that the row/transparent model has broader QEMU coverage now.”

    Action/result: Added the view stress result to the graphics object pipeline chapter.

  1421. Reported: “I’m appending the clean-room evidence for the view stress batch, including the corpus scan that selected the cases and the QEMU report path.”

    Action/result: Appended the optional view stress batch expansion note to the clean-room executable notes.

  1422. Reported: “The evidence note is in. I’m updating PROGRESS.md so the view area no longer lists broad transparent-color coverage as an untouched gap; the remaining view work is more about clipping, priority/control combinations, and animation state.”

    Action/result: Updated the view coverage tracker in PROGRESS.md.

  1423. Reported: “I’m catching the progress log up for the view stress work before running another verification pass. The log is long, but it’s doing its job as the clean-room trail.”

    Action/result: Recorded the view stress work in the progress log.

  1424. Reported: “The progress log is caught up. I’m running the full checks again after the view-batch code/docs changes, then I’ll keep moving to the next tracker item if it’s clean.”

    Action/result: Ran the full verification set after the view stress pass.

  1425. Reported: “Verification is clean again: 113 tests pass, opcode evidence is consistent, mdBook builds, and whitespace is clean. I’m checking PROGRESS.md for the next highest-value gap now; likely candidates are right/bottom clipping or interleaved picture line/fill/pattern cases.”

    Action/result: Re-read PROGRESS.md and selected picture pattern/ interleaving coverage as the next renderer task.

  1426. Reported: “PROGRESS.md now points me back to picture renderer coverage: interleaved line/fill/pattern streams and broader pattern modes. I’m going to add a few valid curated pattern cases next and run them through the same fuzz/QEMU path.”

    Action/result: Began adding pattern-focused curated picture fuzz cases.

  1427. Reported: “The existing pattern source notes already cover bits 0x10 and 0x20, but the curated QEMU cases don’t isolate 0x10 or mix fill/line/pattern in one stream. I’m adding those as safe base cases.”

    Action/result: Reviewed pattern source notes and existing fuzz coverage before adding new cases.

  1428. Reported: “I’ll add three pattern cases: 0x10 mask bypass alone, an interleaved rectangle-fill-line-pattern stream, and a combined 0x10|0x20 pseudo-random pattern sequence. The local tests will assert the first two at the pixel level.”

    Action/result: Added base_024, base_025, and base_026 to tools/picture_fuzz.py.

  1429. Reported: “The new pattern cases are in the fuzz catalog. I’m adding renderer assertions for mask-bypass geometry and the interleaved operation order so these aren’t just anonymous hashes.”

    Action/result: Added local pixel-level tests for pattern mask-bypass and interleaved operation ordering.

  1430. Reported: “The pixel-level pattern tests are in. I’m running the targeted renderer/fuzz tests and regenerating the standard corpus so the base-case counts stay truthful.”

    Action/result: Targeted renderer/fuzz tests passed and the standard corpus regenerated with 1,051 cases, 1,049 safe for QEMU.

  1431. Reported: “The local pattern tests pass, and the standard corpus now reports 1,051 total cases with 1,049 safe for QEMU. I’m running the three new pattern fixtures through the original engine next.”

    Action/result: Started the pattern_interleaved_001 QEMU snapshot batch.

  1432. Reported: “The pattern QEMU batch is running from the snapshot disk. These cases should tell us whether the local renderer’s mask-bypass and mixed operation ordering match the original engine.”

    Action/result: Waited for the original-engine pattern batch to finish.

  1433. Reported: “The original-engine pattern batch matched all three new cases with 0 mismatches. I’m documenting the result and updating the remaining-picture-work wording so interleaved line/fill/pattern coverage is no longer listed as missing.”

    Action/result: Read pattern_interleaved_001.json and began documenting the successful QEMU result.

  1434. Reported: “The compatibility chapter still has the pre-pattern counts. I’m bumping it to 27 curated base cases, adding pattern_interleaved_001, and narrowing the remaining picture risk again.”

    Action/result: Updated compatibility-testing counts and pattern QEMU evidence.

  1435. Reported: “I’m appending the clean-room evidence entry for the pattern additions now, including the source-note check, local tests, corpus regeneration, and QEMU report.”

    Action/result: Appended the pattern/interleaved picture fuzz expansion note.

  1436. Reported: “The pattern evidence note is in. I’m updating the picture tracker so the remaining renderer work focuses on odd/even-mask variation and broader real-resource parity rather than the interleaved cases we just validated.”

    Action/result: Updated PROGRESS.md for the new pattern/interleaving coverage.

  1437. Reported: “I’m catching the progress log up for the pattern-fuzz pass, then I’ll run the full verification set again. After that, I’ll keep going into the next remaining gap.”

    Action/result: Recorded the pattern-fuzz work in the progress log.

  1438. Reported: “The progress log is current again. I’m running the full check set after the pattern-fuzz changes: unit tests, opcode evidence, mdBook, and whitespace.”

    Action/result: Ran the full verification set after pattern-fuzz changes.

  1439. Reported: “Verification is clean: 115 tests pass, opcode evidence is consistent, mdBook builds, and whitespace is clean. I’m moving next to the object placement/search gap, specifically what happens when collision or control rejection makes the spiral continue past the first bounds-acceptable spot.”

    Action/result: Began a source-first pass on placement-search rejection predicates.

  1440. Reported: “My first disassembly window landed on the familiar MZ-offset trap, so I’m rerunning the helper slices with the corrected file skip. I’ll use the existing docs as guardrails and only refine what the source clearly supports.”

    Action/result: Re-ran 0x4719 and 0x56b8 disassembly with corrected file offsets.

  1441. Reported: “The corrected source slices confirm the caller contract already in the docs: placement tests bounds, object-object collision, then control acceptance at every spiral candidate. I’m going to make the local placement helper’s accept hook explicit and tested, so collision/control rejection can be modeled without hard-coding only bounds cases.”

    Action/result: Prepared a local placement-helper clarification and test.

  1442. Reported: “I’m editing the helper and tests now: the source-backed spiral remains the same, but the test will prove an external collision/control predicate can reject early candidates and force the same spiral onward.”

    Action/result: Began adding placement-helper documentation and coverage.

  1443. Reported: “The helper already had the hook; this is mostly documentation and coverage, not new behavior. That’s the right kind of small change for a source-confirmed routine.”

    Action/result: Added a docstring to search_object_placement().

  1444. Reported: “The helper docstring is in. I’m adding a regression test that rejects the first few otherwise-valid candidates and verifies the next accepted point follows the exact source spiral order.”

    Action/result: Added the placement accept-hook regression test.

  1445. Reported: “The accept-hook test now pins the first rejected-candidate sequence: (20,80), (19,80), (19,81), (20,81), then (21,81). I’m reflecting that in the placement docs.”

    Action/result: Updated the graphics pipeline placement documentation.

  1446. Reported: “I’m recording this as a small source-model refinement, not as new original-engine dynamic evidence: the source shows the later predicates are applied at each candidate, and the local helper now exposes/tests that structure.”

    Action/result: Appended the placement-search predicate-hook evidence note.

  1447. Reported: “The evidence note is in. I’m updating the object-pipeline tracker to say the local placement model now has a tested predicate hook, while full object-record collision/control fixtures remain future work.”

    Action/result: Updated PROGRESS.md for the placement-search predicate hook.

  1448. Reported: “I’m checking the symbol map before verification; if the collision helper already has a label, I’ll leave it alone, and if not I’ll add one so future version comparisons can map both placement predicates.”

    Action/result: Found the control helper label but no collision helper label.

  1449. Reported: “The control helper has a label, but the object-object collision helper doesn’t. I’m adding code.object.collision_test at 0x4719 so the placement predicate pair is complete in the symbol map.”

    Action/result: Added code.object.collision_test to the symbolic label map.

  1450. Reported: “The symbol map now has both placement predicates named. I’m adding that to the evidence note, then I’ll run the targeted and full checks.”

    Action/result: Updated the placement evidence note with the new symbolic label.

  1451. Reported: “I’m running the focused graphics tests first for the placement-hook change. If those pass, I’ll run the full project checks again.”

    Action/result: Ran focused graphics tests for placement changes.

  1452. Reported: “The focused graphics tests pass, including the new placement predicate hook case. I’m running the full verification set now, then I’ll keep going from the tracker.”

    Action/result: Ran the full verification set after the placement-hook change.

  1453. Reported: “Full verification is clean again: 116 tests pass, opcode evidence is consistent, mdBook builds, and whitespace is clean. I’m logging the placement-hook work, then I’ll move to the next highest-value non-graphics gap: text/input UI semantics.”

    Action/result: Confirmed the full suite and prepared to move to text/input UI semantics.

  1454. Reported: “I’m shifting into the text/input UI area now. First I’m auditing what is already QEMU-backed versus what is only dispatch-smoked so I don’t spend time duplicating evidence.”

    Action/result: Began auditing text/input coverage against the tracker and existing probes.

  1455. Reported: “0xa9’s active saved-window path may not be reachable cleanly from the existing simple fixtures, but the source check is still useful: it tells us exactly which state bit gates the restore call and which globals are cleared afterward.”

    Action/result: Re-read action 0xa9 and identified the saved-window restore gate and cleanup writes.

  1456. Reported: “The remaining text/input gap is narrower than PROGRESS.md says. Most of the input-line, prompt, mapped-key, raw-key, status, and width-mode behavior already has focused QEMU coverage; I’m updating the tracker to name the real gap instead of the stale broad one.”

    Action/result: Narrowed the PROGRESS.md text/input remaining-work entry.

  1457. Reported: “I’m adding a short evidence note for this text/input audit so the tracker change has a source trail. After that I’ll pick the next partial subsystem rather than overfitting a brittle 0xa9 fixture.”

    Action/result: Added the text/input tracker audit note.

  1458. Reported: “With the text/input tracker narrowed, I’m moving to another partially covered subsystem from PROGRESS.md: sound. I’ll start from the load/start/stop source that the current QEMU probes already touch, then see how much of the resource format falls directly out of the code.”

    Action/result: Started the source-first sound-resource pass.

  1459. Reported: “The first sound disassembly window was actually joystick/picture code, so I corrected the address window and landed on the sound cache lookup/load/start/stop cluster. The loader is doing something pleasantly concrete: reading four little-endian offsets from the payload into the cache record.”

    Action/result: Corrected the sound disassembly window and identified the four-channel offset setup.

  1460. Reported: “The sound loader source is clear on the resource container, and a quick SQ2 corpus scan says all present sound resources follow the same four-offset header shape. I’m reading the driver-start/tick routines next to see what each channel stream record contains.”

    Action/result: Scanned SQ2 sound resources and moved to the playback start/tick source.

  1461. Reported: “I’m going to add a small sound-resource parser, but only for the source-confirmed container/event fields: four channel offsets, then duration/tone/control records ending in 0xffff. Pitch and timing can stay provisional until the driver code is better understood.”

    Action/result: Added tools/agi_sound.py.

  1462. Reported: “The parser is added. I’m adding tests that scan every present SQ2 sound resource so the format claim is pinned to the local corpus, not just sound 1.”

    Action/result: Added tests/test_sound_resources.py.

  1463. Reported: “The sound tests are in. I’m running them now, then I’ll update the subsystem docs and tracker if the corpus scan stays clean.”

    Action/result: Ran targeted sound-resource tests.

  1464. Reported: “The sound parser tests pass across all 49 SQ2 sound resources: every channel offset is sorted/in-bounds, every channel reaches an in-payload 0xffff terminator, and sound 1 matches the first event shape from the source pass. I’m checking the symbol map for the sound channel globals before documenting this.”

    Action/result: Confirmed the targeted sound-resource tests and prepared sound symbol-map updates.

  1465. Reported: “The symbol map already names the main sound code paths, but it didn’t yet name the channel pointer/countdown/state globals that 0x7f96 initializes. I’m adding those data labels so later interpreter versions can be mapped by role instead of address.”

    Action/result: Prepared sound channel data labels.

  1466. Reported: “The sound label block has a slightly different neighbor order than I expected, so I’m patching it at the existing sound section rather than moving unrelated labels around.”

    Action/result: Added the sound data labels in the existing sound symbol section.

  1467. Reported: “I found the sound block and added the channel stream pointer, countdown, active-word, and attenuation labels. Next I’ll finish the docs for the sound resource shape and run the full verification set before the next commit point.”

    Action/result: Completed the symbol-map update and queued sound documentation/verification work.

  1468. Reported: “I’ll get the current work into a clean commit. First I’m checking the tree and the recent docs/test state so I don’t accidentally freeze an unfinished evidence trail.”

    Action/result: Checked git status, diff stats, and the tail of the progress log.

  1469. Reported: “The tree is large but coherent: opcode/probe work, graphics fuzz/view validation, placement modeling, and the new sound parser. I found the progress log stops just before the text/input and sound updates, so I’m going to patch that evidence gap and the sound docs before committing.”

    Action/result: Decided to finish missing progress-log and sound-doc updates before staging.

  1470. Reported: “The sound parser itself is small and the source-backed shape is clear: loader builds four channel pointers from the payload header, and the tick routine consumes duration/tone/control records. I’m adding that to the model and compatibility docs so the commit tells the full story.”

    Action/result: Added sound-resource shape documentation to the runtime, opcode, compatibility, progress, and evidence notes.

  1471. Reported: “Before editing, I’m keeping the patches scoped to documentation and one tiny test cleanup. No new reverse-engineering claim here without a source or test hook behind it.”

    Action/result: Kept the commit-prep edits scoped to evidence and verification support.

  1472. Reported: “The documentation gap is patched. I’m running the project’s full pre-commit checks now: unit tests, opcode evidence consistency, mdBook build, and whitespace scan.”

    Action/result: Ran the full pre-commit verification set.

  1473. Reported: “All four checks passed cleanly, and the full test suite is now 120 tests with the sound parser included. I’m recording that verification result in the progress log, then I’ll stage only the tracked project files plus the new sound parser/tests, leaving generated DOS/build artifacts alone.”

    Action/result: Confirmed python3 -B -m unittest discover -s tests passed with 120 tests, opcode evidence check passed, mdBook built, and git diff --check passed.

  1474. Reported: “The intended files are staged and the generated build/ and DOS installer directory are still untracked. I’m doing one staged diff sanity check, then I’ll write the commit.”

    Action/result: Verified staged status and staged diff summary before the commit.

  1475. Reported: “I added the final staging update to the historical log as well, so the record stays complete. Now I’m restaging that one log file and committing.”

    Action/result: Restaged the progress log before committing.

  1476. Reported: “Staged whitespace is clean and the only unstaged items are the expected generated directories. I’m committing this batch now.”

    Action/result: Committed the staged reverse-engineering, documentation, and compatibility-test updates.

  1477. Reported: “I’ll pick up from the tracker and avoid committing. First I’m rereading PROGRESS.md and the current tree so I can choose the next highest-value source-first work instead of wandering.”

    Action/result: Re-read the progress tracker, git status, progress log, and remaining-work references.

  1478. Reported: “The tracker points to sound playback and implementation-ready subsystem text as a good next seam. I’m going source-first there: re-read the sound driver/tick code around the parser I just added, then turn any concrete timing/state behavior into docs and tests.”

    Action/result: Began a source-first pass over the sound playback driver.

  1479. Reported: “The first pass exposed a useful state machine: in the one-channel path the tick loop only advances channel 0, while the four-channel path advances BX offsets 0,2,4,6. I’m checking the gate call at 0x7502 and the timer hook now so the docs don’t accidentally imply every tick always advances audio.”

    Action/result: Identified active-channel selection and moved to the flag gate and timer-hook source.

  1480. Reported: “The tick gate is flag 9: if that flag is clear, the sound tick stops playback immediately and sets the configured completion flag. I’m scanning the SQ2 sound corpus for duration edge cases before writing a schedule helper, because duration zero would have a very non-obvious 16-bit countdown consequence.”

    Action/result: Confirmed the flag-9 gate and scanned sound-event durations.

  1481. Reported: “The SQ2 corpus has 3,619 sound events and no zero-duration events, but the source would treat duration zero as a 16-bit countdown wrap before the next record. I’m adding a small schedule helper that captures the source timing rule without pretending to synthesize actual audio.”

    Action/result: Added source-backed sound scheduling helpers.

  1482. Reported: “The helper now models three source-backed things: which channels tick for the hardware selector, the initial one-tick delay before the first record, and the 16-bit countdown wrap. I’m adding tests against SQ2 sound 1, a longer multi-channel sound, and a synthetic zero-duration edge.”

    Action/result: Added sound scheduling tests.

  1483. Reported: “The targeted sound tests pass: the schedule helper matches sound 1’s tick-40 termination, distinguishes sound 60’s one-channel versus four-channel completion, and pins the zero-duration wrap. I’m folding this into the mdBook and the symbolic map now.”

    Action/result: Ran python3 -B -m unittest tests.test_sound_resources; all 9 targeted sound tests passed.

  1484. Reported: “The source also ties natural completion to the timer interrupt hook, not script bytecode: the IRQ calls the tick only while [0x1258] is active, and the tick sets the completion flag when the last active channel terminates. I’m making that distinction explicit in the docs and tracker.”

    Action/result: Documented sound tick scheduling, channel selection, flag-9 gating, natural completion, and remaining hardware-output gaps.

  1485. Reported: “The sound docs and labels are updated. I’m running the cheap checks for this slice now, then I’ll move to the next tracker item rather than stopping.”

    Action/result: Ran the targeted sound tests, mdBook build, whitespace check, and status check; the checks passed, with only expected modified project files and generated/untracked directories present.

  1486. Reported: “The sound slice is clean: targeted tests, book build, and whitespace check all passed. Next I’m tackling the remaining 0xa9 active saved-window path from source by finding every writer of the saved-window globals before deciding whether a dynamic probe is worth building.”

    Action/result: Began the text-window source pass and searched references to the saved-window globals and helpers.

  1487. Reported: “The active 0xa9 path is clearer now: message-window setup at 0x1d96 closes any prior saved window, computes two packed rectangle coordinates, draws/saves through 0x5590, sets [0x0d1d]=1, and later 0xa9 restores via 0x560c. This looks source-complete even if it’s awkward to isolate dynamically.”

    Action/result: Identified the modal saved-window producer at 0x1d96, the boxed draw/save helper at 0x5590, the restore helper at 0x560c, and the close consumer at 0x1f2b.

  1488. Reported: “I’m picking up from the active text-window restore pass and the tracker, with the source-first constraint in mind. I’ll close out the 0xa9 saved-window evidence cleanly, then move to the next highest-value item in PROGRESS.md without touching git.”

    Action/result: Re-read git status, the progress tracker, the relevant opcode/runtime/symbolic-label sections, and the recent progress log.

  1489. Reported: “The log is current through the sound documentation update, so I’m adding the missing verification and text-window source-pass entries before I edit the book. That keeps the historical trail aligned with what already happened rather than retrofitting it at the end.”

    Action/result: Prepared to record the missing verification and active text-window source-pass progress-log entries.

  1490. Reported: “I’m going to patch the text-window sections now: symbolic labels first, then the opcode/runtime prose, then the tracker and executable notes. The main change is to promote the active 0xa9 path from "helper calls seen" to a traced open/restore lifecycle with the producer at 0x1d96 and the close path at 0x1f2b.”

    Action/result: Added symbolic labels for the modal message-window opener, boxed draw/save helper, restore helper, active flag, and packed rectangle words; then updated the opcode, runtime, tracker, and executable notes.

  1491. Reported: “The symbolic map now has stable names for the modal window opener, draw helper, restore helper, active flag, and packed restore-coordinate words. Next I’m tightening the spec prose so readers see the lifecycle rather than a pile of addresses.”

    Action/result: Tightened the 0xa9 and modal text-window lifecycle prose in docs/src/logic_bytecode.md and docs/src/runtime_model.md.

  1492. Reported: “The 0xa9 docs now describe both sides of the saved-window lifecycle: where the rectangle is created and where it is restored. I’m updating the historical log with those intermediate reports next, then I’ll run the book/whitespace checks and continue to the next tracker item.”

    Action/result: Updated the historical progress log for the sound verification and active text-window restore documentation pass.

  1493. Reported: “I’m running the lightweight verification for the text-window documentation now. If that passes, the next likely target is save/restore path semantics from the tracker, since the 0xa9 gap is now source-backed.”

    Action/result: Ran mdbook build docs and git diff --check; both passed. A first search for old 0xa9 wording had a harmless shell quoting error from backticks in the pattern.

  1494. Reported: “The book build and whitespace check passed. I tripped a harmless shell quoting issue while searching for the old 0xa9 wording, so I’ll re-run that search safely and then start the save/restore path pass from disassembly.”

    Action/result: Re-ran the search with safe quoting and confirmed only historical progress-log wording still contained the old phrase.

  1495. Reported: “The tracker’s new top practical item is save/restore path selection and state transitions. I’m re-reading the existing save docs plus the handler and selector disassembly now, looking for concrete data-flow we can promote into a higher-level spec.”

    Action/result: Re-read the existing save/restore mdBook sections, symbolic labels, progress tracker entries, and source windows for the save, restore, and selector handlers.

  1496. Reported: “The selector helper is richer than the current docs show: 0x85e5 is mostly orchestration, with subhelpers at 0x8705 for path prompting/validation, 0x86a3 for drive/path availability messages, 0x8814 for enumerating/selecting slots, and 0x8794 for the modal text edit field. I’m narrowing those subhelpers from the disassembly file now so I can document them without guessing from UI behavior.”

    Action/result: Identified the save selector’s major subhelpers and moved to bounded disassembly windows for each one.

  1497. Reported: “I noticed the prebuilt whole-file disassembly is not using the same image-offset origin as these helper windows, so I’m avoiding it for exact save-selector addresses. I’ll use bounded ndisasm | sed windows from the decrypted executable instead, where the origin/skip convention matches the earlier notes.”

    Action/result: Used bounded ndisasm windows for 0x85e5, 0x8794, 0x8814, and 0x8b9f rather than relying on the stale origin in the prebuilt disassembly file.

  1498. Reported: “The save selector splits cleanly now: it returns zero for cancel/no valid choice, otherwise returns a 1-based-ish file selector that gets formatted into 0x1c8c. The save-mode special case is also visible: when saving into an empty-description slot, it prompts for a description into 0x1c6c before creating the file.”

    Action/result: Mapped selector return behavior, selected filename formatting, and the save-mode empty-description prompt.

  1499. Reported: “There are real SQ2 save files in the local game directory, which is perfect for deterministic parser tests. I’m going to inspect their block lengths and headers, then add a small save-file parser under tools/ if the files match the source-derived layout.”

    Action/result: Scanned SQ2/SQ2SG.* and found 11 local saves matching a 31-byte header plus five length-prefixed blocks.

  1500. Reported: “Good catch from the real files: the existing save-block table had the first block backward. The source writes a 1,505-byte block starting at data address 0x0002, not a 2-byte block starting near 0x05e1; all local SQ2 saves confirm the five block lengths [1505, 903, 328, 200, variable]. I’m adding a parser/test so this correction is pinned.”

    Action/result: Corrected the first save-block interpretation and added a plan to pin it with parser tests.

  1501. Reported: “I’m adding a tiny parser rather than a full save emulator. Its job is deliberately narrow: prove the file envelope the source writes and reads, keep block offsets/lengths explicit, and fail on truncation so future compatibility work has a trustworthy fixture reader.”

    Action/result: Added tools/agi_save.py with parse_save/load_save and save block dataclasses; added tests/test_save_resources.py.

  1502. Reported: “The save parser and tests are in place. I’m running the new save tests now, then I’ll fold the corrected block layout and selector flow into the mdBook and tracker.”

    Action/result: Ran python3 -B -m unittest tests.test_save_resources; all four save parser tests passed.

  1503. Reported: “The new save tests pass and they pin the source correction. I’m updating the docs now: first the exact save block envelope, then the selector state machine, then the compatibility/tracker notes.”

    Action/result: Began updating the executable, opcode, runtime, compatibility, progress, and symbolic-label documentation for the corrected save-file and selector model.

  1504. Reported: “I’m also naming the selector subhelpers in the symbol map. That matters for later cross-version work: the addresses will move, but the pattern of select_slot_or_path -> prompt_path -> list_slots -> read_slot_summary should be recognizable.”

    Action/result: Prepared symbolic labels for the selector subhelper chain.

  1505. Reported: “The executable chapter now has the corrected block table and selector flow. I’m mirroring the same model into the opcode chapter, keeping it concise enough that 0x7d/0x7e remain readable.”

    Action/result: Updated docs/src/agi_executable.md and then docs/src/logic_bytecode.md with the save-block envelope and selector flow.

  1506. Reported: “The opcode chapter now says exactly what goes on disk and how the slot selector exits. I’m turning that into a small implementation-facing save/restore data model next.”

    Action/result: Updated the save/restore data model in docs/src/runtime_model.md.

  1507. Reported: “The runtime model is updated. I’m adding the save selector’s subroutine/data labels now so future version comparisons can look for behavior, not just the SQ2 addresses.”

    Action/result: Added symbolic labels for selector subhelpers and save/path/header/filename buffers.

  1508. Reported: “The symbol map has the save selector broken into reusable labels. I’m adding the new save parser tests to the compatibility chapter and then I’ll mark the tracker’s save/file work as more advanced, with the remaining gaps narrowed.”

    Action/result: Documented tests.test_save_resources in docs/src/compatibility_testing.md and updated PROGRESS.md.

  1509. Reported: “The tracker now narrows the save/file gap to round trips, error paths, and path-validation edges. I’m adding the clean-room evidence note for this pass with the exact commands and the save-file block correction, then I’ll run the relevant checks.”

    Action/result: Added the clean-room executable notes section for the save-file selector and block-envelope source pass.

  1510. Reported: “I’m bringing the progress log up to date with the text-window verification and save-selector work before running the next check batch. The log is getting long, but it’s doing exactly what the project asks: leaving a breadcrumb trail for the clean-room story.”

    Action/result: Updated the historical progress log through the save-selector documentation and parser-test work.

  1511. Reported: “The cleanup search is clean. Next I’m taking the tracker’s heap/allocation item source-first, because the disassembly should tell us whether this is a true free-list allocator, a bump allocator, or some hybrid tied to room/resource lifetimes.”

    Action/result: Ran broad cleanup searches after the save-selector pass. A first search pattern accidentally used shell-expanded backticks in a double-quoted command, then the safe single-quoted searches found no stale save-block wording. Began source-first heap/allocation analysis.

  1512. Reported: “The heap source pass is already yielding a compact model: this is a bump allocator with marks, not a general free list. I’m following each mark helper now so we can describe which lifetimes are temporary, room-scoped, or diagnostic-only.”

    Action/result: Identified the current-top, limit, temporary-mark, room/reset-mark, and high-water heap globals around 0x0a55..0x0a5f.

  1513. Reported: “The allocator helpers are now source-clear enough to document as a normative model. One important detail: allocation failure does not look recoverable here; it displays the out-of-memory text and enters the restart/exit helper.”

    Action/result: Documented 0x13d6 as a bump allocation helper with a fatal out-of-memory path and high-water/free-memory updates.

  1514. Reported: “The startup path confirms one key lifecycle anchor: after initial object/inventory setup and logic 0 load, the engine stores the room/reset heap mark with 0x1476. That makes the later room-switch cleanup much less mysterious.”

    Action/result: Connected startup’s 0x1476 call to the room/reset mark consumed by room switch, restart, and restore cleanup helper 0x1485.

  1515. Reported: “The logic-resources chapter now describes the heap as a mark-and-rewind bump allocator. I’m mirroring that into the runtime model next, because implementers should not have to reconstruct lifetime semantics from a helper table.”

    Action/result: Added the heap helper table and diagnostic formulas to docs/src/logic_resources.md.

  1516. Reported: “The runtime model has the heap lifecycle table now. I’m adding the same labels to the symbolic map, including the individual globals, so this remains useful when we compare another interpreter build with moved addresses.”

    Action/result: Added the implementation-facing heap/allocation lifecycle table to docs/src/runtime_model.md.

  1517. Reported: “That patch missed because the data labels have shifted. I’m reopening the exact symbol-map sections and applying it in smaller pieces instead of guessing at the surrounding text.”

    Action/result: Re-read the symbol-map sections before applying the heap label updates.

  1518. Reported: “I’ll pick up from the heap/allocation pass that was in flight, finish wiring that evidence into the tracker and docs, then keep moving down PROGRESS.md until there’s a real blocker.”

    Action/result: Re-read PROGRESS.md, the symbol map, the progress log, and the current executable notes to resume from the in-progress heap work.

  1519. Reported: “I found the in-progress state: the heap model is already written into two chapters, but the symbol map and tracker still have the older compressed allocator note. I’m tightening those now so the cross-version map has names for the allocator routines instead of one catch-all heap label.”

    Action/result: Replaced the catch-all heap label with individual routine labels for allocation, current-top lookup, direct rewind, temporary mark save/restore, room/reset mark save, reset, free-memory update, and heap-status display.

  1520. Reported: “The allocator routines are now individually labeled. Next I’m adding the matching heap globals, because those names are what will make later “same source, different address” comparisons less brittle.”

    Action/result: Added symbolic labels for heap current top, base, room/reset mark, limit, temporary mark, and high-water globals.

  1521. Reported: “One small documentation cleanup: the allocator docs now refer to the free-memory byte by name, but that byte variable did not have a symbol row yet. I’m adding that before updating PROGRESS.md, so the references resolve cleanly.”

    Action/result: Added data.vars.free_memory_pages_0011 to the symbol map.

  1522. Reported: “The heap symbol map is complete enough for this pass. I’m marking the memory/allocation tracker as source-backed now, with the remaining work narrowed to initial heap setup and any observable out-of-memory UI edge we actually care about.”

    Action/result: Updated PROGRESS.md to describe the source-backed bump-heap model and narrow remaining memory/allocation work.

  1523. Reported: “The tracker now reflects the allocator as a mark-and-rewind bump heap instead of an unknown free-list. I’m adding the evidence note with the disassembly commands and the exact helper behavior, then I’ll run the book checks before choosing the next PROGRESS.md item.”

    Action/result: Added the heap allocation and mark/rewind source-pass section to docs/src/clean_room_executable_notes.md.

  1524. Reported: “I’m catching up the progress log now, including the allocator updates I reported before the context handoff and the shorter updates from this resumed pass. It’s a bit bookkeeping-heavy, but it keeps the clean-room timeline intact.”

    Action/result: Updated this progress log through the resumed heap documentation pass.

  1525. Reported: “The heap pass is now recorded across docs, tracker, symbols, and the progress log. I’m running the lightweight checks now; after that I’ll continue with the next high-value item, most likely restore/restart transitions from the source.”

    Action/result: Ran mdbook build docs, git diff --check, and targeted save/sound unit tests; all passed.

  1526. Reported: “The heap docs build cleanly, whitespace check is clean, and the targeted save/sound tests still pass. I’m moving to restore/restart transitions next and starting by rereading the existing source-backed notes so I don’t duplicate old ground.”

    Action/result: Re-read existing save/restore, replay, restart, and symbol-map notes.

  1527. Reported: “The existing notes already cover replay well, so I’m aiming this pass at the narrower gap: restart confirmation and restart-state cleanup. I’m going back to the handlers and their shared fatal/restart helper before changing prose.”

    Action/result: Began source-first restart/termination disassembly pass.

  1528. Reported: “One of the disassembly commands was too broad because ndisasm -e is a skip offset, not an end address. I caught that before using it as evidence; I’m switching to the existing full disassembly and bounded sed slices so the addresses I document are reliable.”

    Action/result: Discarded the broad exploratory output and switched to bounded slices with the project image/file-offset convention.

  1529. Reported: “The address convention is the EXE image address, with the file slice starting 0x200 bytes later. I’m using that convention now: -o image_address and -e image_address+0x200, then small bounded slices.”

    Action/result: Used corrected ndisasm commands for handlers 0x02ae, 0x027f, 0x2472, 0x2512, 0x2753, and shutdown helpers.

  1530. Reported: “The restart/restore slice confirms one important thing already: the fatal helper at 0x02ae calls cleanup and exits via the lower-level termination helper; restore read failure closes the file, displays the error message, and then enters that same helper. I’m mapping the confirm/restart action handlers next.”

    Action/result: Identified code.system.exit_with_cleanup and confirmed restore read failure is not recoverable in the observed source.

  1531. Reported: “The existing opcode chapter has good handler summaries; I’m verifying them against the slices so I can promote restart transitions into the runtime model without just restating the opcode table.”

    Action/result: Verified 0x80, 0x86, 0x7e, and 0x7d source slices against the opcode chapter.

  1532. Reported: “The restart path has a clearer shape now: 0x80 is an in-engine reset that returns zero to stop the current logic stream, while 0x86(1) and restore read failure go through 0x02ae, which performs shutdown/exit instead of a room-style reset. I’m checking that shutdown helper before naming it.”

    Action/result: Traced 0x02ae into 0x8275 and DOS termination wrapper 0x00ae.

  1533. Reported: “Shutdown cleanup is not just “exit”: 0x8275 closes the log file if it is open, then calls an interrupt/vector cleanup helper and a final display/driver helper before DOS termination. I’m documenting it at that level unless a later test needs each vector detail.”

    Action/result: Mapped shutdown cleanup through log close helper 0x838c, interrupt-vector restore helper 0x849f, and BIOS video-mode helper 0x5a5e.

  1534. Reported: “I’m adding symbolic names for the restart and termination helpers now. The key distinction I want the docs to preserve is “reset game state and keep running” versus “cleanup and terminate DOS process.””

    Action/result: Added restart, termination, shutdown cleanup, interrupt hook, and video-mode labels to docs/src/symbolic_labels.md.

  1535. Reported: “The symbol map now separates restart, fatal exit, shutdown cleanup, and interrupt restoration. I’m turning that into a short runtime lifecycle table so implementers can model the behavior without copying the interpreter’s exact globals.”

    Action/result: Began adding a restart/termination lifecycle table to docs/src/runtime_model.md.

  1536. Reported: “That insert missed because the next heading text is slightly different than I remembered. I’m reopening that small section and patching against the exact lines.”

    Action/result: Re-read the save/restore and object-lifecycle boundary in docs/src/runtime_model.md.

  1537. Reported: “The runtime model already has a good object lifecycle immediately after save/restore, so I’m placing restart/termination between those. That keeps all whole-session transitions together.”

    Action/result: Added the restart and termination lifecycle table to docs/src/runtime_model.md.

  1538. Reported: “The runtime model now has explicit rows for accepted restart, canceled restart, restore success, restore read failure, and fatal exit. I’m updating PROGRESS.md so restart polish is no longer listed as an open gap.”

    Action/result: Updated the room/restart/save/restore tracker entry and highest-value remaining work in PROGRESS.md.

  1539. Reported: “The tracker now reflects the restart distinction. I’m recording the source pass in the evidence log, including the corrected ndisasm address convention, so the trail is reproducible.”

    Action/result: Added the restart, restore-failure, and shutdown cleanup source-pass section to docs/src/clean_room_executable_notes.md.

  1540. Reported: “The evidence log is updated. I’m catching the progress log up for the restart pass, then I’ll run checks and continue into the next item rather than stopping here.”

    Action/result: Updated this progress log through the restart/termination documentation pass.

  1541. Reported: “The restart checks passed. For the save/restore item, I’m going source-first but adding a small compatibility-facing tool improvement too: if we can reserialize the parsed save envelope byte-for-byte, future tests can mutate or generate save files without hand-writing binary glue.”

    Action/result: Began the save/restore round-trip pass by inspecting the current save parser, save tests, compatibility docs, executable notes, tracker entry, and DOS wrapper disassembly.

  1542. Reported: “I’ll pick up exactly where the context left off: the save/restore round-trip item. I’m going to add a byte-for-byte save serializer and fold the DOS file-wrapper source notes into the docs so this moves from “we can parse saves” to “we can preserve and generate the exact envelope.””

    Action/result: Confirmed the next implementation target after context compaction and re-read the relevant parser, tests, docs, and tracker sections.

  1543. Reported: “The current save helper is intentionally minimal, so the next edit will stay small: serialize the same five-block envelope the parser already accepts, then test every checked-in SQ2 save for exact byte preservation. That gives us a useful compatibility primitive without pretending we’ve modeled every in-memory field yet.”

    Action/result: Prepared the save serializer/test edit scope.

  1544. Reported: “The serializer is now in place and deliberately validates the same structural promises the parser relies on: exact 31-byte header, exactly five blocks, matching block order, and matching length prefixes. Next I’m expanding the save tests so we prove every local SQ2SG.* file round-trips byte-for-byte.”

    Action/result: Added serialize_save, u16le_bytes, and write_save to tools/agi_save.py.

  1545. Reported: “The save tests now cover exact serialization plus validation failures for malformed in-memory save objects. I’m running the focused test module before I touch the prose, so the docs describe something we’ve actually checked.”

    Action/result: Added byte-for-byte round-trip coverage for all local SQ2SG.* files and serializer structural mismatch tests.

  1546. Reported: “The focused save tests pass: 6 checks, including byte-for-byte reserialization of all 11 checked-in saves. I’m documenting that as structural compatibility evidence, while keeping the dynamic original-engine restore path marked as separate remaining work.”

    Action/result: Ran python3 -B -m unittest tests.test_save_resources, which passed 6 tests.

  1547. Reported: “The compatibility chapter now distinguishes “container envelope is exact” from “the engine successfully restores a generated scenario.” I’m making the same distinction in the executable notes so future readers don’t overread the static test.”

    Action/result: Updated docs/src/compatibility_testing.md and docs/src/agi_executable.md with save envelope serialization evidence.

  1548. Reported: “While checking the DOS wrapper pass, I found an older symbol-map problem that’s worth fixing immediately: several DOS wrapper labels after open_file were shifted to the wrong addresses. The disassembly is clear here, so I’m correcting the label map before I write more prose against those names.”

    Action/result: Rechecked the DOS wrapper disassembly and found the correct post-open wrapper map beginning at 0x5cef.

  1549. Reported: “That patch missed because the surrounding lines had already shifted slightly. I’m reopening the small symbol-table window and applying the correction against the exact current text.”

    Action/result: Re-read the current DOS wrapper rows in docs/src/symbolic_labels.md.

  1550. Reported: “The stale rows are isolated. I’m replacing just that DOS wrapper block, so the rest of the resource/event labels stay untouched.”

    Action/result: Corrected the DOS wrapper symbolic labels and added labels for handle duplication, current-directory/drive helpers, find-first/find-next, drive probing, file-time reads, and the shared pre-call helper.

  1551. Reported: “The DOS symbol map is corrected now. I’m recording the correction in the evidence trail too, because stale labels are exactly the kind of thing that would cause pain when we compare another interpreter build later.”

    Action/result: Added the save-envelope round-trip and DOS wrapper correction section to docs/src/clean_room_executable_notes.md.

  1552. Reported: “The evidence notes now have the serializer result and the corrected DOS wrapper table. I’m catching up the progress log with these intermediate updates, then I’ll run the docs/tests checks and move on to the next PROGRESS item.”

    Action/result: Updated this progress log through the save-envelope and DOS-wrapper correction pass.

  1553. Reported: “One more small tracker tweak: since the wrapper map was corrected, I’m adding that explicitly to PROGRESS.md so the dashboard reflects the source-backed DOS helper names too.”

    Action/result: Added the corrected DOS wrapper symbolic-label map to the DOS file I/O tracker evidence.

  1554. Reported: “The tracker is caught up. I’m running the focused save tests, mdBook build, and whitespace checks now; after that I’ll continue down the remaining high-value list instead of stopping here.”

    Action/result: Ran focused save tests, mdbook build docs, and git diff --check; all passed.

  1555. Reported: “Checks are clean: save tests pass, mdBook builds, and git diff --check is quiet. Next I’m staying on the save/restore item, but shifting back to the disassembly to map the actual read/write helper failure paths rather than inferring them from the file format.”

    Action/result: Started source inspection of the save and restore handler failure paths around image offsets 0x2512, 0x26b0, 0x2753, and 0x28c6.

  1556. Reported: “The source pass confirms the error split: save write failures are recoverable after closing and deleting the partial file, while restore block-read failures go through fatal cleanup. I’m checking the local strings around those message offsets so the docs name the observable prompts more clearly.”

    Action/result: Inspected AGIDATA.OVL strings around 0x0d34..0x0e46 and confirmed the save/restore confirmation and error messages.

  1557. Reported: “The strings confirm the observable messages: restore read failure says “Error in restoring game” and then quits, while save create/write problems are “directory full/write-protected” or “disk full” style continue prompts. I’m adding a concise failure-path table to the implementation-facing docs.”

    Action/result: Prepared runtime-model save/restore failure lifecycle update.

  1558. Reported: “The runtime model already had restore success/failure, but it didn’t distinguish recoverable save/open failures from fatal restore reads. I’m extending that lifecycle table so an implementer can model the continuation behavior directly.”

    Action/result: Added save create failure, save write failure, and restore open failure rows to docs/src/runtime_model.md.

  1559. Reported: “The lifecycle table now captures save create failure, save write failure, restore open failure, restore success, and restore read failure as separate transitions. I’m adding the lower-level call/return details to the executable chapter next.”

    Action/result: Updated docs/src/agi_executable.md with write/read helper return rules and save/restore failure continuations.

  1560. Reported: “The executable chapter now has the helper-level behavior. I’m also tightening the opcode rows, because 0x7d and 0x7e are where most readers will first look for save/restore continuation semantics.”

    Action/result: Updated the 0x7d and 0x7e rows in docs/src/logic_bytecode.md with create/open/write/read failure behavior.

  1561. Reported: “The opcode rows now include the recoverable versus fatal failure distinction too. I’m recording this source pass in the clean-room notes with the exact disassembly/string probes that support it.”

    Action/result: Added the save/restore file-error source-pass section to docs/src/clean_room_executable_notes.md.

  1562. Reported: “The clean-room notes now have the save/restore error-path pass. I’m updating the progress log, then I’ll run checks and continue to the next highest-value area in PROGRESS.md.”

    Action/result: Updated this progress log through the save/restore file-error documentation pass.

  1563. Reported: “Since the source-backed file error paths are now documented, I’m narrowing the tracker wording. The remaining save work is mainly dynamic round-trip and representative UI probes, not basic source mapping.”

    Action/result: Narrowed the save/restore and DOS file I/O remaining-work text in PROGRESS.md.

  1564. Reported: “The tracker now reflects that the source mapping is done and the dynamic/UI side remains. I’m running checks for the doc changes, then I’ll inspect the existing QEMU logic probe harness to see whether a save/restore round-trip case can be added cleanly without inventing a one-off path.”

    Action/result: Ran focused save tests, mdbook build docs, and git diff --check; all passed. Searched the logic probe harness for save/restore cases and input support.

  1565. Reported: “The harness search shows existing save/restore coverage is mostly Escape/cancel smoke, not a full file round-trip. I’m reading the probe generator now to see how it handles post-launch keys and fixture DOS directories before deciding whether to add a dynamic save case or leave it as a larger follow-up.”

    Action/result: Re-read tools/logic_interpreter_probe.py helpers, case definitions, and snapshot runner.

  1566. Reported: “A full save/restore round trip through the UI is probably more involved than a single new case, but the harness can patch AGIDATA.OVL. I’m checking the selector source to see whether a prefilled path/description buffer lets us trigger a representative open/create error without brittle key navigation.”

    Action/result: Disassembled the save/restore selector around 0x85e5..0x8814 and inspected save-related initial data.

  1567. Reported: “The selector does have a noninteractive path when [0x0e72] is already nonzero, but it still goes through slot matching/formatting rather than simply trusting 0x1c8c. I’m reading that branch before I decide whether a patched fixture will be stable enough for QEMU evidence.”

    Action/result: Inspected the noninteractive slot-matching branch around 0x894e..0x8b99 and the filename formatter/path validator helpers.

  1568. Reported: “This selector path is not quite a clean one-liner: the prefilled-buffer branch still scans slot summaries and may fall back through UI-like selection logic. Rather than force a brittle dynamic save probe, I’m going to document that constraint and move to the next high-value item, the picture/view renderer compatibility work, where we have more direct harnesses.”

    Action/result: Deferred a dynamic save/restore probe as a larger focused task and moved to the picture/view compatibility tracker items.

  1569. Reported: “The picture/view tracker points to two practical gaps: valid picture edge semantics and broader view clipping. I’m starting with the view batch because the test already says base coverage includes mirror and top/left clipping, but PROGRESS.md still calls out right/bottom clipping as remaining.”

    Action/result: Inspected picture/view progress entries, view-batch tests, picture fuzz tests, and related docs.

  1570. Reported: “The object-overlay harness already has right and bottom edge cases, but the simpler picture-plus-view batch only has left/top. I’m going to broaden that base batch with right/bottom clipping cases and add local tests so the coverage is visible before any QEMU run.”

    Action/result: Re-read tools/view_batch.py, object-overlay clip cases, and clipping-related docs/tests.

  1571. Reported: “View 11’s frame is 20 by 5, so clean right and bottom cases are straightforward: request left 150 to exercise right clamping to 140, and baseline 170 to exercise bottom clipping past the 168-row logical screen. I’m adding those to the base view batch and tests.”

    Action/result: Added view_011_right_clip and view_011_bottom_clip to the base view-batch registry.

  1572. Reported: “The local frame type names the raw control byte rather than transparent_color, so that quick dimension probe tripped on an attribute name. I’m rerunning it with the actual fields and checking the compose clipping function before adding cases.”

    Action/result: Confirmed view 11/group 0/frame 0 dimensions as 20x5 with control byte 0x01, and reran view-batch tests.

  1573. Reported: “The base view batch now includes right and bottom clipping. I’m updating the tests so they assert all four clip directions explicitly and keep the expected base/stress counts honest.”

    Action/result: Updated tests/test_view_batch.py for four edge case IDs and the new base count.

  1574. Reported: “The local coverage tests are updated. I’m running the view-batch tests now, then I’ll decide whether to run a small QEMU snapshot for the two new cases or the full base batch.”

    Action/result: Ran python3 -B -m unittest tests.test_view_batch; all 5 tests passed.

  1575. Reported: “The view tests pass. To run just the two new QEMU cases cleanly, I’m adding the same repeated --case filter that the other probe harnesses already have, instead of creating an ad hoc JSON fixture file.”

    Action/result: Added repeated --case filtering to tools/view_batch.py.

  1576. Reported: “The view harness has the filter now. I’m adding a small unit test for selected-case loading, including the unknown-case error, so the CLI behavior is covered before I use it.”

    Action/result: Added view-batch case-filtering tests.

  1577. Reported: “The filter test is in place. I’m running the view-batch unit tests again; if they pass, I’ll launch just the two new clipping cases through QEMU snapshot mode.”

    Action/result: Ran python3 -B -m unittest tests.test_view_batch; all 6 tests passed.

  1578. Reported: “The view-batch tests pass with the new filter. I’m running a two-case QEMU snapshot batch for view_011_right_clip and view_011_bottom_clip now, which should give us original-engine evidence without running the whole suite.”

    Action/result: Ran clip_right_bottom_001; QEMU produced a mismatch for view_011_right_clip.

  1579. Reported: “The right-edge QEMU case mismatched, so that’s useful rather than bad: our simple local composition model is missing an observable placement rule for this path. I’m inspecting the report and generated capture to see whether this is the same spiral placement behavior already seen in object-overlay probes or a separate view-batch fixture issue.”

    Action/result: Inspected the failed report and capture; the mismatch bbox aligned with placement-search behavior rather than a decode failure.

  1580. Reported: “The mismatch is the same placement-search rule already documented for add_to_pic: a right-edge request for view 11 at left 150, baseline 80 lands at left 140, baseline 67, not at a simple clamped baseline 80. I’m going to make the view-batch case model allow expected placement overrides, then rerun right and bottom together.”

    Action/result: Rechecked object-overlay clip cases and placement-search tests for comparison.

  1581. Reported: “The object-overlay helper already has a source-derived fallback for the right-edge placement. I’m reusing that idea in the simpler view batch by adding optional expected position fields, rather than changing the renderer’s direct composition primitive.”

    Action/result: Inspected ObjectOverlayCase and its search_object_placement-based comparison path.

  1582. Reported: “Actually the better reuse is the existing search_object_placement helper from agi_graphics, which already models the bounds-only spiral search. I’m wiring view-batch comparison through that helper by default, with optional overrides for future odd cases.”

    Action/result: Re-read the placement-search helper and computed candidate placements for the new view requests.

  1583. Reported: “The placement search predicts the new requests as (150,80) -> (140,71) and (20,170) -> (23,167). I’m updating view_batch.py so QEMU comparisons use this source-derived placement search, matching how object-overlay probes already handle add_to_pic.”

    Action/result: Added expected_view_tuple and source-derived placement comparison to tools/view_batch.py.

  1584. Reported: “The view comparison now runs requested placements through search_object_placement unless a case provides an explicit expected override. I’m adding unit checks for the predicted right and bottom placements so the behavior is pinned locally too.”

    Action/result: Added placement expectation tests for the right and bottom view-batch cases.

  1585. Reported: “The placement expectations are now explicit in tests. I’m running the view-batch tests, then I’ll rerun the two-case QEMU batch.”

    Action/result: Ran python3 -B -m unittest tests.test_view_batch; all 7 tests passed.

  1586. Reported: “The local view tests pass, including the source-derived placement expectations. I’m rerunning the same two QEMU cases now to see whether right and bottom clipping line up with the original engine.”

    Action/result: Ran clip_right_bottom_002; both right and bottom cases matched with 0 mismatches.

  1587. Reported: “The rerun matched both cases: right and bottom clipping are now QEMU-validated in the view batch using the placement-search expectation. I’m updating the docs and tracker with both the failed first run and the corrected source-derived result so the evidence trail stays honest.”

    Action/result: Began documentation updates for the view-batch right/bottom placement validation.

  1588. Reported: “I’m updating the compatibility chapter now: the base view registry has grown from the original six to eight, and the new right/bottom cases have a focused QEMU report with 2 matches. The older six-case/stress notes stay as historical runs rather than being rewritten as if we reran everything.”

    Action/result: Updated docs/src/compatibility_testing.md with the focused right/bottom view-batch command, initial mismatch note, and corrected match result.

  1589. Reported: “The compatibility notes now record the initial mismatch and the corrected two-case QEMU match. I’m adding the same placement details to the graphics pipeline chapter so the implementation model and test evidence stay connected.”

    Action/result: Updated docs/src/graphics_object_pipeline.md with the new right/bottom placement-search evidence.

  1590. Reported: “The graphics chapter now ties the new view-batch evidence to the existing 0x593a placement model. I’m narrowing PROGRESS.md so right/bottom clipping is no longer listed as an open view-renderer gap.”

    Action/result: Updated the view resource tracker in PROGRESS.md.

  1591. Reported: “The tracker is narrowed. I’m recording the view-batch source/test/QEMU pass in the evidence notes, including the failed first comparison because it explains why the harness now uses placement search.”

    Action/result: Added the view-batch right/bottom placement validation section to docs/src/clean_room_executable_notes.md.

  1592. Reported: “The evidence notes now have the right/bottom view placement pass. I’m catching the progress log up with the save-probe decision and the view-batch work before I run the broader checks.”

    Action/result: Updated this progress log through the view-batch right/bottom placement validation pass.

  1593. Reported: “Focused checks are clean. I’m staying in picture/view compatibility now, since PROGRESS.md still calls out valid-picture edge semantics and broader renderer comparisons as a high-value open area.”

    Action/result: Returned to the picture/view compatibility tracker after the view-batch validation pass.

  1594. Reported: “The picture fuzz base already has both corner commands and the lower-right edge behavior pinned. I’m looking for the next useful source-backed gap: either a compact real-resource batch or a focused priority/control interaction, and I’ll choose the one with the cleanest evidence path.”

    Action/result: Reviewed existing picture fuzz and view/object coverage to avoid duplicating already-covered edge cases.

  1595. Reported: “There isn’t a reusable real-picture batch harness yet, only one-off fixture generation and comparison. I’m going to add that first so we can keep growing original-engine parity checks without rebuilding the plumbing each time.”

    Action/result: Started a reusable real-picture QEMU batch harness.

  1596. Reported: “Before picking real-picture batch seeds, I’m scanning the local picture directory for a couple of useful representatives: first present resource, largest payload, and pattern-command coverage.”

    Action/result: Scanned the local SQ2 picture corpus to choose small base cases for the real-picture batch.

  1597. Reported: “Picture 45 is still the largest payload in this local corpus, and picture 1 gives us an early resource with pattern plotting. I’m using those as the initial real-picture batch so the first run is meaningful but not huge.”

    Action/result: Selected picture 1 and picture 45 as the first reusable real-picture parity cases.

  1598. Reported: “The new picture-batch harness is in place. It can run either serially or through the existing QEMU snapshot path, and it writes the same kind of JSON summary we use for the fuzz/view batches.”

    Action/result: Added tools/picture_batch.py.

  1599. Reported: “The picture-batch tests are written for case loading, filtering, DOS directory naming, and report summaries. I’m running them alongside the view-batch tests before using QEMU.”

    Action/result: Added tests/test_picture_batch.py.

  1600. Reported: “The local picture/view batch tests pass. I’m running the two-case real-picture batch in QEMU snapshot mode now.”

    Action/result: Ran the local picture/view batch unit tests successfully.

  1601. Reported: “The picture batch built its fixtures, but QEMU couldn’t bind the local VNC port from the sandbox, so it failed before the game ran. I’m rerunning that exact harness with the existing escalation path rather than changing the evidence target.”

    Action/result: Recorded the unprivileged QEMU/VNC bind failure as a sandbox execution issue, not interpreter behavior.

  1602. Reported: “The real-picture QEMU batch passed: picture 1 and picture 45 both matched with 0 mismatches. I’m adding this reusable command to AGENTS.md, then I’ll update the compatibility docs and progress tracker.”

    Action/result: Ran picture_base_001; both real-picture cases matched.

  1603. Reported: “AGENTS now includes the real-picture batch command. I’m adding the compatibility report details next: the base batch covers the first present picture and the largest valid picture payload, both matched against the original engine.”

    Action/result: Added the real-picture batch command to AGENTS.md.

  1604. Reported: “That doc patch missed on the later provisional paragraph because it has shifted wording. I’m reopening the exact sections and applying the update in smaller pieces.”

    Action/result: Re-opened the compatibility sections and applied the real-picture batch documentation in smaller patches.

  1605. Reported: “The evidence trail is caught up for the real-picture batch. I’m running the focused unit tests and doc build now; if those are clean, I’ll use PROGRESS.md to choose the next reverse-engineering chunk rather than stopping at bookkeeping.”

    Action/result: Ran focused picture/view/save tests, mdBook build, and whitespace checks successfully.

  1606. Reported: “Focused verification passed: picture/view/save tests, mdBook, and whitespace checks are clean. I’m moving to the next open PROGRESS.md item now, with a source-first pass and QEMU only if it gives us a durable compatibility test.”

    Action/result: Re-read the high-value remaining-work list and selected picture/view renderer compatibility as the next source-first target.

  1607. Reported: “The next tracker items overlap: picture/view parity and implementation-ready object drawing. Since the user has explicitly pushed for source-first work, I’m going to inspect the picture drawing routines around the mask/pattern paths before adding more QEMU cases.”

    Action/result: Began a picture pattern/mask disassembly pass.

  1608. Reported: “The source map already has labels for the picture command scanner and pattern data tables, so I’m narrowing onto the pattern-plot routine itself. The aim is to turn “odd/even mask interactions” from a tracker phrase into exact behavior we can encode and test.”

    Action/result: Re-read the pattern plot documentation, local renderer, and disassembly windows around picture command dispatch and pixel writes.

  1609. Reported: “The first disassembly window landed in the middle of adjacent code and table bytes, so I’m realigning from the known scanner entry and following the dispatch target into 0xfa. This is exactly the sort of place where labels help but raw source still needs a careful re-read.”

    Action/result: Realigned disassembly to the command scanner, pattern handler, coordinate readers, and common pixel writer.

  1610. Reported: “One useful correction surfaced immediately: the picture routine labels in the docs are loaded-image offsets, while ndisasm -e is reading file offsets. For this executable that creates a 0x200-byte difference in this region, so I’m keeping the existing symbolic labels but recording file-offset windows explicitly in the evidence notes.”

    Action/result: Kept existing symbolic labels and recorded the file-offset detail in the clean-room notes.

  1611. Reported: “The disassembly confirms pattern plotting does not have its own channel semantics: it stores candidate coordinates in the normal picture pixel register and calls the same pixel writer as lines and fills. The odd/even behavior therefore lives in the color-enable commands and pixel_write, so I’m reading the color mapper next.”

    Action/result: Inspected the common pixel writer and EGA color-mapper path.

  1612. Reported: “For full 16-color EGA, the mapper returns the same nibble in AL and AH, so odd/even masks exist but do not diverge for visual colors. A good next compatibility step is to add valid synthetic pattern cases that cross multiple rows with visual-only, control-only, and visual+control active, proving the shared pixel writer’s channel-mask behavior through QEMU.”

    Action/result: Chose three safe pattern-channel fuzz cases based on the source-backed common pixel-writer path.

  1613. Reported: “I’m going to add three curated valid picture-fuzz cases: visual+control pattern writes, visual-disabled/control-only pattern writes, and control-disabled/visual-only pattern writes. The local tests will assert both nibbles; the QEMU batch will validate the visible EGA surface for these streams against the original engine.”

    Action/result: Added base_027, base_028, and base_029 to tools/picture_fuzz.py, plus renderer tests for channel-mask behavior.

  1614. Reported: “The curated fuzz cases and local channel assertions are in. I’m adding a small harness test too, so the new case IDs stay part of the stable base corpus rather than quietly drifting.”

    Action/result: Added a base-case ID assertion to tests/test_picture_fuzz.py.

  1615. Reported: “The local assertions now distinguish visible and control nibbles for pattern plotting. I’m running the focused renderer/fuzz tests before regenerating the corpus and asking QEMU to confirm the visible surface.”

    Action/result: First focused test run failed, revealing that the sample control class used in the both-active case matched the default control nibble and therefore did not show as a control change.

  1616. Reported: “The new tests caught something useful before QEMU: my assumptions about the sample payload were off, not the executable. The cell values show the command stream I chose is not isolating the intended channel state, so I’m inspecting the renderer command stepping and then I’ll adjust the fixtures to match the source semantics.”

    Action/result: Probed the three sample payloads locally and identified default control nibble 4 as the reason one assertion was wrong.

  1617. Reported: “Found the issue: the default control nibble is already 4, so using control color 4 made “changed control pixels” look unchanged. I’m switching the both-active case to control 5 and adjusting the control-disabled expectation to preserve the default high nibble.”

    Action/result: Changed base_027 to control class 5 and corrected the control-disabled expected cell value.

  1618. Reported: “The channel tests now use a non-default control class when they need to observe a control change. I’m rerunning the focused tests and regenerating the small corpus so the manifest matches the patched cases.”

    Action/result: Focused renderer/fuzz tests passed; regenerated the small pattern-channel corpus with 38 cases and 36 safe-for-QEMU cases.

  1619. Reported: “The local renderer and corpus tests are clean. I’m running the three new valid pattern-channel cases through the original engine now; this only validates the visible EGA output, while the control-nibble part stays source-backed and locally tested.”

    Action/result: Started QEMU snapshot batch pattern_channel_masks_001.

  1620. Reported: “QEMU is running the three-case snapshot batch now. It has built all fixtures and booted the shared snapshot disk; I’m waiting on the captures/comparisons.”

    Action/result: Waited for the running QEMU snapshot batch.

  1621. Reported: “The QEMU batch matched all three new cases with zero mismatches. I’m documenting the source result and the visible-surface confirmation now, including the caveat that QEMU screenshots do not expose the control buffer directly.”

    Action/result: Began updating compatibility, graphics pipeline, progress, and clean-room evidence notes for the pattern channel-mask pass.

  1622. Reported: “The old compatibility paragraph still says there are 27 curated base cases. With the three new channel-mask cases, that needs to become 30; I’m updating the docs and then I’ll regenerate the standard corpus so the recorded count is true.”

    Action/result: Updated compatibility docs and PROGRESS.md for the three new pattern-channel cases and the narrowed EGA odd/even-mask gap.

  1623. Reported: “The compatibility chapter and tracker now describe the channel-mask cases and narrow the odd/even visual-mask gap to non-EGA behavior. I’m regenerating the standard deterministic corpus next so the docs’ 1,054/1,052 counts match the current base-case set.”

    Action/result: Regenerated the standard fuzz corpus; it reported 1,054 total cases and 1,052 safe-for-QEMU cases.

  1624. Reported: “The regenerated corpus reports 1,054 total cases and 1,052 safe-for-QEMU cases, as expected. I’m recording the exact source windows, tests, corpus generation, and QEMU result in the clean-room notes now.”

    Action/result: Added the pattern channel-mask source/QEMU evidence section to docs/src/clean_room_executable_notes.md.

  1625. Reported: “The evidence notes are updated with the source-first channel-mask pass. I’m catching the progress log up now, then I’ll run the relevant checks and decide the next PROGRESS.md item.”

    Action/result: Updated this progress log through the pattern channel-mask pass.

  1626. Reported: “The progress log is caught up through the channel-mask pass. I’m running focused checks now; if they pass, I’ll keep going into the next open item instead of stopping.”

    Action/result: Ran focused picture/fuzz/picture-batch/view/save tests, mdBook build, and whitespace checks successfully.

  1627. Reported: “Focused checks are clean after the channel-mask work. The next useful picture task is broader real-resource parity, so I’m scanning the local picture corpus for representative resources instead of simply picking room numbers by hand.”

    Action/result: Scanned all valid local PICDIR payloads and ranked them by payload size, pattern count, fill count, and command-family diversity.

  1628. Reported: “The corpus scan gives a better broad-picture set than just “more numbers”: picture 46 is pattern-heavy, 44 is fill-heavy, 17 exercises all command families with several pattern-mode changes, and 43/45 are large dense resources. I’m extending the real-picture batch harness with base, broad, and all presets, then I’ll run the broad preset in QEMU.”

    Action/result: Added broad_cases, all_present_cases, and preset selection to tools/picture_batch.py.

  1629. Reported: “The harness now has a dynamic all preset plus a curated broad preset. I’m running its unit tests before taking the broad set to QEMU.”

    Action/result: Added preset/discovery tests and ran python3 -B -m unittest tests.test_picture_batch; all 8 tests passed.

  1630. Reported: “The picture-batch tests pass. I’m running the broad eight-picture preset through the original engine with one QEMU snapshot now; this should give us a much stronger real-resource parity sample without committing to a full 74-picture run yet.”

    Action/result: Started QEMU snapshot batch picture_broad_001.

  1631. Reported: “The broad real-picture run has built all eight fixtures and is executing from the snapshot disk. I’m waiting for the captures and comparisons; if one fails, the report should stop at the first mismatch because this is a parity run.”

    Action/result: Waited for the broad real-picture QEMU batch.

  1632. Reported: “Still waiting on the broad QEMU batch; the larger real pictures take longer to render/capture than the tiny synthetic cases. The run is still active, so I’m leaving it alone and polling.”

    Action/result: Continued polling the running broad QEMU batch.

  1633. Reported: “Five of the eight broad real-picture cases have matched so far, including the dense/fill-heavy ones. I’m waiting on the remaining largest/pattern-heavy cases now.”

    Action/result: Observed the first five broad cases matching during the running QEMU batch.

  1634. Reported: “The broad real-picture batch passed: all eight representative resources matched the original engine with zero mismatches. I’m adding the preset command/result to the docs and progress tracker now.”

    Action/result: Recorded picture_broad_001 as 8 matches, 0 mismatches, and 0 errors.

  1635. Reported: “The broad batch evidence is now in the compatibility chapter and tracker. I’m adding the broad preset command to AGENTS.md too, so a future agent can rerun the representative real-resource check directly.”

    Action/result: Added the broad picture-batch command to AGENTS.md.

  1636. Reported: “AGENTS.md has the broad real-picture command now. I’m recording the corpus-selection scan, harness preset changes, tests, and QEMU result in the clean-room notes before running the verification suite again.”

    Action/result: Added the broad real-picture preset parity section to docs/src/clean_room_executable_notes.md.

  1637. Reported: “The progress log is current through the broad real-picture batch. I’m running the focused checks plus mdBook and diff whitespace again; after that I’ll move to the next remaining item.”

    Action/result: Ran focused picture-batch, graphics-rendering, picture-fuzz, view-batch, and save tests plus mdBook and whitespace checks; all passed.

  1638. Reported: “Checks are clean. Since the broad preset passed, the natural next step is a full present-picture parity batch; the harness now supports it, and this would retire most of the remaining “broader real-resource parity” risk if the DOS image has enough room. I’m checking the snapshot-disk constraints before launching a long run.”

    Action/result: Inspected snapshot helper behavior, all-present case count, fixture sizes, and current DOS snapshot image size/free space.

  1639. Reported: “The full 74-picture run would likely exceed the 64 MB DOS snapshot image because each fixture directory carries a full runnable game copy, and 74 copies would be around 70 MB before filesystem overhead. I’m checking whether we can shrink picture fixtures or whether the practical next step should be a few preset batches rather than one giant disk.”

    Action/result: Compared full-tree fixture contents and confirmed repeated original volumes/save files dominated picture-batch fixture size.

  1640. Reported: “There’s a better way around the full-corpus disk problem: pack each tested picture payload into that fixture’s VOL.3 alongside the generated LOGIC.0, and copy only the small engine/support files. That should make a 74-picture snapshot feasible while preserving the original picture data being tested. I’m going to implement this as a packed fixture path for picture_batch.py and validate it against the already-passing broad preset before attempting all pictures.”

    Action/result: Designed the packed picture fixture approach.

  1641. Reported: “I’m adding a new packed picture-fixture builder rather than changing the older one-off fixture generator. That keeps historical commands stable while letting batch runs use the smaller, purpose-built layout.”

    Action/result: Added copy_minimal_picture_tree and build_packed_picture_fixture to tools/qemu_fixture.py.

  1642. Reported: “picture_batch.py now uses packed picture fixtures, while qemu_fixture.py picture remains the older full-tree generator. I’m adding a unit check that the packed fixture actually omits the big original volumes and repoints PICDIR into VOL.3.”

    Action/result: Updated tools/picture_batch.py and added packed-fixture structural coverage to tests/test_qemu_fixture.py.

  1643. Reported: “There is already a qemu-fixture test module, so I’m putting the packed-fixture structural check there instead of squeezing it into picture-batch tests.”

    Action/result: Integrated the packed-fixture test with existing qemu-fixture tests.

  1644. Reported: “The packed fixture structure test is in. I’m running the qemu-fixture and picture-batch unit tests now; after that I’ll validate the packed layout with a small QEMU run before considering all 74 pictures.”

    Action/result: Ran tests.test_qemu_fixture and tests.test_picture_batch; all 37 tests passed. A local size probe showed packed picture fixtures are about 72 KB of input files.

  1645. Reported: “The packed fixture is tiny now, about 72 KB per picture instead of roughly 1.7 MB. Unit tests pass, so I’m running the two-picture base QEMU batch with the packed layout to make sure the original engine is happy with the trimmed fixture set.”

    Action/result: Ran picture_base_packed_001; pictures 1 and 45 matched with 0 mismatches.

  1646. Reported: “The packed base QEMU run matched both pictures, so the trimmed fixture set is valid. I’m launching the full 74-picture present resource batch now with packed fixtures; this may run for a while, but it should fit on the snapshot image.”

    Action/result: Started QEMU snapshot batch picture_all_001 using --preset all and packed fixtures.

  1647. Reported: “The full-picture batch built all 74 packed fixtures and is now running in QEMU from one snapshot. I’ll keep polling; this is the long parity pass.”

    Action/result: Confirmed the full QEMU batch had entered the capture phase.

  1648. Reported: “No comparison output yet; QEMU is still active. The first batch output usually appears after the initial cases have rendered and screendumped, so I’m continuing to wait.”

    Action/result: Continued polling the active QEMU process.

  1649. Reported: “Still no per-case results from the full-picture run, but the QEMU process has not exited. I’m letting it continue; with 74 cases the snapshot setup and early captures can be a little quiet.”

    Action/result: Continued waiting on the full-picture QEMU batch.

  1650. Reported: “A quick note on the silence: picture_batch.py compares only after run_snapshot_qemu_cases finishes all captures, so it won’t print per-picture matches during the QEMU phase. This is expected for the full run.”

    Action/result: Clarified the batch harness output behavior while the run continued.

  1651. Reported: “The full QEMU run is still active. I’m continuing to poll and will report the summary once the capture phase completes.”

    Action/result: Continued polling the active QEMU process.

  1652. Reported: “This is taking long enough that I’m checking the capture directory from the host side while QEMU continues, just to make sure files are being produced and we’re not waiting on a dead run.”

    Action/result: Counted 19 generated captures, proving the full run was progressing.

  1653. Reported: “Host-side check shows 19 captures have already been written, so the full-picture batch is progressing normally. I’m letting it keep running.”

    Action/result: Continued the full-picture QEMU batch.

  1654. Reported: “The full QEMU capture phase is still running. I’m staying with it; once QEMU exits the script will compare all generated screenshots in one pass.”

    Action/result: Continued polling the active QEMU process.

  1655. Reported: “Another quick progress check from the filesystem side: I’m checking how many captures have landed so far.”

    Action/result: Counted 26 generated captures.

  1656. Reported: “We’re at 26 captures out of 74, so the run is moving steadily. No action needed beyond waiting.”

    Action/result: Continued the full-picture QEMU batch.

  1657. Reported: “The full-picture parity run is still in the capture phase. I’m continuing to let QEMU work through the list.”

    Action/result: Continued polling the active QEMU process.

  1658. Reported: “Polling the capture count again so we have a rough sense of progress.”

    Action/result: Counted 33 generated captures.

  1659. Reported: “33 of 74 captures are present now. The run is almost halfway through the QEMU phase.”

    Action/result: Continued the full-picture QEMU batch.

  1660. Reported: “Still running; I’m continuing to poll. The silence from the script is expected until all 74 screenshots have been captured.”

    Action/result: Continued polling the active QEMU process.

  1661. Reported: “Checking capture count again.”

    Action/result: Counted 40 generated captures.

  1662. Reported: “40 of 74 captures are in. The packed fixture approach is paying off; the disk is not the bottleneck now, just the per-case draw/capture loop.”

    Action/result: Continued the full-picture QEMU batch.

  1663. Reported: “The full-picture run is still active. I’m waiting on the remaining capture loop.”

    Action/result: Continued polling the active QEMU process.

  1664. Reported: “When asked about using input to cycle through resources: "Yes, absolutely, and it’s a good idea. The current snapshot harness optimizes away DOS boot, but it still starts a fresh engine process per case so every capture is isolated and easy to reason about…"”

    Action/result: Identified carousel-style resource sweeps as a useful future throughput optimization while keeping the current isolated harness as a reference oracle.

  1665. Reported: “The current full-picture batch is still running in the slower per-process snapshot style. I’m going to let it finish for this run, then I can add a carousel mode as a follow-up because it fits exactly the bottleneck we’re hitting.”

    Action/result: Continued the existing full-picture batch rather than interrupting it to change harness strategy mid-run.

  1666. Reported: “Good call. Simply growing the raw file would not help by itself because the DOS partition/FAT geometry inside it would still describe the old 64 MB volume; we’d need a larger formatted/bootable DOS test image or a purpose-built large fixture image. For the current run the packed fixtures avoid the limit, so I’m letting it finish, but I’ll note the larger-image path in the docs and can build one if the carousel/all- in-one fixture starts wanting more room.”

    Action/result: Confirmed that packed fixtures solved the current DOS image limit and documented the need for a properly formatted larger image if future batches need more space.

  1667. Reported: “The long run is still active, so I’m checking capture progress again while it continues.”

    Action/result: Counted 56 generated captures.

  1668. Reported: “56 of 74 captures are present. We’re through most of the current batch; after it finishes I’ll document both your carousel idea and the DOS-image-size note, then decide whether to implement carousel mode next.”

    Action/result: Continued the full-picture QEMU batch.

  1669. Reported: “Agreed. Once this run finishes, I’m going to treat batch throughput as part of the reverse-engineering infrastructure, not just a convenience: carousel-style scripts for picture/view sweeps, larger or purpose-built DOS test images where needed, and keeping the isolated per-case harness as the slow but simple reference path. That will matter a lot when we start comparing interpreter versions across games.”

    Action/result: Promoted high-throughput batch infrastructure to an explicit future work item for cross-game/interpreter comparison.

  1670. Reported: “The run is still alive. I’m checking the capture count again; if it completes cleanly, the next infrastructure task is clearly a faster in-engine resource sweep mode rather than just adding more one-shot batches.”

    Action/result: Counted 64 generated captures.

  1671. Reported: “64 of 74 captures are present. We’re on the final stretch of this slower full-picture pass.”

    Action/result: Continued the full-picture QEMU batch.

  1672. Reported: “Still running; I’m letting QEMU finish the last ten-ish cases.”

    Action/result: Continued polling the active QEMU process.

  1673. Reported: “Polling capture count one more time as it nears the end.”

    Action/result: Counted 71 generated captures.

  1674. Reported: “71 of 74 captures are in. The capture phase should finish soon, then the script will compare all 74 against the local renderer.”

    Action/result: Continued the full-picture QEMU batch.

  1675. Reported: “The capture phase finished and comparisons have started. First two pictures matched; I’m waiting for the full comparison summary.”

    Action/result: Observed the first two full-picture comparisons matching.

  1676. Reported: “The comparison pass is moving quickly now: the first 30 present pictures have matched. Continuing to poll for the rest.”

    Action/result: Observed the first 30 full-picture comparisons matching.

  1677. Reported: “60 of 74 comparisons have matched. The full corpus parity result is looking very good; waiting on the final fourteen.”

    Action/result: Observed 60 full-picture comparisons matching.

  1678. Reported: “Full present-picture parity passed: all 74 valid SQ2 pictures matched the original engine with 0 mismatches. I’m going to document that result, plus the packed-fixture optimization and your carousel/larger- image notes, then I’ll run the full checks.”

    Action/result: Recorded picture_all_001 as 74 matches, 0 mismatches, and 0 errors.

  1679. Reported: “I’m updating the docs now in two layers: first the factual result (74/74 pictures matched), then the infrastructure lesson (packed fixtures solved the 64 MB pressure, and carousel/input-driven sweeps should be added for future multi-game work).”

    Action/result: Updated compatibility docs and the progress tracker with the full present-picture parity result.

  1680. Reported: “The report gives us a strong milestone: every valid local SQ2 picture resource now matches pixel-for-pixel against the original EGA engine. I’m making sure the tracker reflects that this piece is no longer merely “broader parity pending.””

    Action/result: Updated PROGRESS.md to move full-corpus SQ2 picture parity from remaining work to evidence.

  1681. Reported: “I’ve added the full-picture command and the packed-fixture note to the reusable instructions. I’m adding the detailed evidence note next, including the throughput lesson from this long run and the future carousel strategy.”

    Action/result: Added AGENTS.md commands/notes and the packed picture/full-parity clean-room evidence section.

  1682. Reported: “The clean-room evidence now records both the full 74-picture result and the infrastructure lesson. I’m also going to put the carousel recommendation into AGENTS.md, because it is exactly the kind of future-agent context that should not be buried only in the long notes.”

    Action/result: Added the carousel-style high-throughput recommendation to AGENTS.md.

  1683. Reported: “AGENTS.md now flags carousel-style fixtures as the preferred future direction for high-throughput sweeps. I’m updating the progress log with this longer run and the two throughput discussions, then I’ll verify everything.”

    Action/result: Updated the progress log through the full-picture parity run and infrastructure discussion.

  1684. Reported: “The log is current through the full parity run and the infrastructure notes. Before starting a new harness feature, I’m running the verification suite once, so any later carousel work starts from a clean baseline.”

    Action/result: Ran all 146 tests, opcode evidence check, mdBook build, and whitespace checks successfully.

  1685. Reported: “Full verification is clean: all 146 tests, opcode evidence check, mdBook, and diff whitespace. I’m going to prototype the carousel mode now, starting from existing bytecode helpers rather than inventing a separate control path.”

    Action/result: Began implementing a picture carousel harness.

  1686. Reported: “I’m going to implement the carousel as a separate batch tool rather than modifying the existing picture-batch semantics. That gives us a fast path for sweeps while keeping the one-case-per-engine-run harness intact as the simple reference oracle.”

    Action/result: Added carousel bytecode helpers and packed carousel fixture generation to tools/qemu_fixture.py.

  1687. Reported: “The bytecode side of the carousel is now in qemu_fixture.py: it shows the first picture once, advances on raw key events, and discards the previous picture after showing the next one so cache growth doesn’t become the new bottleneck. I’m adding structural tests for that generated fixture now.”

    Action/result: Started adding carousel fixture tests.

  1688. Reported: “The test file has shifted a little from my expected context, so I’m reopening the import block and helper-test section and applying the carousel tests in smaller patches.”

    Action/result: Reopened tests/test_qemu_fixture.py and split the test patch into smaller edits.

  1689. Reported: “The smaller patches are straightforward: imports first, then the condition helper assertions, then the packed carousel fixture tests near the existing packed-picture test.”

    Action/result: Added carousel-related imports, helper assertions, and fixture layout tests.

  1690. Reported: “Imports are updated. I’m adding the assertions for the raw-key condition bytes and the carousel fixture layout next.”

    Action/result: Added raw-key and carousel fixture assertions.

  1691. Reported: “The fixture tests are in. I’m adding the runner tool now: build one packed carousel fixture, start the engine once, capture picture 0, send a key to advance, capture picture 1, and so on.”

    Action/result: Added tools/picture_carousel.py.

  1692. Reported: “The carousel runner tool is in place. I’m adding lightweight unit tests around its report and orchestration path with mocks, then I’ll do a short QEMU smoke run to validate the raw-key advance behavior in the actual engine.”

    Action/result: Added tests/test_picture_carousel.py.

  1693. Reported: “The unit tests cover the carousel’s naming/reporting and that it builds one fixture while comparing each capture separately. I’m running these plus the qemu-fixture tests now.”

    Action/result: Ran carousel/qemu-fixture tests successfully and confirmed a two-picture carousel fixture is about 77 KB.

  1694. Reported: “The carousel unit tests pass and the two-picture carousel fixture is tiny, about 77 KB. I’m running the two-picture carousel against QEMU now; this is the important proof that raw-key advancement works in the original engine.”

    Action/result: First unprivileged QEMU carousel attempt failed at VNC bind before the engine ran.

  1695. Reported: “As expected, the first carousel QEMU run failed at the sandbox VNC bind, before the engine ran. I’m rerunning the exact command with the same escalation pattern we’ve used for the other QEMU harnesses.”

    Action/result: Reran with approved tools/picture_carousel.py prefix; the first picture matched but the second mismatched.

  1696. Reported: “The carousel ran under QEMU, but only the first picture matched; the second capture mismatched. That means the packed carousel fixture boots and captures correctly, but the raw-key advance either didn’t fire or didn’t settle before capture. I’m inspecting the report and capture rather than treating this as a renderer problem.”

    Action/result: Compared the second capture and found it was closest to picture 1 with a visible UI/text damage rectangle.

  1697. Reported: “The failed carousel smoke is behaving like the key is being handled by the normal input path, not by our advance condition. There’s already a validated key-mapping/status-byte path for logic probes, so I’m switching the carousel advance trigger from raw key condition to map_key_event plus status byte condition. That should consume x as a mapped event instead of typed parser input.”

    Action/result: Investigated status-byte mapping as a possible carousel trigger.

  1698. Reported: “That search command had a quoting mistake. I’m rerunning a simpler search for the status-byte table and then I’ll patch the carousel trigger.”

    Action/result: Re-ran the status-byte search successfully.

  1699. Reported: “The status-byte path would need either explicit status clearing or many one-shot mapped statuses, so I’m trying the lighter source-backed fix first: disable the normal input line in the carousel setup. The failed capture looked like the typed key was affecting normal input UI, so keeping input-line processing out of the sweep may let the raw-key predicate own the advance event cleanly.”

    Action/result: Added input-line disable action to carousel setup.

  1700. Reported: “The carousel setup now disables the normal input line before drawing the first picture. I’m rerunning the structural tests and then the QEMU two-picture carousel smoke.”

    Action/result: Tests passed; QEMU base carousel still mismatched the second picture.

  1701. Reported: “Disabling the input line did not fix the carousel advance: first picture still matches, second still mismatches. I’m checking whether the second capture is still basically picture 1 with UI damage, or whether the advance happened but the display state carried over.”

    Action/result: Confirmed the second capture remained closest to picture 1 with the same UI/text mismatch area.

  1702. Reported: “The second capture is still closest to picture 1, with a mismatch rectangle that looks like UI/text damage. So the raw-key advance is not the right trigger as currently wired. I’m trying a non-printing key next; if it fires the raw-key predicate without typing into the parser UI, it may be enough for carousel advancement.”

    Action/result: Tried f1 as the raw-key advance key; it still mismatched.

  1703. Reported: “f1 also failed to advance. I’m going to stop treating this as a quick harness tweak and inspect the generated bytecode plus the known event/update ordering; the carousel tool can stay, but it needs a better advance mechanism before we mark it validated.”

    Action/result: Re-read the earlier validated raw-key logic probe.

  1704. Reported: “The first two attempts prove something useful but not complete: “press a key and check raw-key condition” is not enough in this carousel setting, because the normal command/input path is still producing visible UI changes while the picture index stays put. I’m reading the earlier validated raw-key probe to see what setup difference made that one work.”

    Action/result: Compared carousel logic against the existing raw-key probe setup.

  1705. Reported: “Aha, found the likely bug in my carousel bytecode: raw-key caches at absolute byte [0x001c], while AGI variable actions are offset from [0x0009]. I used variable index 0x1c, which clears [0x0025]; the correct variable slot for absolute [0x001c] is 0x13. That would let one key event advance twice in the same logic pass and wrap back to picture 1. I’m patching that now.”

    Action/result: Corrected the raw-key cache clear to script variable v19.

  1706. Reported: “The raw-key cache clear is corrected to variable slot 0x13. I’m tightening the test so this exact off-by-base mistake is pinned.”

    Action/result: Added a regression assertion for assignn(v19, 0).

  1707. Reported: “The regression check now looks for assignn(v19, 0), which is the bytecode way to clear absolute [0x001c]. I’m rerunning tests and then the QEMU carousel smoke.”

    Action/result: Tests passed; QEMU base carousel picture_carousel_base_003 matched both pictures.

  1708. Reported: “The carousel tests pass with the corrected raw-key slot. I’m rerunning the QEMU base carousel; this should tell us whether the fast path is genuinely usable.”

    Action/result: Confirmed the corrected two-picture raw-key carousel passed with 2 matches.

  1709. Reported: “The corrected carousel smoke passed: one engine process, two pictures, two matches, zero mismatches. I’m going to try the eight-picture broad preset next, which is the real speed comparison against the snapshot-per-case approach.”

    Action/result: Started broad raw-key carousel run.

  1710. Reported: “The broad carousel run is active. It should complete much faster than the old eight-process-style snapshot batch if the per-key advance remains stable.”

    Action/result: Broad raw-key carousel matched first three pictures, then mismatched five later captures.

  1711. Reported: “The broad carousel advanced correctly through the first three pictures, then mismatched on the larger resources. That smells like capture timing, not bytecode: the larger pictures need more time to decode/draw after the key. I’m rerunning broad with a longer per-advance wait.”

    Action/result: Reran broad raw-key carousel with --advance-wait 8.

  1712. Reported: “The longer-wait broad carousel run is in progress. If this passes, we’ll have a usable fast mode with an explicit wait-time knob for heavier resources.”

    Action/result: Waited on the longer broad carousel run.

  1713. Reported: “The longer broad carousel run is still active. It captures and compares after the run finishes, so silence here just means it is stepping through the eight resources.”

    Action/result: Longer broad raw-key carousel failed at the same boundary.

  1714. Reported: “The longer wait did not change the failure boundary: pictures 1, 6, and 17 match, then the larger packed resources fail. I’m checking whether capture 4 is the wrong picture, a partially rendered picture, or a bad resource-load effect.”

    Action/result: Found captures 4 onward were exactly picture 17.

  1715. Reported: “The broad failure is now clearer: after picture 17, every later capture is still exactly picture 17. So the carousel is not misrendering big pictures; it stops advancing after the third displayed picture. I’m going to probe whether that is tied to picture 17, the third index, or repeated key delivery.”

    Action/result: Created and ran a manual four-picture carousel probe.

  1716. Reported: “That combined command included a small inline Python file-write before the carousel run, so the approved prefix didn’t cover the QEMU segment cleanly. The case file is written now; I’m rerunning just the carousel command.”

    Action/result: Reran the manual four-picture carousel command.

  1717. Reported: “The four-picture manual carousel is running now. This should tell us whether the stall is “third advance” generally or something about picture 17/broad ordering.”

    Action/result: Manual raw-key carousel also stalled after the third displayed picture.

  1718. Reported: “The manual four-picture run also stalls after the third displayed picture, so it’s not picture 17 or large-resource decode. The next likely culprit is my persistent index variable: I used a high scratch-style variable (v249), which may be fine for immediate operands but not safe as per-cycle state. I’m moving the carousel index to a lower dedicated variable and retesting.”

    Action/result: Changed the carousel index variable from v249 to v32.

  1719. Reported: “The carousel index now uses v32 instead of v249. I’m updating the tests to assert the new state variable, then I’ll rerun the manual four-picture carousel.”

    Action/result: Updated tests for v32.

  1720. Reported: “The test now pins the lower carousel index variable. I’m running the structural tests and the same four-picture QEMU probe again.”

    Action/result: Tests passed; the four-picture raw-key carousel still stalled after the third displayed picture.

  1721. Reported: “The structural tests pass with v32. I’m rerunning the four-picture carousel to see whether the third-advance stall disappears.”

    Action/result: Confirmed the lower index variable did not fix the stall.

  1722. Reported: “The lower index variable didn’t change the stall, so the script state is probably fine. The remaining suspect is QEMU/DOS key delivery for repeated identical monitor keys. I’m teaching the runner to accept a comma-separated key cycle, then I’ll try x,y,z so each advance is a distinct key event.”

    Action/result: Added comma-separated key cycling to the carousel runner.

  1723. Reported: “The runner can now cycle through a comma-separated key list. I’m adding a tiny test for rejecting an empty key list, then I’ll rerun the four-picture QEMU probe with x,y,z.”

    Action/result: Added empty advance-key validation test.

  1724. Reported: “The empty-key guard test is in. I’m running the carousel tests and then the distinct-key four-picture QEMU probe.”

    Action/result: Tests passed; distinct printable keys still stalled after the third displayed picture.

  1725. Reported: “The distinct-key carousel run is active. If this still stalls after the third capture, the key-repeat theory is out and we’ll need a different advance event source.”

    Action/result: Confirmed the key-repeat theory did not explain the stall.

  1726. Reported: “The raw-key route is too flaky for long carousels. I’m switching the carousel to the interpreter’s already QEMU-validated mapped-key/status-byte mechanism: setup maps each advance key to a unique status byte, each transition tests “current index and that status,” and the carousel does not wrap. That avoids needing to clear status bytes and fits representative sweeps well.”

    Action/result: Reworked carousel bytecode to use mapped keys and status byte conditions.

  1727. Reported: “The carousel logic now uses mapped status events and has one transition per key, no wrap. I need to update the tests from “raw key advance” to “mapped key/status advance.””

    Action/result: Updated qemu-fixture tests for mapped-key carousel logic.

  1728. Reported: “The tests now expect mapped-key bytecode. I’m wiring the key list through the runner so the fixture maps the same keys that QEMU sends.”

    Action/result: Passed advance key words through tools/picture_carousel.py into the generated carousel fixture.

  1729. Reported: “The runner now defaults to a long mapped-key list and errors if there are not enough keys for the selected carousel. I’m running the tests, then the four-picture and broad QEMU probes again.”

    Action/result: Mapped-key tests passed.

  1730. Reported: “Tests pass with mapped-key carousel bytecode. I’m rerunning the four-picture QEMU probe using the default key list.”

    Action/result: Four-picture printable-key carousel matched two pictures, then showed picture 20 with UI/text artifact and stalled.

  1731. Reported: “The mapped-key four-picture carousel is running. If this passes, I’ll move straight to the broad mapped-key run.”

    Action/result: The mapped-key four-picture carousel did not pass.

  1732. Reported: “Mapped keys improved the repeated-key issue differently but still stalls, now after the second displayed picture. I’m checking the captured image identity again; this will tell us whether status-byte triggering is only firing once.”

    Action/result: Found the third capture was mostly picture 20 with a visible UI/text mismatch, and the fourth capture remained the same.

  1733. Reported: “The second transition did draw picture 20, but with a small UI/text mismatch, and then subsequent captures stayed there. So mapped status events are advancing, but the normal input UI still leaves visible damage after the transition. For a fast oracle, we either need to suppress/repair that UI or compare only after a clean picture refresh.”

    Action/result: Localized the mismatch rectangle to the same visible text/window area.

  1734. Reported: “The mismatch rectangle is the same visible text/window area, so printable mapped keys are still being interpreted by the command parser. I’m adding support for non-printing function keys (f1, f2, …) as mapped advance keys; those should produce key events without putting text into the input/parser path.”

    Action/result: Added function-key BIOS word mapping to tools/picture_carousel.py.

  1735. Reported: “Function-key names now map to BIOS key words for the fixture and are sent to QEMU as the same key names. I’m fixing the carousel bytecode validation to allow those 16-bit key words.”

    Action/result: Updated carousel bytecode validation for 16-bit mapped keys.

  1736. Reported: “The fixture generator now accepts 16-bit mapped key words. I’m running the carousel tests, then the four-picture QEMU run with the function-key defaults.”

    Action/result: Tests passed; function-key four-picture carousel still stalled after the second displayed picture.

  1737. Reported: “Tests pass with function-key support. I’m running the four-picture QEMU carousel with the default f1,f2,f3,... advance keys now.”

    Action/result: Ran the function-key four-picture carousel.

  1738. Reported: “The function-key carousel run is active. This is the cleanest version of the idea so far: mapped non-printing keys, one engine process, no raw-key cache juggling.”

    Action/result: The function-key four-picture run still failed after two displayed pictures.

  1739. Reported: “Function keys still stall after the second displayed picture, so the problem is not only printable input. I’m going to stop short of marking carousel validated beyond the two-picture smoke and document this as a partially working prototype: useful direction, but it needs a better event/ack strategy before replacing snapshot batches.”

    Action/result: Decided to document carousel as prototype infrastructure rather than broad compatibility evidence.

  1740. Reported: “Before documenting the limitation, I’m running the two-picture base carousel once more with the current mapped function-key implementation. That gives us a clean “validated for two-step smoke, not yet broad sweep” boundary.”

    Action/result: Ran picture_carousel_base_mapped_fkey_001; it matched both base pictures with 0 mismatches.

  1741. Reported: “The current carousel implementation is validated for the two-picture base smoke: 2 matches, 0 mismatches. The four/broad sweeps still stall after two displayed pictures, so I’m recording it as a prototype with a clear limitation rather than overclaiming it.”

    Action/result: Updated compatibility docs, PROGRESS.md, and AGENTS.md with the carousel smoke status and limitation.

  1742. Reported: “I’m updating the docs with the precise carousel status: why it exists, what passed, what failed, and what a future fix needs to solve.”

    Action/result: Added carousel status to docs/src/compatibility_testing.md.

  1743. Reported: “The compatibility chapter and progress tracker now mark carousel as a two-picture smoke prototype, not a broad oracle. I’m updating AGENTS.md with the same caveat so future runs don’t accidentally treat it as production-grade.”

    Action/result: Added carousel caveat to AGENTS.md.

  1744. Reported: “AGENTS.md now carries the caveat. I’m adding the detailed carousel evidence trail to the clean-room notes, including the raw-key cache bug and the failed broader runs.”

    Action/result: Added the picture carousel prototype section to docs/src/clean_room_executable_notes.md.

  1745. Reported: “Absolutely. I’m going to treat the current carousel work as a throughput prototype rather than a side experiment: first I’ll make sure the docs/log reflect what we learned, then I’ll verify the repo, and if there’s a clean next improvement for resource iteration I’ll make it rather than leaving it as a vague note.”

    Action/result: Confirmed the progress log contains the carousel evidence trail through entry 1892 and began promoting cross-game resource iteration throughput into the tracker.

  1746. Reported: “Good catch. I’m not currently using the interpreter’s speed setting in the harness; the QEMU waits so far are conservative wall-clock sleeps, and the new timed carousel I’m wiring up uses interpreter cycles but has not yet set the engine speed.”

    Action/result: Searched local docs, SQ2 logic messages, and disassembly for speed/cycle handling.

  1747. Reported: “Answer to the direct question: no, not yet. I’ve been using conservative --draw-wait/--advance-wait sleeps and QEMU snapshotting, not an explicit “fastest” interpreter speed setting.”

    Action/result: Confirmed no obvious SQ2 script message for speed and moved the investigation to the executable.

  1748. Reported: “Found it. The cycle throttle is right at 0x7f78: it reads byte [0x0013], which is script variable v10 if variables start at [0x0009], then spins until timer/counter word [0x1784] reaches that value and clears [0x1784].”

    Action/result: Added a symbolic label and fixture support for setting the speed variable in timed carousel scripts.

  1749. Reported: “For the actual timed QEMU run, I’m not going to use speed 0 for the first probe because it can advance all pictures before the first screendump; I’ll use v10 = 1 with a larger cycle hold so each picture remains visible long enough to capture.”

    Action/result: Timed two-picture carousel picture_carousel_base_timed_001 matched 2/2.

  1750. Reported: “Nice, the timed two-picture carousel matched 2/2 with one engine process. I’m trying the broad eight-picture version with the same speed/hold settings; this is the real test of whether the timed approach avoids the key-event stall.”

    Action/result: The first fixed-sleep broad timed run matched 2/8; identity checks showed wrong-picture cadence misses.

  1751. Reported: “The identity check is revealing: after picture 1, the carousel jumped to picture 17 and then stayed there. That means timing alone is not the whole story; the timed script is losing or corrupting its carousel state after the second transition.”

    Action/result: Moved carousel state back to high scratch variables and confirmed that did not fix the fixed-sleep broad cadence.

  1752. Reported: “The two-picture 17 -> 43 case passes, so picture 43 is fine. The broad stall is likely inside the transition bookkeeping.”

    Action/result: Reordered timed/key carousel transition bytecode to update carousel state before discarding the old picture.

  1753. Reported: “The reorder changed the failure shape: picture 76 now matches, so state is advancing through the sequence. The remaining broad mismatches are cadence alignment, not a hard stall.”

    Action/result: Cached identity check showed captures landing on exact renders for other broad-preset pictures.

  1754. Reported: “The identity pass shows clean cadence drift: captures saw 1,17,43,44,46,76,76,76, so the carousel is advancing roughly twice as fast as the capture interval.”

    Action/result: Tried delay-cycles 240; this aligned picture 6 but still drifted later.

  1755. Reported: “The calibrated run shows why fixed sleeps will be brittle across games: picture load/draw time changes the cadence. I’m going to add a polling mode for timed carousels instead.”

    Action/result: Added --poll, --poll-interval, and --poll-timeout to tools/picture_carousel.py.

  1756. Reported: “Good news: the polling timed carousel has matched the first six broad pictures in sequence. It’s still waiting/capturing the last two, so I’m letting that finish.”

    Action/result: picture_carousel_broad_timed_poll_001 matched all 8 broad pictures with delay-cycles 240.

  1757. Reported: “The faster polling run passed 8/8 in one engine process. I’m trying one more shorter hold (delay_cycles=60) to find a practical lower bound; if it passes, that becomes the better recommendation.”

    Action/result: picture_carousel_broad_timed_poll_fast_001 matched all 8 broad pictures with delay-cycles 120; delay-cycles 60 missed intermediate pictures and is not recommended.

  1758. Reported: “The 60 hold was too short and missed almost everything, so the practical default is delay_cycles=120, speed_value=1, polling every half-second. I’m making the CLI defaults reflect that stable setting and documenting the faster carousel path now.”

    Action/result: Updated CLI defaults, AGENTS.md, compatibility docs, PROGRESS.md, symbolic labels, logic bytecode notes, and clean-room notes with the timed polling carousel evidence.

  1759. Reported: “The clean tree is just generated artifacts now. I’m going to take the next compatibility-suite step: scale the timed polling carousel from the 8-picture broad preset to the full 74-picture SQ2 picture set, because that directly exercises the faster iteration path we’ll need for more games and interpreter versions.”

    Action/result: Began a full-present-picture timed polling carousel run.

  1760. Reported: “The full carousel fixture is built and QEMU is running it now. I’m watching for the first reported comparisons; if this becomes too slow or hits an engine lifecycle limit, the next move will be chunking the picture list rather than restarting per picture.”

    Action/result: Confirmed the full all-picture timed polling fixture could launch under QEMU, but no per-case output appeared during the polling phase.

  1761. Reported: “No per-picture output yet, which means the harness is still inside the QEMU polling phase. That is expected for a large sweep, but I’m noting this as a harness ergonomics issue: future long runs should print poll progress while they are still inside QEMU, not only after QEMU exits.”

    Action/result: Identified the need for live per-case polling progress in tools/picture_carousel.py.

  1762. Reported: “This has now gone past the broad-run timing, which is not surprising for 74 pictures but is exactly why the harness needs internal progress output. I’m letting it finish rather than interrupting, because even a partial failure shape will tell us where the single-process carousel breaks.”

    Action/result: Let the single all-picture QEMU run continue to preserve the failure evidence.

  1763. Reported: “The all-picture run is still active. Given the current timeout settings, the theoretical ceiling is high if one early picture never appears, so I’m waiting a bit longer and then I’ll either read the report or use the partial captures to see what state the engine reached.”

    Action/result: Continued waiting for the report rather than killing the QEMU process.

  1764. Reported: “The report is coming back now. The first 19 all-present pictures matched in sequence; I’m reading the rest of the mismatches to see whether this is renderer drift, a timing issue, or an engine prompt state.”

    Action/result: Single all-picture timed polling run produced a report with 19 matches and 55 mismatches.

  1765. Reported: “The full timed polling run finished: 19 matches, 55 misses. I’m doing an identity pass over the mismatched captures now; that will tell us whether QEMU drew different valid pictures, got stuck on one picture, or produced a UI/error screen.”

    Action/result: Started comparing mismatched QEMU captures against all present local picture renders.

  1766. Reported: “The identity pass shows a clean stall: every later capture is closest to picture 19. I’m opening one of those captures next, because the most likely explanations are either a disk/resource prompt or the generated script failing to advance after a specific resource.”

    Action/result: Determined that captures for picture 20 onward were not renderer mismatches; they stayed visually at picture 19.

  1767. Reported: “The picture 20 capture explains the stall: the engine is showing ‘Please insert disk 3 and press ENTER.’ over picture 19. I’m checking the generated disk contents and packed offsets now before deciding whether this is a fixture bug or an original-engine resource lifecycle limit.”

    Action/result: Inspected the picture 20 capture and found the original engine disk-insert prompt.

  1768. Reported: “The packed record header for picture 20 is correct, and the PICDIR entry points at the right offset. I’m also checking the DOS snapshot directory so we don’t misdiagnose a missing VOL.3 copy as interpreter behavior.”

    Action/result: Confirmed picture 20’s generated PICDIR entry and packed VOL.3 record header were correct.

  1769. Reported: “The DOS snapshot has the large VOL.3 intact, so the prompt is not the obvious missing-file case. I’m treating the single huge carousel as crossing an original-engine resource/disk lifecycle boundary, and I’m adding chunking so large sweeps stay inside the behavior we want to model.”

    Action/result: Confirmed VOL.3 was present in the generated DOS fixture and chose chunking for large picture sweeps.

  1770. Reported: “I’ve got the shape: one process is good up to 19 pictures, but the all-in-one fixture hits a disk prompt before picture 20 even though the generated volume file is present. I’m adding chunked carousel execution now so the all-picture sweep can still run much faster than one boot per picture.”

    Action/result: Began adding chunked carousel execution to tools/picture_carousel.py.

  1771. Reported: “The code change is small: split the case list, build one carousel fixture per chunk, and suffix the DOS directory plus snapshot paths so each chunk is isolated. I’m adding unit coverage for the split and naming behavior before rerunning QEMU.”

    Action/result: Implemented chunk directory/path helpers and chunked carousel orchestration.

  1772. Reported: “Chunked execution is wired into the CLI now. I’m updating the tests to assert the exact chunk split and generated snapshot names, since that is the part future agents are most likely to accidentally break.”

    Action/result: Added unit coverage for chunk splitting and generated snapshot path names.

  1773. Reported: “The main path looks correct, but I’m patching the test import and expectations rather than trusting it by inspection. After that I’ll run the focused carousel tests, then the full 74-picture chunked QEMU sweep.”

    Action/result: Completed test import/expectation updates for chunked carousel support.

  1774. Reported: “I caught one index mismatch in the expected generated DOS names while reading the tests; fixing that now. This is exactly the kind of tiny harness naming issue that’s cheap to catch before a QEMU run.”

    Action/result: Corrected the expected chunked DOS directory names.

  1775. Reported: “Now I’m running focused carousel tests before spending time on QEMU. If those pass, the next command will be the all-picture timed polling sweep split into chunks of 16.”

    Action/result: python3 -B -m unittest tests.test_picture_carousel passed 7 tests.

  1776. Reported: “The chunking tests pass. I’m launching the all-picture sweep again with --chunk-size 16; that should tell us whether the disk prompt was caused by the oversized single carousel rather than picture 20 itself.”

    Action/result: Started the chunked all-present-picture timed polling carousel run.

  1777. Reported: “Chunk 1 of 5 is running now. This is already a better harness shape: we get bounded engine lifetimes and still avoid paying a boot for every individual picture.”

    Action/result: Chunk 1 began under QEMU.

  1778. Reported: “Chunk 1 passed all 16 pictures, so the chunked path is past the easy first segment. The important test is chunk 2, because that includes picture 20, where the single all-picture run hit the disk prompt.”

    Action/result: Chunk 1 matched 16/16.

  1779. Reported: “Chunk 2 passed too, including picture 20. That strongly supports the interpretation that the previous failure was the oversized all-in-one fixture lifecycle, not picture 20 decoding or local rendering.”

    Action/result: Chunk 2 matched all expected pictures, including picture 20.

  1780. Reported: “Chunk 3 also passed. I’m letting the last two chunks finish; if they stay clean, we’ll have full 74-picture carousel evidence with five engine launches instead of 74.”

    Action/result: Chunk 3 matched all expected pictures.

  1781. Reported: “Chunk 4 passed all 16. One short final chunk remains, and if that passes I’ll document the chunked command as the recommended high-throughput real-picture sweep.”

    Action/result: Chunk 4 matched all expected pictures.

  1782. Reported: “I’m picking up the harness-scaling thread from PROGRESS.md: the all-picture timed carousel now works in chunks, so I’m going to finish the evidence trail around that, tighten the harness a little, and then run the standard docs/tests checks.”

    Action/result: Resumed from PROGRESS.md and confirmed the uncommitted chunked carousel work and generated QEMU outputs.

  1783. Reported: “The chunked all-picture run already proved the big compatibility gain: 74 of 74 SQ2 pictures matched when split into 16-picture chunks. I’m going to add the missing evidence notes now, and I’ll also make the polling runner print per-case progress so future long sweeps don’t look frozen.”

    Action/result: Prepared documentation updates and a live polling progress improvement for tools/picture_carousel.py.

  1784. Reported: “One useful limitation surfaced here: the all-in-one run did not reveal a renderer error, it revealed an engine resource/disk prompt boundary after picture 19. The chunked run gives us the practical compatibility path while keeping that boundary documented instead of papered over.”

    Action/result: Recorded the all-in-one disk-prompt boundary as a fixture lifecycle observation, not renderer semantics.

  1785. Reported: “I’m making one small harness edit before the docs: polling mode will now print each expected picture as it matches or times out. That gives us live feedback during big resource sweeps without changing the comparison semantics.”

    Action/result: Added per-case poll matched / poll timed out progress output to the QEMU polling runner.

  1786. Reported: “Now I’m updating the clean-room record: the important detail is that the failed 74-picture single carousel is evidence about original-engine fixture limits, while the chunked version is the reusable high-throughput path.”

    Action/result: Updated AGENTS.md, compatibility docs, and clean-room executable notes with the all-picture chunking evidence.

  1787. Reported: “The docs now say exactly what we learned from the prompt boundary, including the checked VOL.3 and PICDIR bytes. Next I’m updating PROGRESS.md so the dashboard reflects that the picture carousel has moved from ‘broad smoke’ to full 74-picture coverage in chunks.”

    Action/result: Updated PROGRESS.md to include the chunked all-74-picture timed polling carousel validation.

  1788. Reported: “PROGRESS.md is updated. I’m adding the progress-log entries now; I’m keeping them concise but preserving each reported step/result so the historical trail remains useful without turning into raw transcript sludge.”

    Action/result: Added progress-log entries for the all-picture and chunked-carousel work.

  1789. Reported: “The historical log is caught up through the chunked sweep. I still need to log the final result and the documentation pass after verification, but the substantive evidence is now in the project docs.”

    Action/result: Reviewed the working diff before final verification.

  1790. Reported: “I noticed the new test import and a couple of assertions are a bit wide, so I’m cleaning that up before running the full suite. The behavior stays the same; this is just keeping the harness readable for the next pass.”

    Action/result: Wrapped the new tests/test_picture_carousel.py import and long assertions, and wrapped a couple of wide lines in tools/picture_carousel.py.

  1791. Reported: “The code cleanup is done. I’m going to add the last few progress-log entries for this resumed pass, then run the standard verification commands.”

    Action/result: Finished progress-log maintenance and moved to verification.

  1792. Reported: “Verification time. I’m running the unit suite, opcode evidence check, and mdBook build in parallel; after those finish I’ll do the whitespace diff check and one final status pass.”

    Action/result: python3 -B -m unittest discover -s tests passed 158 tests; python3 -B tools/logic_opcode_evidence.py --check passed; and mdbook build docs completed successfully.

  1793. Reported: “All three main checks passed: 158 unit tests, the opcode evidence check, and the mdBook build. I’m doing the final git diff --check and status now.”

    Action/result: git diff --check passed. The remaining tracked changes are the chunked carousel harness, tests, and documentation updates.

  1794. Reported: “The current changes are verified and uncommitted, with only the usual generated build/ and DOS installer artifacts untracked. I’m going to keep going from the next high-value PROGRESS.md item: a representative save/restore or file-error probe, starting from the existing local tools/docs so I can reuse the established clean-room path.”

    Action/result: Began inspecting existing save/restore and file-error evidence for the next PROGRESS.md task.

  1795. Reported: “For the next item I’m looking for the shortest useful dynamic probe: something observable in QEMU, but backed by the existing source/file-format notes instead of freeform poking. First pass is finding the current save/restore tooling and where the docs say the gaps still are.”

    Action/result: Searched existing tools, tests, and docs for save/restore coverage and confirmed the remaining dynamic gap.

  1796. Reported: “The existing gap is exactly where PROGRESS.md says it is: we have static save-file envelope tests and Escape-cancel QEMU checks, but not a real round-trip through the original engine. I’m reading the fixture builder and save-file parser next to see whether we can generate a controlled slot and restore it dynamically.”

    Action/result: Re-read tools/agi_save.py, tools/qemu_fixture.py, and the save-selector notes.

  1797. Reported: “A full restore of an arbitrary existing SQ2 save would pull us into actual game-state rendering, which is less controlled. A more useful first dynamic probe is narrower: generate a fixture with no save files, drive the original save selector to create SQ2SG.1, then pull that file back through the source-backed parser.”

    Action/result: Chose a dynamic save-write probe before attempting a full restore round trip.

  1798. Reported: “I’m going to add this as a reusable tool rather than a one-off experiment. The probe will build a synthetic save fixture, drive the real selector, convert the post-run disk back to raw, extract SQ2SG.1, and parse it with our save envelope code.”

    Action/result: Added the first version of tools/save_roundtrip_probe.py.

  1799. Reported: “The probe tool is in place. I’m adding lightweight tests for the fixture-building pieces now, then I’ll run it against QEMU and tune the key sequence if the selector needs a different path.”

    Action/result: Added tests/test_save_roundtrip_probe.py.

  1800. Reported: “The unit tests cover the generated bytecode shape, fixture save-file removal, and JSON reporting. I’m running those focused tests now before spending QEMU time.”

    Action/result: python3 -B -m unittest tests.test_save_roundtrip_probe passed 4 tests.

  1801. Reported: “Focused tests pass. I’m launching the new save probe with the simplest likely selector sequence: Enter to select the first empty slot, type Codex probe, Enter to accept the description. If that misses, the capture/report should show which selector screen we landed on.”

    Action/result: First QEMU run hit the sandbox VNC socket restriction.

  1802. Reported: “QEMU hit the sandbox’s VNC socket restriction for this new tool, so I’m rerunning the same probe with escalation. This is the same pattern as the other QEMU harnesses: local emulator, local disk image, local VNC endpoint.”

    Action/result: Escalated QEMU run completed, but the probe failed because it tried to compare synthetic picture 0 through the real SQ2 picture comparator.

  1803. Reported: “The QEMU run itself completed, but my probe made a bad local comparison assumption: picture 0 is synthetic in the fixture, not a real SQ2 picture, so compare_picture_capture(0, ...) quite rightly refused it. I’m fixing the probe to compare against the generated blank-picture plus validation-view frame instead.”

    Action/result: Replaced the real-picture comparison with a synthetic blank-picture plus view-11 validation comparison.

  1804. Reported: “That comparison fix keeps the probe honest: it now checks the fixture’s own expected post-save screen rather than pretending the synthetic picture is a stock SQ2 resource. I’m rerunning the focused tests, then the QEMU probe again.”

    Action/result: Focused tests passed; QEMU ran, but no SQ2SG.1 file was created.

  1805. Reported: “The probe ran through QEMU but no SQ2SG.1 was created, so the first key sequence didn’t accept the save. I’m inspecting the post-run capture and DOS directory now to see which save-selector state it reached.”

    Action/result: DOS directory listing showed no save file after the first key-sequence attempt.

  1806. Reported: “The directory listing confirms no save file was written. I converted the capture and I’m going to inspect it visually; that should tell us whether we’re still at the path prompt, the slot list, or a validation screen.”

    Action/result: Visual capture showed the save-description prompt with a blank input line.

  1807. Reported: “The capture shows the engine is sitting at the save-description prompt with a blank input line. That means the no-save fixture skips straight to description entry; I’m rerunning with a longer wait and without the leading Enter, so the typed description lands in the line editor.”

    Action/result: The adjusted command needed escalation because shell newline quoting did not match the approved prefix.

  1808. Reported: “That rerun used shell quoting for the newline, so it didn’t match the approved prefix and hit the same local VNC sandbox restriction. I’m rerunning it escalated; if this sequence works I’ll make newline handling less awkward in the tool.”

    Action/result: Rerun produced no save file; the capture showed a path error for \SVRTcodex probe.

  1809. Reported: “Still no save file, so this is not just the leading Enter. I’m opening the second capture; if the text appears there, the missing step is an acceptance/confirmation key after description, otherwise the key delivery needs a staged delay.”

    Action/result: Visual capture showed the path prompt had interpreted the description text as part of a directory name.

  1810. Reported: “That capture is very helpful: the text landed in the path prompt, producing \SVRTcodex probe. So the selector needs staged input: accept the path, wait for the description prompt, then type the description. I’m changing the save probe to own its small QEMU input loop instead of using the generic one-shot post-launch key field.”

    Action/result: Reworked the tool to drive staged QEMU input directly.

  1811. Reported: “I spotted a variable shadowing bug in that staged-input patch before rerunning: the description argument was being overwritten by the parsed-result field. Fixing that now.”

    Action/result: Fixed the save-description argument handling.

  1812. Reported: “The staged-input path is fixed. I’m running the focused tests again, then the QEMU save creation probe with the new default staged sequence.”

    Action/result: Focused tests passed; QEMU reached the description prompt but still did not write a save file.

  1813. Reported: “The staged sequence still did not create the file. I’m checking the capture again; this should distinguish ‘still in description prompt’ from ‘description accepted but another confirmation/error appeared.’”

    Action/result: Capture again showed the blank description prompt.

  1814. Reported: “It is consistently reaching the description prompt, but the description text is being sent before that editor is actually accepting keystrokes. I’m increasing the staged wait after the path Enter and slowing the key cadence a little.”

    Action/result: Longer description wait still left the prompt blank.

  1815. Reported: “Still no file. I’m checking whether the slower run left typed text on the prompt; if it is still blank, the issue may be that this editor is using the raw keyboard path and QEMU sendkey timing needs a different staging method.”

    Action/result: Capture remained blank, motivating a no-submit debug run.

  1816. Reported: “To separate ‘letters are ignored’ from ‘Enter submits and redraws the prompt before capture,’ I’m adding a debug switch that types the description without the final Enter. Then a capture can show whether the description line editor is actually receiving characters.”

    Action/result: Added --no-submit-description for staged-input debugging.

  1817. Reported: “The debug switch is in. I’m running a no-submit capture now: it should leave the typed description visible if the editor is accepting input.”

    Action/result: No-submit run reached the save-slot selector, proving an intermediate slot-selection stage was missing.

  1818. Reported: “As expected, the no-submit run did not create a file. I’m inspecting the capture now; this is purely to see whether abc reached the description input line.”

    Action/result: Capture showed the numbered save-slot selector rather than the description prompt.

  1819. Reported: “Aha. The staged capture shows the missing screen: after accepting the path, the engine displays the numbered slot selector. So the correct sequence is path Enter, slot Enter, then description text. I’m adding that middle stage to the probe.”

    Action/result: Added --slot-keys and --slot-wait staging.

  1820. Reported: “The tool now has the three stages the source/UI path needs: path prompt, slot selector, description prompt. I’m rerunning the probe with the new defaults.”

    Action/result: Three-stage run accepted the description and reached the final save confirmation screen.

  1821. Reported: “Still no save file. I’m inspecting this capture to see whether the staged sequence is now at the description prompt, the slot selector, or a save error message.”

    Action/result: Capture showed About to save the game described as: codex probe, so a final confirmation Enter was still needed.

  1822. Reported: “Good, now the description was accepted and the engine is showing its final ‘About to save’ confirmation. One more staged Enter is needed after that. I’m adding the confirmation stage.”

    Action/result: Added --confirmation-wait and --confirmation-keys.

  1823. Reported: “The confirmation stage is wired in. I’m rerunning the save probe with the default path/slot/description/confirmation sequence; this should be the actual original-engine save-write path now.”

    Action/result: Original engine wrote a save, but it was named SG.1 rather than the probe’s expected SQ2SG.1.

  1824. Reported: “No file yet after confirmation, so I’m checking whether the final Enter was too early, whether a disk/path error appeared, or whether the save wrote to a different directory/name than the extraction assumes.”

    Action/result: DOS directory listing showed SG.1 of size 2889 bytes, and the capture matched the validation screen.

  1825. Reported: “The save actually succeeded. The generated file is named SG.1, not SQ2SG.1, in this synthetic no-save fixture, and the validation view drew after the save returned. I’m updating the extractor to accept the observed save stem instead of hardcoding SQ2SG.”

    Action/result: Added --save-stem and changed the default extracted filename to SG.1.

  1826. Reported: “The extracted SG.1 parses cleanly: description codex probe, blocks [1505, 903, 328, 100, 12], size 2889 bytes. I’m turning the observed save stem into a CLI option so the probe report goes green instead of failing on the filename assumption.”

    Action/result: Confirmed the original-engine save output parses through tools/agi_save.py.

  1827. Reported: “The extractor now looks for SG.1 by default, with --save-stem available for other interpreter/game launches. I’m rerunning the same probe; this time it should extract, parse, and compare the post-save validation capture.”

    Action/result: save_roundtrip_007 passed with status match, parsed description codex probe, block lengths [1505, 903, 328, 100, 12], and 0 visual mismatches.

  1828. Reported: “That’s the dynamic save-write probe we wanted: original engine created SG.1, the parsed save envelope is valid, the description matches, and the post-save validation draw matched with zero visual mismatches. I’m documenting it and updating the progress tracker now.”

    Action/result: Began updating AGENTS.md, compatibility docs, clean-room notes, and PROGRESS.md.

  1829. Reported: “The compatibility chapter now records the dynamic save-write result and keeps the restore part honest as remaining work. I’m adding the detailed clean-room notes next, including the failed key-sequence discoveries because they explain the final staged UI driver.”

    Action/result: Updated docs/src/compatibility_testing.md and appended dynamic save-write evidence to docs/src/clean_room_executable_notes.md.

  1830. Reported: “The clean-room notes now include the full trail: wrong filename assumption, path prompt, slot selector, final confirmation, and the parsed SG.1 envelope. I’m updating PROGRESS.md so the save/write part no longer looks purely static.”

    Action/result: Updated PROGRESS.md to list dynamic original-engine save-write evidence and leave restore round-trip/file-error UI as remaining work.

  1831. Reported: “I’m catching up the progress log for the save probe now. There were several useful failed attempts, and they matter because they map the real save UI sequence: path prompt, slot selector, description prompt, confirmation.”

    Action/result: Added progress-log entries for the dynamic save-write probe work.

  1832. Reported: “The save-write documentation and tracker updates are done. I’m doing a quick code/readability pass now, then I’ll run the full verification suite again.”

    Action/result: Ran a Python syntax check over the new save probe and reviewed the new test file.

  1833. Reported: “The new save tool/test are still untracked, so they did not appear in git diff --stat; I’m checking status and cleaning any py_compile cache if it appeared.”

    Action/result: Found untracked tools/save_roundtrip_probe.py and tests/test_save_roundtrip_probe.py, plus generated __pycache__ directories from py_compile.

  1834. Reported: “py_compile left __pycache__ directories behind. I’m removing only those generated Python cache directories; they aren’t project artifacts.”

    Action/result: Removed generated tools/__pycache__ and tests/__pycache__.

  1835. Reported: “The generated caches are gone. I’m wrapping the new test imports/long line next so the added test file matches the style of the surrounding suite.”

    Action/result: Wrapped long imports and a long generated-save assertion in tests/test_save_roundtrip_probe.py.

  1836. Reported: “With the style cleanup done, I’m running the full verification set again: all unit tests, opcode evidence, and mdBook. After that I’ll do whitespace/status checks.”

    Action/result: python3 -B -m unittest discover -s tests passed 162 tests; python3 -B tools/logic_opcode_evidence.py --check passed; and mdbook build docs passed.

  1837. Reported: “Full verification passed: 162 tests, opcode evidence check, and mdBook. I’m doing the final diff whitespace and status pass now.”

    Action/result: git diff --check passed. Tracked changes plus the new save probe/test remain uncommitted; generated build/ and 002962_ms_dos_622/ remain untracked.

  1838. Reported: “The final checks are clean. I’m adding the last verification/status entries to progress_log.md so the documentation trail is complete for this turn.”

    Action/result: Completed progress-log updates for the verification pass.

  1839. Reported: “I’ll stage only the tracked project files plus the two new save-probe source files, leaving build/ and the DOS installer images untracked. Then I’ll commit with a concise message for the harness/doc work.”

    Action/result: Checked status before staging the save-write and chunked carousel work.

  1840. Reported: “The untracked generated directories are exactly the usual build/ and installer images. I’m staging the seven modified files and the two new save-probe files now.”

    Action/result: Staged AGENTS.md, PROGRESS.md, docs, carousel harness changes, and the new save probe/tool test.

  1841. Reported: “Staged. I’m checking the staged summary once before committing, mainly to make sure no generated files slipped in.”

    Action/result: Staged diff check passed; only source/docs/tests were staged.

  1842. Reported: “The staged set is clean: 9 source/doc files, no generated artifacts. I’m creating the commit now.”

    Action/result: Created commit 9a314b1 with message Add save write probe and chunk picture carousel.

  1843. Reported: “Commit succeeded. I’m doing one final status check so I can give you the exact commit hash and what remains untracked.”

    Action/result: Confirmed only build/ and 002962_ms_dos_622/ remained untracked after the commit.

  1844. Reported: “I’ll pick up from the committed state and use PROGRESS.md to choose the next highest-value gap. Given the last commit closed the save-write side, I expect the next useful target to be either restore round-trip/file-error UI or renderer/resource sweep expansion, but I’ll let the tracker and existing notes steer it.”

    Action/result: Read PROGRESS.md and selected dynamic restore round-trip work as the next target.

  1845. Reported: “PROGRESS.md points straight at restore round-trip now. I’m going to extend the save probe rather than start from scratch: use the generated SG.1, build a restore fixture whose LOGIC.0 has the same byte layout except 0x7e instead of 0x7d, then drive the original restore selector and check that the restored continuation draws the same validation view.”

    Action/result: Added restore fixture and restore-mode scaffolding to tools/save_roundtrip_probe.py.

  1846. Reported: “The restore mode is patched in. Before running QEMU, I’m checking the new control-flow block carefully; this tool has enough staged input that a small indentation bug would waste a boot cycle.”

    Action/result: Focused save-roundtrip tests passed and the restore-mode code was reviewed.

  1847. Reported: “The focused tests still pass, and the restore mode reads cleanly. I’m adding a couple of small tests for the new restore fixture path before I spend a QEMU run on it.”

    Action/result: Added tests for restore action byte layout, stale save removal, and restore fixture save-copy behavior.

  1848. Reported: “The restore fixture tests are in. I’m running the focused test module, then I’ll try restore mode using the SG_007.1 save generated by the previous probe.”

    Action/result: python3 -B -m unittest tests.test_save_roundtrip_probe passed 6 tests; confirmed build/save-roundtrip/SG_007.1 exists.

  1849. Reported: “Focused tests pass and the generated save is present. I’m running restore mode now with the same staged path/slot/confirmation timing; if restore has a different confirmation shape, the capture will tell us.”

    Action/result: First restore QEMU run parsed the generated save but mismatched visually.

  1850. Reported: “Restore mode selected and parsed the save, but the validation comparison mismatched. I’m opening the capture now to see whether restore reached a UI screen, resumed at a different instruction, or restored a different visible state than the save-return validation screen.”

    Action/result: Capture showed the restore path prompt, not restored game state.

  1851. Reported: “The restore capture is still at the restore path prompt, with the default \RSVT path visible. So restore’s selector is slower to become input-ready than save’s selector; I’m rerunning with a longer path-prompt wait before the first Enter.”

    Action/result: Longer path-prompt wait still left the capture at the restore path prompt.

  1852. Reported: “Still mismatched after the longer wait. I’m checking the second restore capture to see whether it advanced past the path prompt or landed on another restore UI stage.”

    Action/result: Second capture was identical to the path-prompt screen.

  1853. Reported: “The restore prompt still doesn’t leave the path screen. One plausible source-backed explanation is that the selector is not finding a save under the filename pattern it expects, so it returns to the path prompt. I’m checking the restore fixture disk contents and then I’ll try the same generated save under the SQ2SG.1 stem.”

    Action/result: Confirmed the restore fixture contained SG.1; prepared an alternate-stem restore run.

  1854. Reported: “The restore fixture contains SG.1, so I’m testing the alternate stem explicitly: copy the same generated save into the restore fixture as SQ2SG.1 and see whether the original restore selector advances.”

    Action/result: Restore run with --save-stem SQ2SG matched with 0 visual mismatches.

  1855. Reported: “That worked: with the generated save copied in as SQ2SG.1, restore mode matched the validation screen with 0 mismatches. So the round-trip is now: save action writes SG.1 in the synthetic fixture; restore selector accepts the same bytes when staged as SQ2SG.1. I’m documenting that observed stem asymmetry carefully rather than smoothing it over.”

    Action/result: Added restore command guidance to AGENTS.md.

  1856. Reported: “AGENTS.md now has both save and restore commands. I’m updating the compatibility docs and PROGRESS.md next so the tracker reflects an actual restore round-trip, with the filename-stem caveat preserved.”

    Action/result: Updated compatibility docs and PROGRESS.md with dynamic restore evidence and the unresolved stem asymmetry.

  1857. Reported: “The compatibility docs and progress dashboard now show the restore probe as complete evidence, while keeping the filename-stem asymmetry as the next source question. I’m adding the detailed clean-room notes for the restore attempt/results now.”

    Action/result: Appended dynamic restore probe notes to docs/src/clean_room_executable_notes.md.

  1858. Reported: “I noticed the previous save-write section still ended by saying restore was future work. Since the next section now supplies that restore evidence, I’m correcting that transition so the docs don’t contradict themselves.”

    Action/result: Corrected the save-write section transition.

  1859. Reported: “I’m updating docs/src/progress_log.md for the commit and this restore pass now. The important bits are the failed SG.1 restore attempt, the successful SQ2SG.1 restore, and the unresolved stem question.”

    Action/result: Recorded commit and restore-probe progress entries.

  1860. Reported: “With restore evidence recorded, the new highest-value item is the stem asymmetry itself. I’m switching back to source-first mode now: inspect the selector/read-summary code that formats save filenames, then use QEMU only if the disassembly leaves a genuine ambiguity.”

    Action/result: Began source-first inspection of save filename formatting and slot summary filtering.

  1861. Reported: “The selector source confirms that both save and restore go through the same numbered-slot helper, which calls the filename formatter at 0x5b73. The stem question is probably in that formatter or its backing data, so I’m reading 0x5b73 and the nearby save-name string buffers now.”

    Action/result: Read formatter 0x5b73 and backing strings around 0x1327..0x135f.

  1862. Reported: “I’m continuing from the restore probe work. First I’m tightening the source evidence around the save-name formatter, because that stem mismatch is now the most useful loose thread in PROGRESS.md.”

    Action/result: Confirmed formatter string %s%s%ssg.%d.

  1863. Reported: “The formatter is already looking much less mysterious: it uses the format string %s%s%ssg.%d, so the slot filename is built from a directory string, a separator string, a prefix string, and the slot number. I’m checking the backing strings and the prefix initializer next, because that should tell us why one run wrote SG.1 while restore selected SQ2SG.1.”

    Action/result: Identified path/separator strings and the prefix argument as pointer DS:0x0002.

  1864. Reported: “The formatter’s third %s argument is a pointer to data offset 0x0002, not a hard-coded SQ2 string at the call site. That makes the asymmetry a runtime-data question: what initializes DS:0x0002, and why did our save and restore harnesses see different effective prefixes?”

    Action/result: Shifted investigation to initialization and consumers of DS:0x0002.

  1865. Reported: “I found the important source-level detail: the save slot list reader rejects files unless the final saved state block begins with the same string at DS:0x0002. Since DS:0x0002 is also the filename prefix used before sg.N, restore scanning is tied to that runtime prefix, not just to the file bytes being structurally valid.”

    Action/result: Read code.save.read_slot_summary at 0x8b9f, including the first-block signature compare against DS:0x0002.

  1866. Reported: “Aha, this project already has a source note for a verifier/helper that copies a logic message into DS:0x0002 and checks it against embedded SQ2. I’m reading that path now, because it may be both the game-signature check and the save filename prefix initializer.”

    Action/result: Re-read action 0x8f and helper 0x5b49; confirmed the SQ2 signature guard writes/checks DS:0x0002.

  1867. Reported: “That explains half the mismatch cleanly: our synthetic save fixture did not necessarily run the normal 0x8f signature action before saving, so an empty prefix naturally produces SG.1. The surprising half is the restore fixture’s SQ2SG.1 behavior, so I’m checking the generated fixture bytecode and launcher path instead of guessing.”

    Action/result: Confirmed the old generated fixture bytecode did not call 0x8f.

  1868. Reported: “The generated save/restore logic is intentionally tiny and does not call 0x8f; I hit a tool assumption while trying to disassemble the fixture directly, so I’m reading the local disassembler/resource helpers before deciding whether to add a small fixture disassembly path or inspect the bytes by hand.”

    Action/result: Inspected fixture VOL.3 bytes directly and confirmed the old save/restore payloads differed only by action byte.

  1869. Reported: “The fixture bytecode confirms no signature opcode: it draws picture 0, loads view 11, performs 0x7d or 0x7e, then draws the validation object. So if restore required SQ2SG.1, some earlier startup path or copied runtime state is setting DS:0x0002 before logic 0 runs.”

    Action/result: Rechecked launcher files and runtime setup as possible sources of prefix state.

  1870. Reported: “One more clue: the copied launcher is SIERRA.COM, but its embedded strings include _sq2.* and agidata.ovl; the batch file is present but probably unused by our harness. I’m narrowing the launch sequence and startup initialization now, because the prefix is probably set before our synthetic logic 0 gets control.”

    Action/result: Confirmed the harness launches SIERRA; no direct launcher conclusion was needed after later source/QEMU corrections.

  1871. Reported: “The selector source makes the restore result fit if DS:0x0002 == \"SQ2\" at selection time: it scans SQ2SG.N, then verifies the first seven bytes of the first saved state block against that same string. I’m rechecking the saved disk image now, because if save really only produced SG.1, then save and restore saw different prefix state and we need to know why.”

    Action/result: Verified old save_roundtrip_007 wrote only SG.1.

  1872. Reported: “The generated SG.1 state block starts with zero bytes, so the save run really did have an empty/zeroed DS:0x0002 when it wrote state. The successful restore from SQ2SG.1 means the restore run was also compatible with that zeroed state by the time it validated/drew, so I’m checking the report and capture timing before treating the stem behavior as a source rule.”

    Action/result: Found the old restore validation was visually ambiguous.

  1873. Reported: “There’s a subtle flaw in the current restore validation: the post-restore visual can also happen if the restore action simply returns to the following bytecode. I’m going to make the probe distinguish success from cancel/failure by saving one variable value and using a different pre-restore value in the restore fixture; then the final object position tells us whether state was actually restored.”

    Action/result: Planned a stronger X=50/X=90 restore oracle.

  1874. Reported: “I’m changing the fixture so the visual check is no longer just “did execution continue?” The save fixture stores marker X=50 in a byte variable; the restore fixture starts with X=90, and only a real restore should put X back to 50 before the final draw.”

    Action/result: Patched tools/save_roundtrip_probe.py to draw validation from variables.

  1875. Reported: “The fixture code is patched; I’m cleaning up the tests now so they assert the stronger restore/cancel distinction instead of the old immediate-draw shape.”

    Action/result: Updated focused save/restore fixture tests.

  1876. Reported: “I caught a small test-shape issue before running it: the marker assignment is followed by other setup assignments before the save/restore action, so the assertion should look for the assignment itself and the later action separately.”

    Action/result: Corrected the test assertions.

  1877. Reported: “The stronger fixture shape is in place. I’m running the focused save-probe tests first, then I’ll regenerate the save and rerun the restore cases so the QEMU evidence matches the new oracle.”

    Action/result: Focused tests.test_save_roundtrip_probe passed.

  1878. Reported: “Focused tests passed. I’m starting with a fresh save run now, because the old SG_007.1 was produced by the weaker fixture and can’t prove the restored-variable marker.”

    Action/result: Generated SG_008.1; it still wrote a blank-prefix save and matched the save-side visual check.

  1879. Reported: “The fresh save run matched and still wrote SG.1. I’m now running two restore variants against the stronger marker: first with the generated file staged as SG.1, then as SQ2SG.1, so we can tell whether either path truly restores state rather than merely continuing.”

    Action/result: SG.1 restore stayed mismatched.

  1880. Reported: “I checked the saved state block and initially used the absolute data offset without subtracting the block base. Correcting that, the marker variable is inside the saved first block as intended; I’m running the restore probes now.”

    Action/result: Confirmed saved validation variables include X=50.

  1881. Reported: “The SG.1 restore variant still mismatched, so it did not reach the restored marker. I’m running the SQ2SG.1 variant with the stronger variable marker now; this will tell us whether the earlier success was a real restore or just a continuation artifact.”

    Action/result: SQ2SG.1 restore drew the unrestored X=90 marker.

  1882. Reported: “Good, the stronger oracle exposed the old false positive: SQ2SG.1 no longer matches restored-state X=50. Its mismatch is much smaller, which likely means the action returned and drew the unrestored X=90 marker. I’m quantifying that against both expected positions.”

    Action/result: Compared captures against X=50 and X=90; SQ2SG.1 matched X=90 exactly.

  1883. Reported: “The image conversions are done; the comparison script is still crunching a full-frame Python comparison because that path is slower than I expected. I’m polling it now, then I’ll fold the result back into the docs and tracker.”

    Action/result: Comparison completed and confirmed the X=90 false positive.

  1884. Reported: “The stronger comparison is decisive: the SQ2SG.1 run drew the unrestored X=90 marker exactly, so the previous restore “match” was a false positive. I’m checking the captures and slot-selection source now to see whether the file is rejected during candidate scanning or whether our key timing is causing a cancel path.”

    Action/result: Inspected captures and returned to selector source.

  1885. Reported: “The clean way forward is to make the fixture behave like normal SQ2 logic: call 0x8f verify_game_signature with a local SQ2 message before save/restore. That initializes DS:0x0002, which source shows is both the filename prefix and the saved-state signature, so I’m updating the harness default to use SQ2SG.N and keeping the stronger variable oracle.”

    Action/result: Patched the harness to call 0x8f("SQ2") and default to SQ2SG.

  1886. Reported: “The harness now calls 0x8f and defaults to SQ2SG. I’m adding test assertions for the signature action so this doesn’t accidentally regress back to the ambiguous no-prefix fixture.”

    Action/result: Added test assertions for 0x8f 01.

  1887. Reported: “The signature assertions are in; I’m rerunning the focused tests and then the QEMU save/restore pair with the corrected default stem.”

    Action/result: Focused tests passed.

  1888. Reported: “Focused tests pass again. I’m generating a new signed-prefix save now; if the source interpretation is right, this one should be named SQ2SG.1 and its first state block should begin with SQ2.”

    Action/result: Generated SQ2SG_009.1; save block began SQ2\0.

  1889. Reported: “That worked: the corrected fixture wrote SQ2SG.1. I’m checking the saved prefix bytes and then immediately using that file for a restore run with the variable-position oracle.”

    Action/result: Confirmed SQ2SG.1, block lengths, prefix bytes, and validation variables.

  1890. Reported: “The saved block now begins SQ2\0, and the marker variable is saved as X=50. I’m running the restore with SQ2SG.1; this should only match if the original engine actually restores the saved variable state.”

    Action/result: First signed-prefix restore remained at the path prompt.

  1891. Reported: “The signed-prefix restore still did not match, so the remaining problem is not just the filename/signature. I’m opening the capture now; then I’ll adjust the harness around the selector flow rather than changing the spec based on a failed interaction.”

    Action/result: Capture showed the restore path prompt.

  1892. Reported: “The capture is still at the path prompt, so this specific run simply missed or failed the path-entry Enter. Since the save side used the same line-editor path successfully, I’m rerunning restore with a longer path-prompt delay before drawing any conclusion from it.”

    Action/result: Longer delay still did not complete restore.

  1893. Reported: “Longer delay did not solve it. I’m checking whether the path validator is rejecting the default current directory in restore mode, then I’ll either feed an explicit path string or adjust the fixture to bypass the path prompt using source-supported state.”

    Action/result: Re-read path/selector source.

  1894. Reported: “The selector source confirms that when restore finds zero valid candidates and no saved-description filter, it displays a “no games to restore” message and returns zero. Since our capture remains at the path edit itself, I’m checking the key injection helper and trying an explicit path/Enter sequence next.”

    Action/result: Inspected QEMU monitor key injection and restore fixture bytes.

  1895. Reported: “The fixture bytes look right: 0x8f 01, marker X=90 before restore, then variable draw. I’m going to try a deliberately redundant Enter sequence through the path/slot prompts; if that moves past the prompt, it confirms this is a harness timing/input issue rather than a resource or source-semantics issue.”

    Action/result: Initial redundant-Enter run hit a sandboxed VNC bind failure.

  1896. Reported: “That attempt hit the sandboxed VNC bind rather than engine behavior. I’m rerunning the same QEMU restore command outside the sandbox so the emulator can bind its local display socket.”

    Action/result: Rerun completed and drew the X=90 failure path.

  1897. Reported: “The redundant keys moved past the path prompt but still drew X=90, so restore did not load the saved variable state. I’m comparing that capture to X=50/X=90 and then going back to the restore handler source around the open/read/return paths.”

    Action/result: Confirmed the capture matched X=90 exactly.

  1898. Reported: “I noticed one of the restore-handler disassemblies was shifted by using the wrong executable-header skip. I’m rerunning that source read with the correct image+0x200 skip before drawing conclusions from those addresses.”

    Action/result: Correctly disassembled restore action at 0x2512.

  1899. Reported: “The restore handler source explains the oracle problem: a successful restore returns zero and ends the current logic stream, so code immediately after 0x7e is a failure/cancel path, not a success path. I’m changing the fixture again: save will set a flag plus marker variables, and restore logic will draw the success marker only when the restored flag is present on the next cycle.”

    Action/result: Planned the final restored-flag branch oracle.

  1900. Reported: “That patch missed the exact import layout. I’m reading the top of the file and applying the same change against the current shape.”

    Action/result: Reapplied the restored-flag branch patch successfully.

  1901. Reported: “The restore fixture now has a “restored state” branch at the top, so its payload is no longer the same length as the save fixture. I’m updating the tests to assert the branch and the failure marker separately.”

    Action/result: Updated focused tests for the restored-flag branch.

  1902. Reported: “The tests now describe the intended restore oracle: save sets the restored marker flag, and restore has a top-level branch for that flag. I’m running them before regenerating the save again.”

    Action/result: Focused tests passed.

  1903. Reported: “Focused tests pass. I’m generating a new save with the restored-state flag included, then I’ll run restore against that save.”

    Action/result: Generated SQ2SG_010.1, matching the save-side visual check.

  1904. Reported: “The new save wrote and matched. I’m checking the saved flag/variable bytes, then restoring with the redundant prompt keys because that was the sequence that reliably moved through the selector.”

    Action/result: Confirmed saved X variable was 50; noted the flag is packed rather than a direct byte slot.

  1905. Reported: “The saved X variable is definitely 50; the flag lives in a packed flag area rather than a one-byte slot, so that quick byte check was not meaningful. I’m running the restore now; the branch itself will be the real flag proof.”

    Action/result: First restore run was blocked by sandboxed VNC bind.

  1906. Reported: “The sandbox blocked the VNC socket again; I’m rerunning the same restore probe outside the sandbox.”

    Action/result: restore_roundtrip_sq2stem_006 matched with 0 visual mismatches, proving actual restored state through the restored flag and variables.

  1907. Reported: “Now we have the real restore round trip: signed-prefix save writes SQ2SG.1, the saved block starts with SQ2, and the restore probe only matches after the restored flag/variable branch draws X=50. I’m updating the docs and tracker to replace the earlier ambiguous restore note with this stronger evidence.”

    Action/result: Began doc/tracker updates for the corrected restore evidence.

  1908. Reported: “I’ve got the documentation targets. I’m going to update them in one pass: AGENTS commands, compatibility evidence, clean-room notes, executable/source spec, symbolic data labels, PROGRESS, and the progress log.”

    Action/result: Updated AGENTS.md and compatibility docs with the signed-prefix save/restore commands.

  1909. Reported: “The historical notes now preserve the false start and the corrected oracle. Next I’m tightening the implementation-facing source description: filename prefix, slot signature filtering, and restore-success continuation.”

    Action/result: Updated docs/src/clean_room_executable_notes.md and docs/src/agi_executable.md.

  1910. Reported: “I’m adding the stable labels for this now: the slot filename formatter and the DS:0x0002 signature/prefix buffer are important for future cross-version comparisons.”

    Action/result: Added symbolic labels for code.save.format_slot_filename and data.save.signature_prefix_0002.

  1911. Reported: “The symbol map is updated. I’m removing the stem asymmetry from the “highest value” queue now, because source plus the corrected QEMU run explain it: blank 0x0002 produced the old SG.1, while 0x8f(\"SQ2\") produces and validates SQ2SG.1.”

    Action/result: Updated PROGRESS.md; next save/restore item is a representative file-error UI probe.

  1912. Reported: “PROGRESS.md now points at file-error UI as the next save/restore task. I’m adding one short implementation-facing note to the runtime model so the DS:0x0002 prefix/signature rule is visible outside the low-level executable chapter.”

    Action/result: Added the save filename/signature prefix row to docs/src/runtime_model.md.

  1913. Reported: “I’m resuming from the PROGRESS-guided thread and picking up the file-error UI probe work first. I’ll keep this source-led and use QEMU as the representative observable check once the fixture is in place.”

    Action/result: Confirmed the tree was clean apart from generated artifacts and selected the save/restore file-error probe from PROGRESS.md.

  1914. Reported: “I’ve verified the tree is clean apart from generated inputs/artifacts, and the current tracker really does point to save/restore failure behavior as the next high-value gap. I’m patching the existing save-roundtrip harness rather than creating a one-off script, so the evidence stays reusable for later interpreter versions.”

    Action/result: Inspected tools/save_roundtrip_probe.py, tests/test_save_roundtrip_probe.py, and the existing source-backed save/restore docs.

  1915. Reported: “The fixture shape is now clear from the source path: a truncated save can still be listed by the restore selector if its 31-byte description and first seven signature bytes look right. The actual restore then asks the block reader for the full first block and trips the documented read-error path.”

    Action/result: Added restore-read-error fixture support to tools/save_roundtrip_probe.py.

  1916. Reported: “The harness now has a first-class restore-read-error mode and unit coverage for the malformed-but-selector-visible save envelope. I’m running the focused tests before spending QEMU time on it.”

    Action/result: python3 -B -m unittest tests.test_save_roundtrip_probe passed with 8 tests.

  1917. Reported: “The source-backed fixture is ready, and the narrow test suite is green. I’m launching the original engine with that fixture now; if the local VNC bind is sandboxed again, I’ll rerun the same command with approval escalation.”

    Action/result: Sandboxed QEMU launch failed to bind the local VNC socket, as expected.

  1918. Reported: “As expected, the sandbox blocked QEMU’s VNC socket, not the probe itself. I’m rerunning the exact same original-engine check outside the sandbox so we can get the capture.”

    Action/result: The broad-key restore-read-error probe completed, but the capture showed DOS because redundant Enters dismissed the fatal dialog.

  1919. Reported: “The original-engine run completed and produced a stable capture: the 40-byte save fixture was accepted by the selector, then the restore read failed on the declared 1505-byte first block. I’m converting and inspecting the image so the docs can record exactly what was observed.”

    Action/result: Converted and inspected restore_read_error_001; the image showed the final DOS prompt rather than the error dialog.

  1920. Reported: “The first capture landed back at DOS, which is useful but not the UI evidence I wanted. I’m tightening the capture timing now to see whether the engine displays the restore-read error briefly before terminating, or whether this path goes straight through cleanup too quickly for a static end-state screenshot.”

    Action/result: A short final capture still landed at DOS, indicating the key sequence rather than the wait duration needed correction.

  1921. Reported: “The short capture still shows DOS, which suggests the failure path exits before the current harness’s final screenshot point. I’m going to trace the prompt timing with intermediate screenshots rather than guess from the final state.”

    Action/result: Started one-key-at-a-time restore UI timing captures.

  1922. Reported: “I’m narrowing where the interaction actually is: first I’ll capture the restore prompt before sending any keys, then I’ll advance one key at a time if needed. That should separate “we missed a brief error” from “the prompt sequence is different for this failure fixture.””

    Action/result: Captured the restore directory prompt with the default fixture directory in the input field.

  1923. Reported: “Good, the first prompt is visible and waiting for the restore directory. The default path field is the fixture directory, so I’m sending a single Enter next and capturing before any slot-selection keys.”

    Action/result: Captured the selector listing the truncated save as codex probe.

  1924. Reported: “The selector lists the truncated save as expected, which proves the malformed file passed the source-backed signature scan. I’m sending the slot-selection Enter next and capturing immediately after that transition.”

    Action/result: Captured the restore confirmation dialog naming \rera\sq2sg.1.

  1925. Reported: “After slot selection, the engine shows the confirmation dialog with the truncated save’s filename. I’m now using the exact one-Enter-per-stage sequence; this avoids the earlier extra keys that were spilling into DOS after the fatal restore path exited.”

    Action/result: Captured the restore-read error dialog: Error in restoring game. Press ENTER to quit.

  1926. Reported: “That nailed it: with exactly one Enter at directory, slot, and confirmation, the screenshot is the restore failure dialog: “Error in restoring game. Press ENTER to quit.” I’m doing one stable timed run now with a normal wait, then I’ll fold the command and hashes into the docs and tracker.”

    Action/result: Stable 8-second capture restore_read_error_002.ppm matched the quick dialog capture with RGB SHA-256 556971f26fc34deb32497a9d10c08eedeb28f6bdb0957cd7676a8ef26830849c.

  1927. Reported: “The stable run matches the quick capture exactly: same hash and same dialog geometry after an 8-second wait, so this failure dialog is persistent until Enter. I’m updating the reusable commands, evidence notes, compatibility chapter, and PROGRESS.md now.”

    Action/result: Updated AGENTS.md, compatibility notes, clean-room executable notes, and PROGRESS.md for the restore-read-error probe.

  1928. Reported: “The capture sequence also documents the UI lifecycle: directory prompt, save selector row, restore confirmation, then the fatal restore-read dialog. I’m making the docs reflect that nuance because it matters for compatibility tests.”

    Action/result: Added prompt-sequence details and the stable capture hash to docs/src/compatibility_testing.md and docs/src/clean_room_executable_notes.md.

  1929. Reported: “The compatibility command should use single Enter keys, not the broader redundant key sequence used for the success restore. I’m recording that explicitly so future runs don’t accidentally advance past the error dialog into DOS.”

    Action/result: Added the single-Enter restore-read-error command to AGENTS.md.

  1930. Reported: “I’m also updating PROGRESS.md so the dashboard stops advertising this as remaining work. The next queue item after this will be graphics/view compatibility and spec polish unless the verification pass uncovers a regression.”

    Action/result: Removed the representative file-error UI probe from the highest-value remaining-work list.

  1931. Reported: “The tracker is updated: the file-error UI probe has moved from “remaining” to evidence. I’m adding the historical log entries for the QEMU attempts and then I’ll run the full verification suite for these harness/docs changes.”

    Action/result: Completed progress-log entries through the stable restore-read-error capture.

  1932. Reported: “The file-error work is documented and logged. I’m running the standard local verification stack now: unit tests, opcode evidence check, mdBook build, and whitespace diff check.”

    Action/result: Verification passed: 166 unit tests, opcode evidence check, mdBook build, and git diff --check.

  1933. Reported: “Verification is clean: 166 unit tests passed, opcode evidence check passed, mdBook built, and git diff --check found no whitespace issues. I’m checking the resulting diff now, then I’ll continue down the next PROGRESS.md item.”

    Action/result: Reviewed the diff and cleaned up long import/test lines.

  1934. Reported: “Readability cleanup is done. I’m rerunning the same verification commands so the current working tree, not just the previous one, is known good.”

    Action/result: Verification passed again with the same command set.

  1935. Reported: “The refreshed verification pass is clean again: 166 tests, opcode evidence, mdBook, and diff whitespace all pass. I’m moving to the next remaining item in PROGRESS.md, which is picture/view renderer compatibility work.”

    Action/result: Re-read the graphics/view/object coverage and identified that pattern and object-overlay edge cases are already broadly covered.

  1936. Reported: “The first remaining item is explicitly optional and already says those opcodes are covered unless a representative dynamic fixture is worth adding. I’m treating the concrete next work as graphics compatibility: source/test review first, then one focused edge-case addition rather than random probing.”

    Action/result: Inspected picture fuzz, renderer tests, and current picture/view tracker entries.

  1937. Reported: “The picture side already has broad real-resource parity; the remaining useful work is the thin edge around valid synthetic streams. I’m looking at the renderer and fuzz corpus together so the next addition is source-aligned and testable.”

    Action/result: Re-read pattern renderer tests, base fuzz cases, and related source-backed documentation.

  1938. Reported: “One candidate edge is random pattern plotting state: it has both a seed byte and per-plot mask logic, and those semantics are easy to accidentally model incorrectly. I’m checking the disassembly around the pattern handler before adding or changing any fixture.”

    Action/result: Disassembly at 0x64ff..0x6602 confirmed the existing pattern-mode and random-seed model already matches documented tests and QEMU coverage.

  1939. Reported: “The pattern/channel cases are already QEMU-covered, including the base_027..029 channel-mask trio. I’m shifting to the view/object side of the same graphics item, where the tracker still calls out priority/control combinations and placement semantics.”

    Action/result: Inspected tools/view_batch.py, tests/test_view_batch.py, and compatibility notes for view/object coverage.

  1940. Reported: “The view batch already covers clipping and transparency stress, while object overlay probes carry the actual priority/control visibility semantics. I’m reading that harness next; it looks like the best place to add a focused priority-band fixture if there’s a real gap.”

    Action/result: Confirmed the object overlay harness already covers threshold pairs, low/high nibble behavior, auto-priority rebuilds, persistent objects, and selected group/frame offsets.

  1941. Reported: “The object overlay suite is already broader than the tracker wording: it has threshold pairs, low/high nibble priority checks, auto-priority rebuilds, persistent objects, and selected group/frame offsets. Rather than duplicate that, I’m taking the next concrete tracker item: make save/file selection more implementation-ready in the spec.”

    Action/result: Switched from adding duplicate graphics cases to improving the save/file selector spec.

  1942. Reported: “The save section has the facts, but it is still compressed into a few table rows. I’m going back to the selector disassembly and the existing source notes, then I’ll turn that into an explicit implementation-facing state machine.”

    Action/result: Re-read selector/path/slot disassembly around 0x85e5, 0x8705, 0x8814, and 0x8b9f.

  1943. Reported: “The disassembly confirms the selector is a small UI state machine around path editing, slot scanning, list interaction, optional save-description editing, and caller-side file I/O. I’m adding that model now, with the uncertain internals kept out of the normative contract.”

    Action/result: Added a save/restore selector state-machine table to docs/src/runtime_model.md.

  1944. Reported: “The runtime model now has a selector state machine instead of just a compact data row. I’m updating the dashboard to reflect that save/file selection has moved forward, then I’ll verify the docs build again.”

    Action/result: Updated PROGRESS.md to remove save/file selection from the remaining implementation-ready state-machine examples.

  1945. Reported: “The remaining spec-polish examples are now sound hardware output, heap initialization, and menu movement/event delivery. I’m adding the corresponding progress-log entries and then running the documentation checks.”

    Action/result: Ran mdbook build docs and git diff --check; both passed.

  1946. Reported: “The latest docs build and whitespace check pass. The remaining implementation-spec examples are now sound hardware output, heap initialization, and menu movement/event delivery; I’m taking heap initialization next because it is most likely to be source-complete without fragile hardware/QEMU behavior.”

    Action/result: Began heap initialization source review.

  1947. Reported: “The heap notes cover allocation/reset well, but initialization is still thin. I’m searching for the startup writes to the heap globals rather than inferring them from the later allocator helpers.”

    Action/result: Found writes to heap globals around raw/image offsets 0x1644, 0x1669, 0x1696, and 0x4420..0x4431.

  1948. Reported: “The byte search found startup writes to the heap globals around raw offsets 0x1644, 0x1669, 0x1696, and 0x4420..0x4431. I’m disassembling those local regions now to see which are true initialization sites versus later helper writes.”

    Action/result: Disassembly showed 0x1644, 0x1669, and 0x1696 are helper writes, while startup memory allocation at 0x43ea..0x4431 seeds heap current/base/limit.

  1949. Reported: “The startup allocation path is source-backed now: DOS AH=48h returns a memory segment, the interpreter converts it into a DS-relative byte offset, seeds heap current/base from that, and computes the limit from the requested paragraph count. I’m documenting that as the missing heap-initialization contract.”

    Action/result: Added code.startup.allocate_runtime_memory and clarified heap global labels in docs/src/symbolic_labels.md.

  1950. Reported: “The runtime model and symbol map now explain where heap base/current/limit come from, not just how later allocation uses them. I’m adding the clean-room evidence note with the exact searches and disassembly observations.”

    Action/result: Added a heap startup initialization source-pass section to docs/src/clean_room_executable_notes.md.

  1951. Reported: “The clean-room notes now show the exact startup allocator evidence. I’m updating PROGRESS.md so heap initialization is no longer listed as an open implementation-spec item.”

    Action/result: Updated PROGRESS.md; heap initialization is now evidence, leaving only optional observable out-of-memory UI behavior for the memory subsystem.

  1952. Reported: “Docs and whitespace are clean. I’m moving to sound hardware output next, but I’ll keep it source-first: identify what the interpreter hands to the hardware driver, and avoid trying to declare PC-speaker electrical behavior unless the executable itself makes it clear.”

    Action/result: Began source-first sound driver output review.

  1953. Reported: “The sound model already covers stream parsing and scheduling; the gap is the low-level write helpers. I’m disassembling driver_write_tone and driver_write_attenuation now to decide what can be specified portably.”

    Action/result: Disassembled the sound driver start/tick/stop/tone and attenuation write helpers around raw/image regions 0x81a0..0x83f6.

  1954. Reported: “The driver output path is now clearer: PC-speaker-like selectors use PIT ports 0x43/0x42/0x61, while other selectors write encoded bytes to port 0xc0. I’m going to capture the portable part in code/tests too: the source-backed PC-speaker divisor formula.”

    Action/result: Added pc_speaker_divisor() and pc_speaker_event_enabled() to tools/agi_sound.py.

  1955. Reported: “I added a small source-backed helper for the PC-speaker divisor formula and the simple attenuation gate. That gives the spec a tested foothold for hardware output without pretending we have full audible synthesis yet.”

    Action/result: Added a sound-resource unit test for sound 1’s first event divisor 10560 and its silent attenuation nibble.

  1956. Reported: “The sound-resource tests now include the hardware-facing divisor/gate check. I’m running that focused suite before updating the spec text.”

    Action/result: python3 -B -m unittest tests.test_sound_resources passed with 10 tests.

  1957. Reported: “The focused sound tests pass with 10 tests. I’m updating the sound runtime model now: event stream semantics, PC-speaker divisor formula, silence gate, and the separate port-0xc0 path for other hardware selectors.”

    Action/result: Updated docs/src/runtime_model.md with the hardware-output driver-interface model.

  1958. Reported: “The runtime model now distinguishes portable sound semantics from hardware port writes. I’m recording the driver-output source pass in the clean-room notes with the disassembly ranges and the new test coverage.”

    Action/result: Added a sound hardware-output source-pass section to docs/src/clean_room_executable_notes.md.

  1959. Reported: “The evidence note captures what the port writers do without turning it into an overbroad audio spec. I’m tightening the symbol-map descriptions for the two sound driver helpers to preserve the same cross-version labels.”

    Action/result: Updated code.sound.driver_write_tone and code.sound.driver_write_attenuation notes in docs/src/symbolic_labels.md.

  1960. Reported: “The symbol map now has the port-writer details attached to the existing labels. I’m updating the compatibility chapter and tracker next so sound hardware output is no longer listed as an open spec-polish example.”

    Action/result: Updated compatibility text and PROGRESS.md for the source-backed sound hardware-output model.

  1961. Reported: “The highest-value spec-polish queue is down to menu movement/event delivery. I’m logging the sound-output work and then running the focused sound tests plus docs checks again.”

    Action/result: Finished the sound-output evidence updates and prepared for verification.

  1962. Reported: “The sound work is logged. Since this touched code and docs, I’m running the full local verification stack again, not just the focused sound suite.”

    Action/result: Ran the full verification stack for the sound changes.

  1963. Reported: “Full verification is clean: 167 tests now pass, opcode evidence passes, mdBook builds, and diff whitespace is clean. I’m moving to the last named implementation-spec item: menu movement/event delivery.”

    Action/result: Confirmed the last full verification pass and switched to menu interaction analysis.

  1964. Reported: “I’m picking up from the verified sound-output pass and moving into the remaining menu interaction item in PROGRESS.md. First I’m re-reading the existing menu notes and labels so the next edits are anchored in the disassembly trail, not just the test harness history.”

    Action/result: Re-read menu opcode prose, existing clean-room notes, symbolic labels, compatibility notes, and PROGRESS.md.

  1965. Reported: “The existing menu chapter already has the core dispatch table, so the gap is not “what does each arrow do?” so much as “what would an implementer build from this?” I’m going to turn that into a runtime state-machine description and then reassess PROGRESS.md for the next highest-value item.”

    Action/result: Compared the existing menu evidence against docs/src/runtime_model.md and identified the missing implementation model.

  1966. Reported: “For the menu work I’m doing one source reread of code.menu.interact itself before editing. The documentation already says the dispatch table result; I want the state-machine text to reflect the actual loop shape and persistence points.”

    Action/result: Disassembled the menu setup and interaction regions and rechecked the local AGIDATA raw-key movement table.

  1967. Reported: “The source reread confirms the useful implementation shape: menu setup owns circular heading/item lists, interaction is a modal event loop, and selection only fires on Enter if the current item’s enable word is nonzero. I’m adding that as a data model plus lifecycle, with the failed QEMU arrow probe still treated as a test gap rather than a spec blocker.”

    Action/result: Added menu/list data-model and interaction-lifecycle tables to docs/src/runtime_model.md.

  1968. Reported: “The runtime model now has the menu lifecycle in implementer-facing form. I’m tightening the tracker and symbolic label file next so future cross-version work has stable names for the newly documented helper/state fields.”

    Action/result: Added code.menu.remember_item_and_restore_rect plus menu saved-rectangle globals to docs/src/symbolic_labels.md.

  1969. Reported: “Tiny correction while editing the label map: the existing navigation row uses “last item” for movement value 4, so I’m patching against the actual current text.”

    Action/result: Applied the symbolic-label update against the current table text.

  1970. Reported: “The symbol map has the helper and rectangle globals now. I’m updating PROGRESS.md so menu movement is marked as implementation-specified from source, while leaving arrow-key dynamic validation as an optional compatibility-suite gap.”

    Action/result: Updated PROGRESS.md and docs/src/compatibility_testing.md to reflect source-backed menu movement semantics and remaining optional dynamic validation.

  1971. Reported: “The menu item is now closed at the spec level: source-backed lifecycle, labels, tracker, and compatibility notes are aligned. I’m running the docs checks for this docs-only pass, then I’ll pick the next item from PROGRESS.md.”

    Action/result: mdbook build docs, git diff --check, and python3 -B tools/logic_opcode_evidence.py --check all passed.

  1972. Reported: “The menu docs pass is clean: mdBook builds, opcode evidence still checks, and whitespace is clean. Next I’m moving to the first concrete remaining renderer item: picture/view compatibility and implementation-ready edge semantics.”

    Action/result: Began reviewing picture/view renderer documentation and test coverage against PROGRESS.md.

  1973. Reported: “One search was noisy because I asked for a non-existent chapter name and rg obediently threw the whole kitchen at me. The useful signal is in graphics_object_pipeline.md, so I’m narrowing to the renderer/model sections now.”

    Action/result: Re-read the graphics pipeline and compatibility sections covering picture fuzz, view rendering, and object overlay coverage.

  1974. Reported: “The renderer docs show a good next target: SQ2 real pictures never use corner-path commands 0xf4/0xf5, so they’re easy to under-specify unless synthetic fixtures cover them. I’m inspecting the fuzz corpus generator and tests to see whether those paths are already pinned down or need curated cases.”

    Action/result: Confirmed existing base cases already cover 0xf4 and 0xf5 and that QEMU evidence exists for the safe base cases through base_020.

  1975. Reported: “Corner paths are already in the base corpus and have been through the “all safe base cases through base_020” QEMU batch. The more useful next bit is to add explicit implementation tests for the source-described command termination behavior and channel interleavings around those paths.”

    Action/result: Chose command-byte scanner-resume as the next picture compatibility expansion.

  1976. Reported: “I’m going to add three curated, safe picture fuzz cases around command-byte termination and scanner resume. These are the sort of edge cases a future engine can get wrong even when real SQ2 pictures happen not to stress them.”

    Action/result: Added base_030_line_pair_command_resume, base_031_corner_command_resume, and base_032_fill_command_resume to tools/picture_fuzz.py.

  1977. Reported: “The new corpus entries are in place. I’m adding focused local assertions so the Python renderer’s scanner behavior is locked down independently of QEMU.”

    Action/result: Added local renderer and fuzz-corpus tests for the three new command-resume cases.

  1978. Reported: “The local tests now pin the three scanner-resume contracts: incomplete coordinate pair, corner path, and seed-fill point list. I’m running the focused graphics/fuzz suites before asking QEMU for the original-engine comparison.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzz passed with 51 tests; regenerated the fuzz corpus with 1,057 cases and 1,055 safe for QEMU.

  1979. Reported: “Focused local tests pass, and the regenerated corpus now has 1,057 cases with 1,055 marked safe for QEMU. I’m running just the three new curated cases through the snapshot harness so this becomes original-engine evidence, not only a local assertion.”

    Action/result: Started QEMU snapshot batch command_resume_001.

  1980. Reported: “QEMU is running the three-case snapshot batch now. This is one of the places where the snapshot path earns its keep: one boot, three renderer edge cases.”

    Action/result: QEMU completed command_resume_001 with 3 matches, 0 mismatches, and 0 errors.

  1981. Reported: “The QEMU batch matched all three new cases with zero mismatches. I’m folding that evidence into the docs and tracker, including the updated corpus counts.”

    Action/result: Updated docs/src/graphics_object_pipeline.md, docs/src/compatibility_testing.md, docs/src/clean_room_executable_notes.md, and PROGRESS.md with command-resume evidence and corpus counts.

  1982. Reported: “The graphics chapter and compatibility chapter now record the scanner-resume evidence, and PROGRESS.md reflects the expanded fuzz corpus. I’m updating the progress log before running the broader verification stack.”

    Action/result: Updated this progress log for the menu and picture scanner-resume work.

  1983. Reported: “The log is caught up through the QEMU batch. Since this touched code, tests, and docs, I’m running the full local verification stack now.”

    Action/result: python3 -B -m unittest discover -s tests passed with 171 tests; python3 -B tools/logic_opcode_evidence.py --check, mdbook build docs, and git diff --check also passed.

  1984. Reported: “Full verification is clean after the scanner-resume expansion: 171 tests, opcode evidence, mdBook, and diff whitespace all pass. I’m logging that and then moving to the next renderer-suite item: whether the view/object sweeps can use the faster carousel strategy too.”

    Action/result: Recorded the verification result and began reviewing the existing view/object batch harnesses for possible carousel reuse.

  1985. Reported: “The existing picture carousel already solved the “one engine process, many resources” problem for pictures. I’m comparing it with the current view batch harness to see whether a view carousel can be built without disturbing the simpler reference snapshot tests.”

    Action/result: Re-read tools/view_batch.py, tools/picture_carousel.py, and the view-batch tests.

  1986. Reported: “A view carousel is feasible without replacing the existing view batch harness: a generated logic script can refresh the picture, load the next view, draw one transient object, wait a fixed number of cycles, then move on. I’m adding that as a separate timed/poll harness so the current per-case snapshot batch stays as the simple reference oracle.”

    Action/result: Added view_carousel_case_actions and view_timed_carousel_logic_payload to tools/qemu_fixture.py.

  1987. Reported: “The fixture generator now has a generic “view timed carousel” logic payload. Next I’m adding the packed fixture builder so each carousel directory carries only the needed pictures and views in VOL.3.”

    Action/result: Added build_view_timed_carousel_fixture, including selected picture/view packing and PICDIR/VIEWDIR patching.

  1988. Reported: “The packed fixture builder is in place. I’m adding unit tests around the emitted bytecode and packed directory entries before writing the QEMU runner.”

    Action/result: Added fixture tests for view-carousel case actions, timed logic, and packed picture/view records.

  1989. Reported: “The fixture-level tests now cover the view carousel payload and packed PICDIR/VIEWDIR layout. I’m adding the actual view_carousel.py runner, modeled on the timed polling picture carousel.”

    Action/result: Added tools/view_carousel.py.

  1990. Reported: “The new runner is in place. I’m adding lightweight tests for case conversion, chunk naming, report summaries, and mocked carousel execution so regressions get caught without launching QEMU.”

    Action/result: Added tests/test_view_carousel.py.

  1991. Reported: “The tests are written. I’m checking the comparison dataclass shape, then I’ll run the focused fixture/carousel tests and fix anything the type/constructor assumptions missed.”

    Action/result: Checked PictureCaptureComparison and corrected the new test constructor.

  1992. Reported: “Good catch: the capture comparison dataclass is simpler than I first assumed, so the test is patched. I’m running the focused fixture, view-batch, and carousel tests now.”

    Action/result: python3 -B -m unittest tests.test_qemu_fixture tests.test_view_batch tests.test_view_carousel passed with 48 tests.

  1993. Reported: “The focused local tests pass. I’m running a tiny real QEMU smoke for the new view carousel next: two view cases in one engine process, polled against the existing local comparison model.”

    Action/result: Initial sandboxed QEMU run failed because VNC socket bind was not permitted.

  1994. Reported: “The smoke run hit the sandbox on QEMU’s local VNC bind, not a harness bug. I’m rerunning that exact view-carousel command with approval so QEMU can open its local display socket.”

    Action/result: Approved rerun of tools/view_carousel.py matched both smoke cases with 0 mismatches.

  1995. Reported: “The two-case view carousel smoke matched QEMU. Since the harness is now proven, I’m going straight to the base eight view cases from one engine process; that gives us useful suite evidence instead of just a toy smoke.”

    Action/result: Started view_carousel_base_001.

  1996. Reported: “The base view carousel is halfway through and the first four captures matched. I’m waiting on the remaining placement and priority cases.”

    Action/result: view_carousel_base_001 completed with 8 matches, 0 mismatches, and 0 errors.

  1997. Reported: “The full base view carousel matched all eight cases from a single engine process. I also noticed the docs still mention an older “17 total” stress count while the current base-plus-stress list is 19, so I’m checking and correcting that while I document the new carousel.”

    Action/result: Confirmed base_cases() has 8 entries and stress_cases() has 11 entries, for 19 total current view cases.

  1998. Reported: “The current registry is 8 base cases plus 11 stress cases, 19 total. Before I change the text, I’m going to see whether the new carousel can handle all 19 in one process; if it does, that is better evidence than merely correcting the count.”

    Action/result: Started view_carousel_stress_001.

  1999. Reported: “The 19-case carousel is running; the first four matched. I’m letting it continue through the edge placements and larger transparent-color cels.”

    Action/result: Continued the 19-case QEMU run.

  2000. Reported: “The base eight and the first two stress cels have matched. The carousel is still progressing cleanly, so I’m waiting for the remaining nine stress cases.”

    Action/result: Continued the 19-case QEMU run through case 10.

  2001. Reported: “Sixteen of nineteen have matched. Only the last three larger/transparent stress cases remain; this is looking like the right fast path for future view sweeps.”

    Action/result: Continued the 19-case QEMU run through case 16.

  2002. Reported: “All nineteen poll comparisons matched; I’m just waiting for the process to print the final summary and exit cleanly.”

    Action/result: view_carousel_stress_001 completed with 19 matches, 0 mismatches, and 0 errors.

  2003. Reported: “The full 19-case view carousel matched from one engine process. I’m documenting the new tool, the base and stress commands, and cleaning up the stale 17-case wording.”

    Action/result: Started documentation updates for tools/view_carousel.py and the corrected view-suite counts.

  2004. Reported: “I found the stale count in the compatibility chapter and PROGRESS.md. I’m patching those now and adding tools/view_carousel.py to the QEMU workflow notes so future agents use the faster path.”

    Action/result: Updated AGENTS.md.

  2005. Reported: “AGENTS.md now has the view carousel command. I’m updating the compatibility narrative to distinguish the snapshot oracle from the new one-process carousel evidence.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2006. Reported: “The tracker now credits the 19-case view carousel. I’m adding the chronological clean-room note with the exact commands and the QEMU results.”

    Action/result: Updated PROGRESS.md and docs/src/clean_room_executable_notes.md.

  2007. Reported: “The clean-room note records the new harness and the socket-permission hiccup. I’m adding the user-facing progress updates to the historical log now, then I’ll run the full test/docs checks.”

    Action/result: Updated this progress log for the view carousel work.

  2008. Reported: “The view-carousel work is logged. I’m running the full verification stack again because this added a new tool, new fixture helpers, tests, and docs.”

    Action/result: python3 -B -m unittest discover -s tests passed with 178 tests; opcode evidence, mdBook, and git diff --check also passed.

  2009. Reported: “Full verification is clean again: 178 tests, opcode evidence, mdBook, and diff whitespace all pass. I’m checking the diff shape, then I’ll pick the next remaining PROGRESS.md item.”

    Action/result: Reviewed git status --short, git diff --stat, the progress-log tail, and the high-value remaining-work section.

  2010. Reported: “The diff is broad but coherent: restore/sound work, menu runtime model, picture fuzz expansion, and the new view carousel. Next I’m turning the picture/view renderer notes into a compact runtime-model contract so implementation agents do not have to mine the long evidence chapter for core behavior.”

    Action/result: Added picture decoder and view/cel drawing contracts to docs/src/runtime_model.md.

  2011. Reported: “The runtime model now has compact picture-decoder and view/cel drawing contracts. I’m updating PROGRESS.md so the remaining renderer work points to new edge coverage, not missing baseline implementation text.”

    Action/result: Updated PROGRESS.md for the new implementation-facing renderer contracts.

  2012. Reported: “The clean-room note now records the renderer contract synthesis as a documentation step. I’m logging these latest updates, then I’ll run the docs checks for the new runtime-model text.”

    Action/result: Added the renderer contract synthesis section to docs/src/clean_room_executable_notes.md and updated this progress log.

  2013. Reported: “The progress log is current through the renderer contract work. This was docs-only after the last full test pass, so I’m running mdBook, opcode evidence, and diff whitespace before continuing.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, and git diff --check passed.

  2014. Reported: “Docs checks are clean. The next high-value gap in PROGRESS.md is parser/input edge behavior, so I’m reading the parser and input sections before deciding whether this wants a source pass, tests, or a spec synthesis pass.”

    Action/result: Reviewed parser/input documentation, runtime-model notes, compatibility coverage, tools/inspect_words.py, and existing logic parser probes.

  2015. Reported: “The parser docs already state two interesting said-style condition semantics that only have thin evidence: word id 0x0001 as a wildcard and 0x270f as a success terminator. I’m checking the local vocabulary so I can add small QEMU probes for those without guessing.”

    Action/result: Used tools/inspect_words.py to confirm look is word ID 0x0002 and get is word ID 0x0005; avoided around because it maps to 0x0000.

  2016. Reported: “The local vocabulary gives look as word id 0x0002 and get as 0x0005. I’m adding three parser probes: exact two-word matching, wildcard matching, and terminator-prefix matching.”

    Action/result: Added three parser-edge cases to tools/logic_interpreter_probe.py.

  2017. Reported: “The parser cases are in the probe catalog. I’m updating the catalog coverage test so those edge cases stay visible in the suite.”

    Action/result: Added the parser-edge case IDs to tests/test_logic_interpreter_probe.py.

  2018. Reported: “The local catalog test now names the new parser cases. I’m running that focused test first, then the three cases through the original engine.”

    Action/result: The focused catalog test passed and QEMU batch parser_edges_001 started.

  2019. Reported: “The parser-edge QEMU batch is running from one snapshot. This should tell us whether the wildcard and terminator rows are truly observable behavior, not just source-model lore.”

    Action/result: parser_edges_001 completed with 3 matches, 0 mismatches, and 0 errors.

  2020. Reported: “The parser-edge batch matched all three cases. I’m updating the runtime model and compatibility docs so wildcard and terminator behavior are QEMU-backed now.”

    Action/result: Updated docs/src/runtime_model.md, docs/src/compatibility_testing.md, and PROGRESS.md.

  2021. Reported: “The parser edge behavior is now reflected in the runtime model, compatibility chapter, and tracker. I’m adding the chronological evidence note with the vocabulary lookup and QEMU batch.”

    Action/result: Added a parser wildcard/terminator probe section to docs/src/clean_room_executable_notes.md.

  2022. Reported: “The parser evidence note is in place. I’m updating the progress log with the parser work and then I’ll run the focused logic-probe tests plus docs checks.”

    Action/result: Updated this progress log for the parser-edge work.

  2023. Reported: “I’m picking up at the optional dynamic-probe audit. The useful distinction here is whether a dynamic probe would prove normal AGI semantics, or just poke timing/hardware plumbing in a brittle way; I’m going to reflect that in the tracker so it stops looking like an unfinished core opcode gap.”

    Action/result: Reviewed the current PROGRESS.md high-value list and confirmed that the relevant opcode rows were already [x] source-backed for the current spec target.

  2024. Reported: “The tracker confirms the opcodes themselves are already covered; the remaining wording was carrying an “optional probes” task as if it were core work. I’m going to turn that into a documented audit result, then move back to the substantive renderer/suite/spec gaps.”

    Action/result: Reframed PROGRESS.md so renderer parity, subsystem spec text, and compatibility-suite growth are the highest-value remaining tasks.

  2025. Reported: “I found the existing compatibility note already explains why the save-description and key-release cases are poor direct probes. I’m adding one consolidated audit section now so the decision is explicit instead of scattered across failed-fixture notes.”

    Action/result: Added a source-backed opcode audit paragraph to docs/src/compatibility_testing.md.

  2026. Reported: “The high-value list is now aimed at work that will actually move the spec forward. Next I’m putting the audit rationale into the evidence trail, because future-us should not have to rediscover why those five opcodes were left source-backed.”

    Action/result: Added the chronological clean-room audit section to docs/src/clean_room_executable_notes.md.

  2027. Reported: “The compatibility chapter now makes the “not worth a brittle QEMU probe” call concrete. I’m adding the same conclusion to the chronological clean-room notes with the commands and rows I reviewed.”

    Action/result: Recorded the reviewed commands and per-opcode rationale for 0x6e, 0x83, 0x8e, 0xaa, and 0xad.

  2028. Reported: “That audit is now in the clean-room notes too. I’m logging the user-facing updates themselves, then I’ll run the docs checks before choosing the next PROGRESS item.”

    Action/result: Updated this progress log for the source-backed dynamic probe audit.

  2029. Reported: “The checks passed. I’m moving into the renderer pipeline next, with the source code as the first authority: draw ordering, priority/control gating, and persistent-object behavior are the parts still marked partial.”

    Action/result: Reviewed docs/src/graphics_object_pipeline.md, tools/object_overlay_probe.py, and related renderer tests.

  2030. Reported: “The current docs already pin down a lot of overlay behavior, including the two priority-gate branches and several persistent-object priority bytes. The remaining weak spot I’m seeing is the update-list/draw-order machinery around persistent objects, so I’m reading that cluster in the executable next.”

    Action/result: Started source inspection around update-list roots, dirty-rectangle helpers, and object overlay entries.

  2031. Reported: “One caution: those first ndisasm windows were too broad and one of them used the project’s easy-to-trip offset convention poorly. I’m rerunning the relevant slices with the EXE header offset accounted for, then I’ll only document the corrected source observations.”

    Action/result: Discarded the exploratory offset-confused disassembly windows and reran corrected slices with image offset plus EXE header skip.

  2032. Reported: “The corrected source slices confirm the docs are mostly current: 0x6a8e rebuilds/draws root 0x1703 first and root 0x16ff second, and 0x6aab refreshes dirty rectangles in the same root order. I’m checking the existing probe cases now to see whether adding a small local model/test is better than another QEMU run.”

    Action/result: Reviewed existing root-partition QEMU cases and compatibility notes.

  2033. Reported: “The QEMU evidence already covers the two-root partition. The remaining source question is the in-root sort: whether objects are ordered by baseline and how fixed-priority objects alter the key. I’m reading the list-builder and insertion helpers (0x0358..0x045e) now, since that should be answerable statically.”

    Action/result: Inspected shared update-list builder 0x0358, node insertion 0x042f, draw walker 0x045e, and reverse-priority helper 0x4cbb.

  2034. Reported: “The insertion/sort pass gives a nice precise result: each root sorts by ascending key, where the key is baseline Y unless fixed-priority bit 0x0004 is set, in which case it maps object +0x24 back to a Y-like row through 0x4cbb. Equal keys are stable in object-table order, and because drawing starts from the list tail, later table entries draw later for equal keys.”

    Action/result: Identified the in-root draw-order contract from source.

  2035. Reported: “I’m going to add a small local model for the update-list draw order rather than another QEMU fixture. It will encode the source-backed root partition, stable ascending sort key, and fixed-priority key mapping, which makes the renderer contract easier to reuse in future compatibility tests.”

    Action/result: Checked existing renderer helpers and tests for a suitable place to add the local model.

  2036. Reported: “There’s an interesting wrinkle in 0x4cbb: the default-table scan starts at index 0xa8, one byte past the 168-row table, and the local initial byte there is zero. I’m not going to over-model fixed-priority sorting beyond the source-backed normal cases until I can tie that byte to runtime initialization; the helper can still encode the exact observed source behavior with a supplied sentinel.”

    Action/result: Inspected the bytes around 0x127a and 0x1322, then treated the one-past-table value as an explicit helper parameter.

  2037. Reported: “The byte search shows [0x124a] is only cleared by the table-rebuild helper in this executable; I don’t see a source write that enables the direct formula path. For SQ2’s normal path, the table scan behavior is the portable thing to document, including the sentinel byte caveat.”

    Action/result: Searched source/docs for 0x124a and found no SQ2 write enabling the alternate direct formula branch.

  2038. Reported: “I’m adding the helper with an explicit after_table_value parameter instead of hard-coding a meaning for the byte after 0x127a. For local SQ2’s initial data that byte is zero, and the tests will lock down what that means without pretending it is a general AGI rule yet.”

    Action/result: Added ObjectDrawCandidate and update-list ordering helpers to tools/agi_graphics.py.

  2039. Reported: “The source-order helper is in place. I’m adding focused tests for the one-past-table sentinel, root ordering, stable equal-key ordering, and fixed-priority sort keys.”

    Action/result: Added focused update-list ordering tests to tests/test_graphics_rendering.py.

  2040. Reported: “The tests now capture the exact local ordering model. I’m running the graphics test module before updating the docs, because this is the kind of small helper where a one-line sort mistake would be easy to miss by reading.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering passed with 43 tests.

  2041. Reported: “The graphics tests pass. I’m documenting the in-root sorting details now and adding stable labels for the shared list-builder helpers, so the cross-version map has more than just the root wrappers.”

    Action/result: Updated docs/src/symbolic_labels.md with labels for 0x0358, 0x042f, 0x045e, 0x0488, and 0x4cbb.

  2042. Reported: “The label map now has the shared builder, insertion, tail-to-head drawing, refresh helper, and reverse-priority helper. I’m tightening the graphics chapter around those details next, including the “later object wins on equal key” behavior.”

    Action/result: Expanded the update-list section in docs/src/graphics_object_pipeline.md.

  2043. Reported: “The graphics chapter has the detailed source behavior. I’m adding the short implementation-facing version to the runtime model so a future clean-room engine gets the ordering rule without having to re-read the evidence chapter.”

    Action/result: Updated docs/src/runtime_model.md with the concise update-list ordering contract.

  2044. Reported: “The runtime model now has the concise draw-order contract. I’m updating the compatibility chapter and tracker to show that draw ordering has moved from “partial” to source-modeled plus QEMU-backed for the root partition.”

    Action/result: Updated docs/src/compatibility_testing.md and PROGRESS.md.

  2045. Reported: “The tracker now treats draw order as covered at the current source/test level. I’m adding the chronological note with the corrected disassembly offsets and the rejected first reads, since that is part of the clean-room trail.”

    Action/result: Added the update-list draw-order source-model section to docs/src/clean_room_executable_notes.md.

  2046. Reported: “The clean-room note is in place. I’m logging the working updates themselves, then I’ll run docs/evidence checks and continue to the next partial renderer item.”

    Action/result: Updated this progress log for the update-list draw-order source-model work.

  2047. Reported: “I’ll pick up from the dirty-rectangle source model that was mid-edit, get that tested and documented, then keep walking the remaining PROGRESS.md items in order. I’ll also keep adding the work-log entries as I go so the evidence trail stays continuous.”

    Action/result: Inspected the worktree, current high-value progress tracker, graphics helper, and graphics tests before resuming edits.

  2048. Reported: “The worktree is still intentionally dirty from the accumulated reverse-engineering changes. The current high-value list has four broad tracks left, so I’m going to keep converting concrete source observations into helpers/tests/docs before moving to broader sweeps.”

    Action/result: Confirmed remaining high-value tracks and located the dirty-rectangle helper and object-pipeline documentation.

  2049. Reported: “The dirty-rectangle helper is present but not yet covered by tests or prose. I’m going to wire that into the graphics tests first, then carry the exact formula into the object pipeline chapter and runtime model.”

    Action/result: Opened the relevant test and documentation sections for a targeted dirty-rectangle update.

  2050. Reported: “I found the existing prose already had the broad dirty-rectangle claim, but it stops before the actual coordinate math. I’m adding the math as executable tests now, then I’ll make the prose precise enough for a future implementation.”

    Action/result: Added DirtyRect and dirty_rect_union() imports plus dirty-rectangle union tests to tests/test_graphics_rendering.py.

  2051. Reported: “The unit coverage now checks identical old/current footprints and both orderings of an offset old/current footprint. I’m running the focused graphics tests before touching the docs so the helper’s coordinate convention is pinned down.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering passed with 45 tests.

  2052. Reported: “The focused graphics tests pass. I’m taking one focused disassembly slice for 0x5762 now so the documentation points to the exact routine behind the helper rather than just inheriting the earlier broad object-pipeline read.”

    Action/result: Ran an ndisasm read from image offset 0x5762; the first routine body confirmed the dirty-rectangle union and the command also demonstrated that ndisasm -e is a skip offset rather than an end offset.

  2053. Reported: “The focused ndisasm command confirmed the 0x5762 body, though it also reminded me that ndisasm -e is a skip offset, not an end offset, so the terminal dump kept going after the routine. I have the needed first block; next I’m updating the docs and log with that caveat instead of pretending the command was neatly bounded.”

    Action/result: Searched existing notes for disassembly conventions before writing the new dirty-rectangle evidence note.

  2054. Reported: “I’m going to record that ndisasm -e lesson in the clean-room note too, because these offset mechanics matter for later cross-version work. Then I’ll keep pushing into the next object pipeline gap rather than letting this become just a prose cleanup pass.”

    Action/result: Prepared documentation edits for the source-backed dirty-rectangle formula.

  2055. Reported: “The object pipeline chapter now has the exact rectangle formula and register arguments. I’m adding the shorter implementation-facing version to the runtime model so the spec has both evidence detail and a direct contract.”

    Action/result: Added the exact dirty-rectangle formula to docs/src/graphics_object_pipeline.md.

  2056. Reported: “The runtime model now has the dirty-refresh contract in the lifecycle table. I’m marking that piece as source-modeled in PROGRESS.md, leaving the object-record collision/control fixture work as the main remaining object-pipeline gap.”

    Action/result: Added the runtime-model dirty-refresh contract.

  2057. Reported: “PROGRESS.md now reflects the dirty-rectangle coverage. I’m adding the chronological evidence note and the progress-log entries next, then I’ll run the focused checks and move to the remaining placement/collision-control work.”

    Action/result: Updated PROGRESS.md and added the dirty-rectangle source model note to docs/src/clean_room_executable_notes.md.

  2058. Reported: “The clean-room note captures the exact routine behavior and the ndisasm caveat. I’m updating the progress log with each user-facing update from this stretch so the historical record stays complete.”

    Action/result: Updated this progress log with entries for the dirty-rectangle source-model work.

  2059. Reported: “The dirty-rectangle documentation and log are in place. I’m running the docs/evidence hygiene checks now; after that I’ll return to the remaining object placement and collision/control semantics rather than stopping on this checkpoint.”

    Action/result: Ran mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, and git diff --check; all passed.

  2060. Reported: “The book build, opcode-evidence check, and whitespace check are clean. I’m moving to the remaining object-pipeline gap now: placement acceptance, collision, and control-screen semantics from source first, with QEMU only if we need an observable confirmation.”

    Action/result: Read current placement, collision, and control-acceptance helper notes and local object-pipeline tests.

  2061. Reported: “The existing notes already cover a lot of 0x4719/0x56b8, and the remaining tracker wording is narrower than it first looked: we need full object-record placement acceptance semantics polished, not a brand-new discovery from scratch. I’m reading the current source-backed helper descriptions against the local harnesses before deciding what small model/test should come next.”

    Action/result: Reviewed current movement/control QEMU cases and the local graphics helper surface.

  2062. Reported: “The harness already has QEMU movement cases for the important control classes, but the local graphics helpers don’t yet expose the 0x56b8 acceptance rule as a small deterministic model. I’m reading the exact routine body now so I can add that kind of helper without inferring from QEMU outcomes alone.”

    Action/result: Disassembled focused slices for code.object.control_acceptance (0x56b8) and code.object.collision_test (0x4719).

  2063. Reported: “The 0x56b8 source clarifies one correction: class 0x20 is not a permanent “encountered anywhere” latch in the scan; the routine resets its class state at each scanned cell, so the final scanned non-rejected cell controls the post-scan flag gates. I’m adding a small helper and tests for that exact source behavior.”

    Action/result: Added ControlAcceptance and control_acceptance_scan() to tools/agi_graphics.py.

  2064. Reported: “The helper is in place as a scanline-level model of 0x56b8, including the priority-15 bypass and the global flag values for event byte zero. I’m adding tests that exercise the source-only edge cases, including the “last scanned class wins” correction.”

    Action/result: Added control-acceptance scan tests to tests/test_graphics_rendering.py.

  2065. Reported: “The tests now lock down the class gates and the final-class behavior for event flag updates. I’m running the focused graphics module again; if it passes, I’ll correct the prose around 0x56b8 so it no longer implies an accumulated class-2 latch.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering passed with 47 tests.

  2066. Reported: “The focused tests pass with 47 graphics cases. I’m updating the docs now to spell out 0x56b8 as a scanline state machine: zero rejects, class 1 needs bit 0x0002, class 2 and class 3 use different post-scan gate bits, and the last scanned class state is what survives.”

    Action/result: Opened the symbolic label, graphics chapter, and runtime model sections that describe object control acceptance.

  2067. Reported: “The symbolic label for code.object.control_acceptance already exists, so I don’t need a new label. I’m revising its description and the graphics chapter now, mainly replacing “encountered class 2” with “current/final class state” to match the disassembly.”

    Action/result: Updated docs/src/symbolic_labels.md.

  2068. Reported: “The label map now says the routine is source-backed for the final scanned class state. I’m updating the detailed graphics chapter and runtime model next so the implementation contract is explicit.”

    Action/result: Updated docs/src/graphics_object_pipeline.md with the source-backed 0x56b8 state machine.

  2069. Reported: “The graphics chapter now states the exact source scan behavior, including the priority-15 bypass and the global flag updates. I’m adding a compact version to the runtime model so implementers don’t have to reconstruct it from the evidence chapter.”

    Action/result: Added the compact control-acceptance contract to docs/src/runtime_model.md.

  2070. Reported: “The runtime model now has an implementation-ready acceptance rule. I’m recording this as source-model progress in PROGRESS.md and the clean-room notes, then I’ll run checks before selecting the next PROGRESS.md item.”

    Action/result: Updated PROGRESS.md.

  2071. Reported: “PROGRESS.md now treats the control-acceptance scan as covered at the source/local-test level, with only optional direct placement fixtures left. I’m adding the evidence note with the 0x56b8 and 0x4719 disassembly slices and the correction to the old wording.”

    Action/result: Added the control-acceptance source-model section to docs/src/clean_room_executable_notes.md.

  2072. Reported: “The clean-room note captures the 0x56b8 model and the correction to the earlier “encountered” language. I’m bringing the progress log up to date for this object-acceptance pass before running the next check set.”

    Action/result: Updated this progress log with entries for the control-acceptance source-model work.

  2073. Reported: “The progress log is current through the control-acceptance work. I’m running the standard docs/evidence/whitespace checks again, then I’ll reassess PROGRESS.md for the next highest-value item.”

    Action/result: Ran mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, and git diff --check; all passed.

  2074. Reported: “The checks are clean again. The next broad high-value item is picture/view renderer compatibility, so I’m scanning the docs and tests for remaining provisional renderer semantics rather than inventing a new probe from the outside.”

    Action/result: Searched current docs/tests for renderer unknowns and remaining-work wording.

  2075. Reported: “The renderer chapter has one stale-looking provisional note: the prepare-picture versus show-picture distinction says it still needs live confirmation, even though later evidence and opcode coverage likely already cover the mechanism. I’m checking the existing probe coverage and source path before either correcting the wording or adding a narrow confirmation.”

    Action/result: Located existing resource-lifecycle QEMU evidence for 0x19, 0x1a, and 0x1c.

  2076. Reported: “Existing evidence already confirms the picture lifecycle distinction: 0x19/0x1c decode into the logical buffer, and 0x1a is the visible refresh/finalization step. I’m removing the stale “needs live confirmation” wording and pointing it at the existing resource-lifecycle QEMU probe.”

    Action/result: Updated the picture-flow section in docs/src/graphics_object_pipeline.md.

  2077. Reported: “That stale picture-lifecycle gap is now corrected using existing source and QEMU evidence. The next concrete renderer unknown is the first two bytes of view resources, so I’m going after the view-loader source to see whether those bytes are actually used by the interpreter.”

    Action/result: Opened current view-layout docs, symbolic labels, and disassembly around the view loader and frame selector.

  2078. Reported: “The view loader/binder source only touched payload +0x02 and later offsets in the slices so far. I’m reading the group/frame selector and preview-text helper next; if those also skip +0x00/+0x01, the honest conclusion is “reserved or unused by observed SQ2 interpreter paths” rather than a fabricated meaning.”

    Action/result: Disassembled code.object.select_group_table and the view preview/display helper, and inspected a sample view header.

  2079. Reported: “The selector and preview slices also skip bytes +0x00/+0x01; the local view inspector shows view 0 has header 01 01 .... I’m doing a local census across all present view resources now, then I’ll update the spec to call those two bytes reserved/unused by observed SQ2 runtime paths if the evidence supports that.”

    Action/result: Counted first-two-byte headers across all valid SQ2 view resources; all 203 were 01 01.

  2080. Reported: “All 203 valid local SQ2 view resources have header bytes 01 01. Since the inspected interpreter paths begin meaningful view parsing at byte +0x02, I’m adding a small census test and changing the docs from “unknown” to “reserved/unused by observed SQ2 code paths.””

    Action/result: Added a view-header reserved-byte census test to tests/test_graphics_rendering.py.

  2081. Reported: “The view-header census test is in place. I’m running the focused graphics tests again before editing the docs, because this is exactly the sort of tiny claim that should be executable.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering passed with 48 tests.

  2082. Reported: “The focused graphics suite passes with 48 tests. I’m updating the view layout prose now: bytes +0x00/+0x01 are stable 01 01 in SQ2 and appear unused by the observed loader/binder/preview paths, while byte +0x02 starts the meaningful layout.”

    Action/result: Updated the view payload layout in docs/src/graphics_object_pipeline.md.

  2083. Reported: “The graphics chapter now treats the first two view bytes as reserved, with both source-path and resource-census evidence. I’m adding the same compact contract to the runtime model and updating the tracker.”

    Action/result: Updated docs/src/runtime_model.md.

  2084. Reported: “The runtime model now has the reserved-byte rule too. I’m updating PROGRESS.md so view decoding reflects the source-backed header interpretation rather than leaving that as an open unknown.”

    Action/result: Updated the view resource decoding evidence line in PROGRESS.md.

  2085. Reported: “PROGRESS.md now records the view header bytes as source-backed. I’m adding the clean-room evidence note for the disassembly slices and the local resource census before running the check set.”

    Action/result: Added the view-header reserved-byte section to docs/src/clean_room_executable_notes.md.

  2086. Reported: “The clean-room note now records the view-header evidence. I’m adding one line to the compatibility coverage summary so the new view-header census test is visible alongside the other local renderer tests.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2087. Reported: “The compatibility chapter now includes the view-header census. I’m updating the progress log for the picture lifecycle cleanup and view-header source pass, then I’ll run the standard checks again.”

    Action/result: Updated this progress log with entries for the picture-lifecycle cleanup and view-header source-model work.

  2088. Reported: “The progress log is current through the view-header work. I’m running the standard checks now, then I’ll continue into the next remaining area from PROGRESS.md.”

    Action/result: Ran mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, and git diff --check; all passed.

  2089. Reported: “Checks are clean. The picture decoder still has two handlers that local SQ2 never uses in real resources, 0xf4 and 0xf5, so I’m checking whether our synthetic coverage is balanced there before choosing any new fuzz/QEMU work.”

    Action/result: Reviewed corner-path renderer code, fuzz cases, unit tests, and coverage notes.

  2090. Reported: “Corner commands are already in the fuzz corpus and QEMU coverage, but the focused graphics unit tests only assert the resume behavior for 0xf4; they don’t directly assert the full 0xf5 X-first path. I’m adding a small deterministic local test for both corner orientations so that rare-but-valid handlers have explicit regression coverage.”

    Action/result: Located existing indirect 0xf4 tests through seed-fill rectangle cases.

  2091. Reported: “I found the seed-fill tests use 0xf4 rectangles, so 0xf4 has indirect coverage. I’m still adding direct corner-path tests because they make the intended point set obvious and put 0xf5 on equal footing.”

    Action/result: Added direct 0xf4 and 0xf5 corner-path point-set tests to tests/test_graphics_rendering.py.

  2092. Reported: “The direct corner-path tests are in place. I’m running the focused graphics suite to make sure the expected point sets match the existing renderer and source semantics.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering passed with 50 tests.

  2093. Reported: “The graphics suite passes with 50 tests. I’m recording the new direct 0xf4/0xf5 coverage in the testing chapter and tracker, then I’ll keep moving through remaining renderer risk areas.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2094. Reported: “The compatibility chapter now calls out direct corner-path unit coverage. I’m updating PROGRESS.md and the clean-room notes with the same small renderer-suite improvement.”

    Action/result: Updated the picture decoder evidence line in PROGRESS.md.

  2095. Reported: “PROGRESS.md now includes the rare corner-path handlers in picture evidence. I’m adding a short evidence note that this was a local regression-coverage improvement, not a new QEMU observation.”

    Action/result: Added the direct corner-path unit-coverage note to docs/src/clean_room_executable_notes.md.

  2096. Reported: “The clean-room note now distinguishes this as local regression coverage for already-source-modeled handlers. I’m updating the progress log and then running the normal check set.”

    Action/result: Updated this progress log with entries for the direct corner-path coverage work.

  2097. Reported: “The log is current through the corner-path coverage. I’m running the standard checks now before moving on again.”

    Action/result: Ran mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, and git diff --check; all passed.

  2098. Reported: “Checks are clean. The view item still says runtime animation state needs broadening, so I’m checking what’s already source-backed and QEMU-backed there before deciding whether the tracker wording is stale or there is a real missing mode/edge.”

    Action/result: Reviewed frame-animation source/model notes, movement probe cases, and test coverage.

  2099. Reported: “The animation side is not actually an uncovered view-decoder gap anymore: modes 0..3, interval seeding, and bit 0x0020 gating are source-backed and QEMU-covered in the object movement harness. I’m tightening PROGRESS.md so the view row’s remaining work is about additional priority/control combinations, not animation state that is already tracked elsewhere.”

    Action/result: Updated the view resource decoding row in PROGRESS.md.

  2100. Reported: “The tracker now no longer double-counts animation as a view-decoder gap. I’m moving to another concrete partial item: vocabulary/tokenization edge cases, checking whether there’s a small source-backed local parser model we should expose for final compatibility work.”

    Action/result: Reviewed parser/vocabulary docs and tools/inspect_words.py.

  2101. Reported: “The parser docs already have a good high-level model, but inspect_words.py is only a utility without tests. I’m adding focused local tests for the WORDS.TOK decoder so the vocabulary format evidence is executable, especially the prefix compression and known IDs used by QEMU parser probes.”

    Action/result: Confirmed no existing test module covered tools/inspect_words.py.

  2102. Reported: “The local decoder reports 1,099 entries and the IDs used in the QEMU parser cases: anyword=0x0001, look=0x0002, get=0x0005. I’m adding a dedicated test module for those facts and the 26-letter offset table.”

    Action/result: Added tests/test_words.py.

  2103. Reported: “The WORDS.TOK tests are in place. I’m running that new test module and the graphics tests together, since both are affected by this round of local compatibility coverage.”

    Action/result: python3 -B -m unittest tests.test_words passed with 3 tests, and python3 -B -m unittest tests.test_graphics_rendering passed with 50 tests.

  2104. Reported: “The new WORDS.TOK tests pass, and the graphics suite still passes. I’m documenting this as local parser-format coverage, then I’ll keep working through the remaining tracker items.”

    Action/result: Started updating parser coverage docs.

  2105. Reported: “The compatibility paragraph I wanted has shifted a bit, so I’m locating the parser section and applying the update against the current text instead of forcing a stale patch.”

    Action/result: Located the parser edge section in docs/src/compatibility_testing.md.

  2106. Reported: “I found the parser section. I’m adding the WORDS.TOK decoder-test summary right after the QEMU parser edge paragraph so the static vocabulary format and dynamic condition behavior sit together.”

    Action/result: Prepared the compatibility-testing update.

  2107. Reported: “The exact paragraph is in hand now. I’m patching against that text and then I’ll update the tracker/notes for the new parser-format tests.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2108. Reported: “The compatibility chapter now links the WORDS.TOK decoder tests to the QEMU parser probes. I’m updating PROGRESS.md and the clean-room notes with this parser-format coverage.”

    Action/result: Updated the parser/input coverage line in PROGRESS.md.

  2109. Reported: “PROGRESS.md now reflects executable WORDS.TOK decoder coverage. I’m adding the chronological note with the exact inspected IDs and offsets.”

    Action/result: Added the WORDS.TOK decoder test section to docs/src/clean_room_executable_notes.md.

  2110. Reported: “The parser-format evidence note is in place. I’m updating the progress log for the animation-tracker cleanup and WORDS.TOK test work, then I’ll run the standard checks plus the new parser test.”

    Action/result: Updated this progress log with entries for the animation tracker cleanup and WORDS.TOK decoder tests.

  2111. Reported: “The progress log is current again. I’m running docs/evidence/whitespace checks plus the new parser test module now.”

    Action/result: Ran mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest tests.test_words; all passed.

  2112. Reported: “The check set passed. I’m moving to another tracker item with a concrete “remaining” note: DOS save path validation. I’ll inspect the existing source map and tests first, then decide whether a small local model/test can close part of that gap.”

    Action/result: Reviewed path-selection docs, symbolic labels, progress wording, and save-resource helpers.

  2113. Reported: “The save/restore state machine is well documented, but the validator itself is still mostly a named box. I’m reading the 0x5bdd path validator source now and will turn any stable rule into a small local helper/test if it’s straightforward.”

    Action/result: Disassembled code.dos.validate_path and neighboring DOS helpers.

  2114. Reported: “The validator source gives a useful split: it normalizes the path string before delegating to DOS. It skips leading spaces, fills an empty path with the current directory, strips a trailing slash/backslash when length is greater than one, accepts a single slash/backslash immediately, treats X: as a drive-only check, and otherwise uses DOS find-first with directory attributes. I’m going to model that source-level classification locally, leaving actual DOS availability as an external result.”

    Action/result: Read the separator table and drive helper source.

  2115. Reported: “The separator table at 0x135f is exactly backslash and slash, and the drive helper lowercases drive letters before probing DOS. I need one more corrected slice for 0x5db2; the earlier dd skip hit the previous helper because I used the wrong file offset.”

    Action/result: Re-read code.dos.get_current_directory at the corrected file offset.

  2116. Reported: “The corrected 0x5db2 slice shows the default path helper writes a leading backslash, calls DOS get-current-directory, and may switch that first separator to slash if the returned path contains one. I’m adding a local “validation plan” helper now: it models source string handling and says which DOS check the interpreter would perform, without pretending to know host/DOS availability.”

    Action/result: Added SavePathValidationPlan and save_path_validation_plan() to tools/agi_save.py.

  2117. Reported: “The path-validation planning helper is in tools/agi_save.py. I’m adding unit tests for the source-visible edge cases: leading spaces, empty-path defaulting, trailing separator stripping, single-root acceptance, and drive-only classification.”

    Action/result: Opened and extended tests/test_save_resources.py.

  2118. Reported: “The save tests already cover the file envelope, so I’m extending that module rather than creating a separate path test file.”

    Action/result: Added save path-validation plan tests.

  2119. Reported: “The path-validation edge tests are in place. I’m running the save-resource tests now to make sure the new model matches the source-derived expectations.”

    Action/result: python3 -B -m unittest tests.test_save_resources passed with 7 tests.

  2120. Reported: “The save-resource tests pass with the new path-validation cases. I’m documenting the validator as a source-level string-normalization and DOS-check plan, and adding the missing symbolic label for code.dos.validate_path.”

    Action/result: Updated docs/src/symbolic_labels.md.

  2121. Reported: “The path validator label is now in the cross-version map. I’m updating the runtime model and compatibility chapter with the exact edge behavior covered by the new helper/tests.”

    Action/result: Updated the save/restore selector state machine in docs/src/runtime_model.md.

  2122. Reported: “The runtime model now spells out the path validator’s string handling and DOS probe choices. I’m adding the same coverage note to the compatibility chapter and tracker.”

    Action/result: Located the current save/restore section after an initial stale patch context.

  2123. Reported: “That save/restore paragraph has shifted too; I’m locating the restore-read-error section and patching the path-validation note into the current wording.”

    Action/result: Opened the save/restore compatibility-testing section.

  2124. Reported: “I found the save/restore section. I’m inserting the path-validation note after the structural save-resource test description, before the dynamic QEMU save/restore probes.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2125. Reported: “The compatibility chapter now captures the path validation plan. I’m updating PROGRESS.md to narrow that remaining item, then I’ll add the clean-room source note.”

    Action/result: Updated the DOS file I/O progress row in PROGRESS.md.

  2126. Reported: “The DOS path-validation row now treats the source-level string rules as covered, leaving only optional dynamic UI failure cases. I’m adding the source-note with the disassembly and test commands.”

    Action/result: Added the save path validation plan section to docs/src/clean_room_executable_notes.md.

  2127. Reported: “The clean-room note captures the path validator’s source behavior and the limits of the local helper. I’m updating the progress log, then I’ll run the relevant tests and standard checks.”

    Action/result: Updated this progress log with entries for the save path validation plan work.

  2128. Reported: “I’m picking this back up at the resource/cache layout work: first I’m going to land the cache-record documentation update that the disassembly already supports, then I’ll re-read PROGRESS.md and keep walking the remaining checklist from the highest-value items down.”

    Action/result: Read the current tracker, resource chapter, progress log, clean-room notes, and worktree status.

  2129. Reported: “The tracker confirms the opcode matrix itself is done, so I’m staying in the subsystem-spec layer now: cache records, renderer edge cases, UI/text/parser/save/audio contracts, and the compatibility suite. The cache table is the next small concrete improvement because the offsets are all source-backed.”

    Action/result: Re-read the remaining engine coverage areas in PROGRESS.md and the existing resource-loader prose.

  2130. Reported: “I’m editing the mdBook resource chapter now. The important implementation-facing point is that each resource family keeps a tiny linked cache record with a different payload-specific layout, so I’m documenting that as a table rather than leaving it scattered through notes.”

    Action/result: Added the resource cache record table to docs/src/resource_files.md.

  2131. Reported: “The resource chapter now has the cache-record table, and PROGRESS.md no longer treats that layout as unresolved. I’m adding the evidence note with the exact disassembly slices so future version comparisons can map changed addresses back onto the same symbols.”

    Action/result: Updated PROGRESS.md and added a resource cache record layout section to docs/src/clean_room_executable_notes.md.

  2132. Reported: “I’m also logging the working updates themselves. That log is deliberately a narrative trail, so I’m adding both the status updates and the concrete result behind each one before I move into the next tracker item.”

    Action/result: Added the current resource-cache progress entries to this log.

  2133. Reported: “The cache-record docs are in place. I’m running the standard documentation/evidence checks now, then I’ll continue down PROGRESS.md rather than pausing at this milestone.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, and git diff --check all passed.

  2134. Reported: “The checks passed. I’m scanning for the next concrete gaps that are both source-discoverable and useful for the final implementation spec, starting with renderer and subsystem TODO language rather than inventing new dynamic probes first.”

    Action/result: Searched PROGRESS.md, docs, tools, and tests for remaining/provisional language and opened the graphics renderer model.

  2135. Reported: “The renderer chapter is mostly in good shape, but I found a couple of stale “unknown” statements: view header bytes are now reserved/unused, and the cache chapter still says view/picture parsing remains to be decoded. I’ll clean those while I’m in this pass, then move to an actual remaining edge.”

    Action/result: Identified stale wording in resource_files.md and the graphics chapter summary.

  2136. Reported: “Before diving into new disassembly, I’m doing a small documentation consistency pass. These aren’t new findings, just removing old uncertainty that the later source work already settled.”

    Action/result: Updated the resource and graphics chapters to point at the now-documented view/picture parsers and to remove view-header bytes from the renderer unknown list.

  2137. Reported: “That stale wording is cleaned up. Next I’m going back to the remaining renderer edge semantics; I’m going to inspect the picture draw helpers around the less common command paths and compare them against the current Python model before adding tests.”

    Action/result: Returned to source-first picture renderer inspection.

  2138. Reported: “I’m checking the local renderer tests against the source-modeled command families now. The first useful target is not a big new harness; it’s locking down byte-reader edge cases where a command byte interrupts an active command and must be reprocessed by the scanner.”

    Action/result: Reviewed existing graphics/fuzz tests and disassembled picture scanner and draw helper slices around 0x6475, 0x6494, 0x6603, and 0x66ab.

  2139. Reported: “The source exposed a real edge: not every operand uses the coordinate/data reader. Commands 0xf0, 0xf2, and 0xf9 consume a raw next byte, so a byte that looks like another command is data in that position. The Python renderer currently treated those operands too strictly, so I’m fixing that and adding regression tests.”

    Action/result: Added PictureRenderer.read_raw_byte() and changed visual, control, and pattern-mode operands to consume raw bytes.

  2140. Reported: “The renderer now distinguishes raw single-byte operands from coordinate/data operands. I’m running the graphics tests; if the hashes stay stable, this should only add edge coverage and not change ordinary SQ2 picture renders.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering passed with 53 tests.

  2141. Reported: “The focused graphics suite passes with 53 tests, and the existing real-picture hashes did not move. I’m going to turn this source edge into reusable corpus coverage as well, so future renderer comparisons don’t lose it.”

    Action/result: Opened the picture fuzz base-case registry and tests.

  2142. Reported: “I found the synthetic fuzz corpus has the right place for this: the existing command-resume cases cover coordinate readers, but not raw one-byte operands. I’m adding three safe base cases so the distinction is visible in the reusable corpus.”

    Action/result: Added base_033_raw_visual_operand, base_034_raw_control_operand, and base_035_raw_pattern_mode_operand to tools/picture_fuzz.py, plus test coverage in tests/test_picture_fuzz.py.

  2143. Reported: “The fuzz base cases now include raw visual/control/pattern-mode operands. I’m running the picture-fuzz tests next, then I’ll regenerate the corpus metadata so the documented counts are real.”

    Action/result: python3 -B -m unittest tests.test_picture_fuzz passed with 12 tests.

  2144. Reported: “The fuzz-tool tests pass. I’m regenerating the deterministic corpus with the three new base cases, which should take it from 1,057 to 1,060 cases while keeping only the two intentional overread cases out of QEMU.”

    Action/result: Regenerated build/picture-fuzz/corpus; it reported 1,060 cases and 1,058 safe for QEMU.

  2145. Reported: “The regenerated corpus reports 1,060 cases, 1,058 safe for QEMU. Since this is a renderer edge and the harness already supports it, I’m going to run the three new raw-operand cases through the original engine as a focused snapshot batch.”

    Action/result: Started snapshot QEMU batch raw_operand_001 for the three new raw-operand cases.

  2146. Reported: “The QEMU snapshot batch is running from one boot and has built all three fixtures. I’m waiting on the captures now; this is the confirmation step, not the discovery step.”

    Action/result: Waited for the QEMU snapshot batch to finish.

  2147. Reported: “The original engine matched all three raw-operand cases with zero mismatches. I’m documenting this as QEMU-confirmed picture semantics and updating the corpus counts in the tracker and compatibility chapter.”

    Action/result: Opened the compatibility chapter, tracker, and notes for updates.

  2148. Reported: “I’m updating the implementation-facing docs now: the nuance is that “command bytes remain pending” applies to coordinate/list readers, while raw one-byte operands are consumed as operands even if they look like commands. That distinction matters for compatibility.”

    Action/result: Updated the picture decoder section of docs/src/graphics_object_pipeline.md.

  2149. Reported: “The renderer chapter now states both sides of the scanner rule. I’m adding the same contract to the runtime model and compatibility chapter, then I’ll record the exact disassembly/QEMU commands in the evidence notes.”

    Action/result: Updated docs/src/runtime_model.md.

  2150. Reported: “The runtime model has the raw-operand rule now. I’m updating the compatibility chapter with the new 1,060-case corpus count and the three-case QEMU batch result.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2151. Reported: “The compatibility chapter now names the raw-operand batch. I’m tightening the tracker too, so the dashboard reflects both the added renderer evidence and the updated corpus size.”

    Action/result: Updated PROGRESS.md.

  2152. Reported: “The tracker is updated. I’m adding the clean-room evidence entry now: disassembly slices, the renderer/test changes, corpus regeneration, and the original-engine batch result.”

    Action/result: Added the picture raw-operand scanner edge section to docs/src/clean_room_executable_notes.md.

  2153. Reported: “The evidence note is in place. I’m updating the progress log with the whole raw-operand sequence before running the standard checks again.”

    Action/result: Added the raw-operand progress entries to this log.

  2154. Reported: “The raw-operand checks passed. I’m moving to the next high-value partial subsystem that is mostly source-discoverable: sound/audio, specifically whether the remaining “pitch/envelope” wording can be turned into a clearer driver-facing contract from the disassembly and existing sound parser.”

    Action/result: Opened tools/agi_sound.py, sound-related docs, and the current sound progress row.

  2155. Reported: “The sound docs already cover the gameplay contract and PC-speaker divisor. The remaining wording is about the non-PC-speaker attenuation/envelope helper, so I’m reading that exact source path now rather than guessing from sound data alone.”

    Action/result: Disassembled code.sound.driver_write_attenuation and nearby driver start/tick/tone helpers, and inspected AGIDATA bytes around the default envelope table.

  2156. Reported: “The attenuation helper is clearer than the tracker implies: it has a specific per-channel ramp table, sentinel byte 0x80, clamp to 0..15, and selector-2 adjustment before the port write. I’m pulling the tone helper slice too so the local model can cover both driver-facing outputs.”

    Action/result: Re-ran the correctly aligned tone/helper disassembly and scanned local SQ2 sound control/tone byte distributions.

  2157. Reported: “One oddity in the tone helper is that the event control byte’s upper bits are not plainly reloaded before code.sound.driver_write_tone; I’m not going to overclaim that path. The attenuation helper, though, has enough direct state reads and writes to model its output byte precisely.”

    Action/result: Limited the new model to the directly source-backed attenuation/output-byte state machine.

  2158. Reported: “I’m going to add a small source-level model for attenuation output rather than an audio synthesizer. It will model the state the driver actually mutates and the byte it writes to port 0xc0; audible waveform reproduction can stay out of the portable spec.”

    Action/result: Added SoundAttenuationState, SoundAttenuationOutput, default envelope decoding, channel masks, and sound_attenuation_output() to tools/agi_sound.py.

  2159. Reported: “The attenuation helper is in tools/agi_sound.py. I’m adding focused tests for the default envelope table, channel masks, selector-2 adjustment, negative/positive clamps, and the 0x80 envelope terminator.”

    Action/result: Extended tests/test_sound_resources.py with attenuation output tests.

  2160. Reported: “The sound tests now cover the driver-facing attenuation state machine directly. I’m running that module to make sure the helper matches the source-derived edge cases.”

    Action/result: python3 -B -m unittest tests.test_sound_resources passed with 14 tests.

  2161. Reported: “The targeted sound tests pass. I’m updating the sound spec text now: attenuation/envelope byte output is no longer an open semantic gap, while actual audible synthesis remains hardware-specific and outside the portable game-state contract.”

    Action/result: Opened the sound sections in the runtime model, compatibility chapter, clean-room notes, and tracker.

  2162. Reported: “I’m patching the sound runtime model with the exact attenuation helper: base attenuation, envelope index/value, default delta table, 0x80 sentinel, channel mask, selector-2 lift, and final port byte.”

    Action/result: Updated docs/src/runtime_model.md.

  2163. Reported: “The sound runtime model now has the driver-facing attenuation contract. I’m adding a compatibility-suite note so the new local tests are visible alongside the existing QEMU load/start/stop probes.”

    Action/result: Located the existing static sound-resource parser section in docs/src/compatibility_testing.md.

  2164. Reported: “The compatibility chapter already had a sound-resource section, so I’m extending that paragraph rather than adding a new one.”

    Action/result: Updated the compatibility-testing sound paragraph with the new attenuation output coverage.

  2165. Reported: “The compatibility note is updated. I’m narrowing the sound row in PROGRESS.md: envelope/attenuation byte behavior is covered now; what remains is actual audible synthesis or optional dynamic timing confirmation.”

    Action/result: Updated the sound/audio row in PROGRESS.md.

  2166. Reported: “The sound tracker now distinguishes driver-byte compatibility from actual sound synthesis. I’m adding the clean-room note with the new disassembly and table observations.”

    Action/result: Added the sound attenuation envelope source-model section to docs/src/clean_room_executable_notes.md.

  2167. Reported: “The sound evidence note is in place. I’m adding the corresponding progress-log entries, then I’ll run the standard checks and continue to the next tracker item.”

    Action/result: Added the sound attenuation progress entries to this log.

  2168. Reported: “The sound checks passed. I’m moving next to parser/vocabulary because that is another remaining partial area where source inspection and local tests can improve the implementation spec without needing a lot of emulator time.”

    Action/result: Searched parser/vocabulary docs, opened the WORDS.TOK decoder and tests, and reviewed the runtime parser summary.

  2169. Reported: “The docs already separate direct string comparison from WORDS.TOK parsing. I’m looking for a source-backed tokenization detail that is useful enough to model locally, especially delimiters, unknown-word behavior, and parser buffer limits.”

    Action/result: Located existing parser notes, data tables, and labels for parsed-word buffers and normalized string comparison.

  2170. Reported: “The earlier notes already identified parser helper addresses, so I’m re-reading those slices directly. If the normalization/token buffers are as clear as the notes suggest, I’ll turn them into a small local parser helper and tests.”

    Action/result: Disassembled action 0x75, parser helper 0x18ac, normalization helper 0x199d, and dictionary lookup helper 0x1a6b.

  2171. Reported: “The tokenization source gives a neat implementation boundary: normalize into a space-separated buffer, then lookup advances a global pointer through that buffer one word at a time. I’m reading the two tail helpers before I code the local model.”

    Action/result: Disassembled unknown-token truncation helper 0x1bc7 and dictionary-entry advance helper 0x1be4, and reviewed earlier parser evidence notes.

  2172. Reported: “The parser source has one subtle correction worth preserving: ignored dictionary words with ID zero do not advance the parsed output index, so an unknown word after “the” reports output slot 1, not raw token 2. I’m coding a local source-model helper for normalization and parsed IDs.”

    Action/result: Added parser normalization and parsed-word helpers to tools/inspect_words.py.

  2173. Reported: “The parser helper is added. I’m extending tests/test_words.py now with source-visible normalization, zero-ID filtering, unknown-slot reporting, and the 10-word parsed buffer limit.”

    Action/result: Added parser/tokenizer tests.

  2174. Reported: “The WORDS tests now exercise the parser model. I’m running them; if any local word ID assumption is off, I’ll adjust from the decoded file rather than guessing.”

    Action/result: First tests.test_words run found that gem has local word ID 0x0174, not the assumed ID.

  2175. Reported: “One assumption was off: gem decodes to 0x0174, not the ID I casually reused from older notes. I’m correcting the test to the local dictionary value.”

    Action/result: Updated the test to assert the decoded gem ID from WORDS.TOK.

  2176. Reported: “The parser test now references the decoded dictionary value explicitly. I’m rerunning the WORDS tests.”

    Action/result: python3 -B -m unittest tests.test_words passed with 8 tests.

  2177. Reported: “The WORDS/parser tests pass. I’m updating the docs and symbolic label map with the parser tables and the corrected “output slot” wording for unknown words after ignored zero-ID terms.”

    Action/result: Began updating symbolic labels and parser documentation.

  2178. Reported: “I’m adding stable labels for the parser separator/ignored tables, dictionary base pointer, normalized buffer, and parsed pointer table. Those are exactly the sort of labels that will help when another interpreter build moves the code.”

    Action/result: Added parser data labels to docs/src/symbolic_labels.md.

  2179. Reported: “The data labels are in. I’m adding the companion routine labels for parse action, normalization, dictionary lookup, unknown-token truncation, and dictionary-entry advancement.”

    Action/result: Located the insertion point near input helpers.

  2180. Reported: “The label section had the view helper immediately after input mapping, so I’m inserting the word-parser routines in that slot.”

    Action/result: Added parser routine labels to docs/src/symbolic_labels.md.

  2181. Reported: “The routine labels are in. I noticed the parser’s current-token pointer also deserves a data label, so I’m adding that before updating the prose.”

    Action/result: Added data.words.current_parse_pointer_0cd1 to the symbolic label map.

  2182. Reported: “I’m updating the parser prose now, especially the zero-ID filtering and unknown-word position semantics. This is a small wording change, but it prevents a future implementation from reporting the wrong parser slot.”

    Action/result: Updated the parser section in docs/src/runtime_model.md.

  2183. Reported: “The runtime model now has the parser contract. I’m mirroring the output-slot correction in the bytecode chapter where the lower-level action 0x75 behavior is described.”

    Action/result: Located the parsed-input producer paragraph in docs/src/logic_bytecode.md.

  2184. Reported: “That paragraph had a slightly different shape than my patch context. I found it and am applying the correction directly.”

    Action/result: Updated the action 0x75 parser-result handling wording in docs/src/logic_bytecode.md.

  2185. Reported: “The bytecode chapter is corrected. I’m updating the compatibility chapter to mention the new local parser normalization/output-slot tests.”

    Action/result: Opened the parser edge section in docs/src/compatibility_testing.md.

  2186. Reported: “The compatibility chapter now points at the parser tests. I’m updating PROGRESS.md so the variables/parser row reflects the new source-modeled tokenizer coverage.”

    Action/result: Updated the variables/parser progress row.

  2187. Reported: “The tracker is updated. I’m adding the evidence note for the parser source-model pass with the exact helper addresses and test command.”

    Action/result: Added the parser normalization and output-slot model section to docs/src/clean_room_executable_notes.md.

  2188. Reported: “The parser evidence note is now in the clean-room log. I’m adding the progress-log entries for this parser pass, then I’ll run checks and continue.”

    Action/result: Added the parser pass progress entries to this log.

  2189. Reported: “The parser progress log is current. I’m running the WORDS tests and standard documentation/evidence checks now.”

    Action/result: Re-ran the parser tests and standard checks after the parser pass.

  2190. Reported: “The parser checks passed. I’m doing a quick tracker audit now so the next pass is guided by actual remaining gaps instead of inertia.”

    Action/result: Reviewed PROGRESS.md, the runtime model, symbolic labels, and compatibility notes for the next high-value subsystem.

  2191. Reported: “The startup/display docs are already broader than I expected, and the display globals are labeled. I’m going to avoid a speculative startup pass for now and instead harden the final compatibility suite: first by running the full local unit suite to catch integration fallout from the new helpers.”

    Action/result: Started the full local unit suite.

  2192. Reported: “The full local suite passes: 205 tests, including the carousel harness tests that build snapshot disks. That gives a good integration checkpoint after the renderer, sound, and parser additions.”

    Action/result: python3 -B -m unittest discover -s tests passed with 205 tests.

  2193. Reported: “I’m going to strengthen the memory/heap row next with a small local source model for allocator/status formulas. The docs already describe them; adding executable tests makes those formulas part of the compatibility suite rather than just prose.”

    Action/result: Began the heap formula test-model pass.

  2194. Reported: “The heap source pass matches the docs cleanly. I’m adding a tiny agi_heap helper module with allocation, free-byte, mark/rewind, reset, and diagnostic formulas, then focused tests to make those formulas executable.”

    Action/result: Added tools/agi_heap.py with source-modeled heap helper formulas.

  2195. Reported: “I’m picking up exactly where the compaction left off: the heap helper has been added, and I’m turning that source-derived model into tests and documentation now. After that I’ll keep working down PROGRESS.md rather than stopping at this checkpoint.”

    Action/result: Inspected working-tree state and the memory/heap tracker row.

  2196. Reported: “The working tree is in the expected state: lots of reverse-engineering edits already staged only in the filesystem, no commit. I’m going to keep the next edits tightly scoped: heap tests, the evidence notes, and the progress dashboard.”

    Action/result: Read the relevant PROGRESS.md, compatibility_testing.md, progress_log.md, and clean-room notes sections.

  2197. Reported: “I’ve got the heap source model and the existing docs lined up. Next I’m adding tests that pin the old-top return value, free-memory page byte, temporary mark restore, room reset rewind, and diagnostic formulas.”

    Action/result: Added tests/test_heap.py.

  2198. Reported: “The heap tests are in. I’m doing one quick readability pass on the helper itself before running them, mostly to keep the return-value formulas easy to inspect later.”

    Action/result: Reformatted long return paths in tools/agi_heap.py.

  2199. Reported: “The helper now reads cleanly. I’m running the new focused heap tests first, then I’ll document the source addresses and fold the result into the progress tracker.”

    Action/result: python3 -B -m unittest tests.test_heap passed with 7 tests.

  2200. Reported: “The heap tests pass: 7 focused checks. I’m recording this as executable compatibility evidence now, with the caveat that it models the source formulas rather than a visible out-of-memory dialog.”

    Action/result: Began updating compatibility notes, clean-room notes, and PROGRESS.md for the heap model.

  2201. Reported: “The heap evidence is now in the docs and dashboard. I’m running the standard checks plus the full local suite so the new helper lands on the same footing as the parser and renderer work.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest discover -s tests passed; the full suite ran 212 tests.

  2202. Reported: “All checks passed, including the full suite now at 212 tests. I’m moving back to the highest-value queue: renderer compatibility, starting by auditing picture/view docs and tests for the next real source-backed gap instead of inventing a random probe.”

    Action/result: Audited renderer docs, tests, and helpers for picture/view coverage gaps.

  2203. Reported: “The renderer docs are dense, but a useful next gap stands out: the view/cel side has good layout coverage and carousel validation, while the low-level run decoder and mirror/orientation helpers could use more executable tests tied to source offsets. I’m checking the disassembly and local implementation around those routines now.”

    Action/result: Reviewed the view-frame decoder implementation, view/cel documentation, graphics tests, and symbolic/evidence notes for the bit-0x80 mirror path.

  2204. Reported: “The mirror model is already QEMU-validated for real resources, but the executable tests only cover one natural sample. I’m rereading code.object.rewrite_frame_orientation so the next tests capture source-visible edge cases like all-transparent rows and long implicit transparent padding.”

    Action/result: Disassembled 0x587d and nearby placement helpers and re-read the earlier orientation-rewrite evidence note.

  2205. Reported: “The source confirms the important rewrite details: leading transparent runs are skipped until the first visible run, all-transparent rows collapse to an empty row, and implicit trailing transparency is emitted in 15-pixel chunks before reversing the counted run bytes. I’m adding those as direct local tests.”

    Action/result: Added focused mirror-row tests to tests/test_graphics_rendering.py.

  2206. Reported: “The mirror edge tests are added. I’m running the graphics suite now; if any expected run differs, I’ll go back to the 0x587d instruction flow rather than massaging the tests.”

    Action/result: First graphics test run found an arithmetic mistake in one expected transparent-run length.

  2207. Reported: “The failing mirror test was my arithmetic, not the model: that row’s explicit width is 10, so the mirrored implicit transparent run is 10 pixels (0x1a), not 9. I’m correcting the expected byte and rerunning.”

    Action/result: Corrected the expected run byte; python3 -B -m unittest tests.test_graphics_rendering passed with 57 tests.

  2208. Reported: “The graphics suite passes with 57 tests. I’m documenting the new mirror edge coverage now so it’s clear these are source-derived row-rewrite semantics, not just arbitrary Python behavior.”

    Action/result: Began documenting the mirror edge tests in the compatibility chapter, graphics/object pipeline, clean-room notes, and progress tracker.

  2209. Reported: “The view mirror edge pass is documented. I’m doing a quick source/test scan around the object priority gate next; that’s the other renderer edge called out in PROGRESS.md, and it’s tightly tied to valid object/view drawing.”

    Action/result: Reviewed object priority-gate tests, graphics helper code, symbolic/evidence notes, and attempted an initial base-executable slice.

  2210. Reported: “The priority-gate test coverage already has the main “higher priority” and “scan downward” cases. I’m checking the overlay file offsets carefully now, because this draw routine lives in the object overlay rather than the base executable and I don’t want to cite the wrong binary slice.”

    Action/result: Confirmed SQ2/IBM_OBJS.OVL contains the object-overlay draw entry and re-read the earlier evidence note.

  2211. Reported: “The overlay disassembly shows a couple of priority-gate edges worth pinning: a low-control destination can still draw if the downward scan finds an equal priority, and rejected pixels inside a run don’t abort the rest of the run. I’m adding local tests for those exact source branches.”

    Action/result: Added object-overlay priority-gate edge tests to tests/test_graphics_rendering.py.

  2212. Reported: “The priority-gate tests are in. I’m running the graphics suite again; this gives us a tighter local oracle for object-over-picture composition without launching QEMU.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering passed with 60 tests.

  2213. Reported: “The graphics suite is now at 60 tests. I’m writing down the object-overlay priority-gate edges with the IBM_OBJS.OVL:0x9e35 branch points, then I’ll run the standard checks again.”

    Action/result: Began documenting the object-overlay priority gate edge tests and updating the progress tracker.

  2214. Reported: “The renderer edge docs are updated. I’m running the standard doc/evidence checks and full test suite again before choosing the next PROGRESS.md item.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest discover -s tests passed; the full suite ran 219 tests.

  2215. Reported: “All checks passed; the full suite is now 219 tests. I’m shifting back to picture bytecode next, looking for valid synthetic cases that the source says are real but the current QEMU corpus may not exercise, especially the rare 0xf4/0xf5 corner-path commands.”

    Action/result: Audited tools/picture_fuzz.py, picture fuzz tests, and renderer docs; confirmed curated QEMU evidence already includes corner path cases.

  2216. Reported: “The corner-path corpus is better covered than I first suspected: both 0xf4 and 0xf5 are already in curated QEMU fuzz evidence, plus local point-set tests. I’m moving to the parser/input side now to see whether the source-backed tokenizer has a matching condition model, since that is important for a portable implementation.”

    Action/result: Audited parser docs, inspect_words.py, parser tests, and opcode evidence for condition 0x0e.

  2217. Reported: “The parser docs already describe the wildcard and terminator behavior, but I don’t see a small local matcher in the tools yet. I’m rereading condition handler 0x095c and will add that model if the source shape is clean.”

    Action/result: Disassembled condition handler 0x095c and re-read the runtime/bytecode parser sections.

  2218. Reported: “The source shape is clean and a little subtle: the terminator word 0x270f succeeds immediately after the previous matches and skips any remaining operand words, while an extra non-terminator after all parsed words are consumed fails. I’m adding a small matcher helper and tests for those cases.”

    Action/result: Added input_word_sequence_matches() and result dataclass to tools/inspect_words.py.

  2219. Reported: “I’m making the matcher small but explicit: it takes the parsed IDs plus parser flags, and returns whether the condition matches, whether flag 4 would be set, and how many operand words were skipped after a terminator or mismatch. That mirrors the handler’s control flow without importing the whole logic VM.”

    Action/result: Completed the local matcher implementation.

  2220. Reported: “The matcher and tests are in. I included one source-derived edge that is easy to miss: a terminator-only pattern can match an unknown parse state because the parser count/error-position is nonzero and flag 2 is set.”

    Action/result: Added parser matcher tests to tests/test_words.py.

  2221. Reported: “The local WORDS suite passes with 13 tests. Because the terminator-on-unknown behavior is surprising, I’m checking whether the existing QEMU probe harness can validate it with one small generated logic case.”

    Action/result: python3 -B -m unittest tests.test_words passed with 13 tests; inspected parser cases in tools/logic_interpreter_probe.py.

  2222. Reported: “The harness can validate this cheaply. I’m adding one parser-edge fixture that parses an unknown word and then tests a terminator-only 0x0e pattern, which should draw if the source model is right.”

    Action/result: Added input_word_sequence_terminator_matches_unknown_word to tools/logic_interpreter_probe.py and its harness test.

  2223. Reported: “The new parser-edge fixture is wired into the harness. I’m running the relevant unit tests first, then I’ll use QEMU for just this one behavior check.”

    Action/result: python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_words passed with 26 tests, and opcode evidence check passed.

  2224. Reported: “The parser and harness unit tests pass. I’m running the single QEMU parser-edge case now to confirm the unknown-word terminator behavior against the original engine.”

    Action/result: QEMU batch parser_unknown_terminator_001 matched the original engine with 1 match, 0 mismatches, and 0 errors.

  2225. Reported: “The QEMU probe matched: the original engine does draw for a terminator-only word sequence after parsing an unknown token. I’m updating the spec text and evidence trail because that’s a real compatibility edge, not just a local helper detail.”

    Action/result: Updated parser/runtime/compatibility docs, symbolic labels, PROGRESS.md, and opcode evidence generator.

  2226. Reported: “I’m also updating the generated opcode-evidence source entry for condition 0x0e, since the condition now has a richer QEMU-backed edge case than the older one-line probe reference.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md.

  2227. Reported: “I’ve got the doc insertion points. The wording here needs to be precise: 0x270f is not “end of all parsed input”; it is an immediate successful terminator for the condition pattern once the parser state is otherwise eligible.”

    Action/result: Refined parser wording in the bytecode and runtime model.

  2228. Reported: “The parser spec text and label map are updated. I changed the opcode-evidence generator too, so I’m regenerating its mdBook chapter before continuing the evidence note.”

    Action/result: Ran python3 -B tools/logic_opcode_evidence.py.

  2229. Reported: “The opcode-evidence chapter is regenerated. I’m adding the clean-room note and progress-log entries for the parser matcher/QEMU confirmation now.”

    Action/result: Added the input-word sequence matcher evidence note and progress-log entries.

  2230. Reported: “The parser matcher evidence is in. I’m running the standard checks again, including the full suite, because this touched generated opcode evidence, logic-probe cases, and parser helpers.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest discover -s tests passed; the full suite ran 224 tests.

  2231. Reported: “The parser pass checks are clean; the full suite is now 224 tests. I’m looking at the compatibility-harness gap next: there are many strong individual probes, but a single manifest/runner would make the final suite easier to reproduce across future games and interpreter versions.”

    Action/result: Audited existing compatibility docs, tools, tests, and PROGRESS.md for a top-level suite runner gap.

  2232. Reported: “I’m going to add a lightweight suite manifest/runner rather than another ad hoc command list. The default will run only local checks; QEMU smoke and broad sweeps stay opt-in so future agents can choose the right cost level.”

    Action/result: Added tools/compatibility_suite.py.

  2233. Reported: “The suite runner is in place. I’m adding tests around the manifest and command selection/execution plumbing, with subprocess calls mocked so the normal unit suite stays fast and deterministic.”

    Action/result: Added tests/test_compatibility_suite.py.

  2234. Reported: “The runner tests are ready. I’m running the focused suite tests and doing a dry-run manifest check so we know the new tool is usable without triggering QEMU by accident.”

    Action/result: python3 -B -m unittest tests.test_compatibility_suite passed with 6 tests; dry-run output listed local checks by default and included QEMU smoke only with --include-qemu-smoke.

  2235. Reported: “The suite manifest behaves as intended: local-only by default, QEMU smoke opt-in. I’m running the default runner once now so it writes a real report and proves the plumbing outside mocks.”

    Action/result: python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_001.json passed, running 230 unit tests, building mdBook, and checking opcode evidence.

  2236. Reported: “The default suite runner passed and wrote a local report under build/compatibility-suite/. I’m documenting the runner in the compatibility chapter and AGENTS so future work uses it as the top-level entry point.”

    Action/result: Updated AGENTS.md, docs/src/compatibility_testing.md, PROGRESS.md, and clean-room notes for the compatibility suite runner.

  2237. Reported: “The suite runner is documented. I’m running the standard checks once more after the docs/AGENTS updates, then I’ll reassess PROGRESS.md for what is genuinely left versus what is waiting on future game/interpreter inputs.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest discover -s tests passed; the full suite ran 230 tests.

  2238. Reported: “The checks pass again: mdBook, opcode evidence, diff check, and 230 tests. I’m doing a tracker audit now to separate actionable SQ2 work from items that are intentionally waiting for future interpreter versions or optional hardware paths.”

    Action/result: Reviewed the current PROGRESS.md remaining-work rows and working-tree state.

  2239. Reported: “The audit shows most remaining SQ2 items are either optional UI/hardware paths or future cross-version work. One still-actionable row is sound: we have event scheduling and port-write formulas, but not a compact implementation-facing “audible channel state” helper, so I’m checking the current sound model before deciding how far that can be taken cleanly from source.”

    Action/result: Reviewed tools/agi_sound.py, sound tests, sound docs, and the existing driver-output source notes.

  2240. Reported: “The sound row is already stronger than it looked: scheduling, PC-speaker divisor, non-PC tone bytes, and attenuation envelopes are all source-backed. I’m checking the driver tick around 0x801c to avoid inventing an “audio synthesis” layer beyond what the interpreter actually outputs.”

    Action/result: Disassembled code.sound.driver_tick and re-read runtime sound documentation.

  2241. Reported: “There’s one sound improvement I can make without overreaching into synthesis: model the actual non-PC tone bytes and stop-silence bytes the driver writes, alongside the existing PC-speaker divisor. That keeps the spec at the interpreter’s hardware-output boundary, which is what the source directly gives us.”

    Action/result: Added SoundToneOutput, sound_tone_output(), and sound_stop_silence_output() to tools/agi_sound.py.

  2242. Reported: “The sound tone-output helper is added. I’m extending the sound tests to pin PC-speaker silence/enable state, non-PC high/low byte writes, high-byte suppression of the low byte, and stop-core silence output.”

    Action/result: Added tone/silence output tests to tests/test_sound_resources.py.

  2243. Reported: “The sound tests now cover the port-output boundary. I’m running them to catch any mismatch against the source formulas.”

    Action/result: python3 -B -m unittest tests.test_sound_resources passed with 16 tests.

  2244. Reported: “The sound tests pass at 16 cases. I’m updating the sound docs to mark the port-output boundary as executable evidence, while keeping physical waveform synthesis out of scope unless we later need it.”

    Action/result: Updated sound sections in the runtime model, compatibility chapter, PROGRESS.md, and clean-room notes.

  2245. Reported: “The sound port-output model is documented. I’m running the standard checks again; after that I’ll decide whether the remaining PROGRESS.md rows need more SQ2 work or are genuinely deferred.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest discover -s tests passed; the full suite ran 232 tests.

  2246. Reported: “The checks pass at 232 tests. The one fully blank tracker row is cross-version workflow; we can’t apply it to another interpreter until you provide one, but I can still document the repeatable workflow and what evidence to capture so that row is no longer an empty placeholder.”

    Action/result: Reviewed mdBook summary, symbolic-label rules, and existing cross-version references.

  2247. Reported: “I’m adding a dedicated cross-version workflow chapter to mdBook. It won’t pretend we’ve compared another interpreter yet; it will define the evidence package, label-mapping method, and compatibility-suite layers to use when the next game/version arrives.”

    Action/result: Added docs/src/cross_version_workflow.md, linked it from docs/src/SUMMARY.md, updated PROGRESS.md, and recorded the evidence note.

  2248. Reported: “I’ve got the thread context and the AGENTS rules in hand. I’m picking up at the compatibility-suite/QEMU smoke validation point: the local suite was passing, but the QEMU smoke layer hit a sandbox VNC socket bind error, so I’m going to rerun that through the approved escalation path rather than treating it as an engine failure.”

    Action/result: Reran python3 -B tools/compatibility_suite.py --include-qemu-smoke --report build/compatibility-suite/qemu_smoke_001.json with local VNC/socket access.

  2249. Reported: “The escalated run is past the local test/mdBook/opcode layers and is now inside the QEMU smoke probes. Good sign so far: this is exercising the same engine fixtures through the compatibility runner rather than a one-off command.”

    Action/result: The local unit suite passed with 232 tests, mdBook built, opcode evidence checked cleanly, and the parser-edge QEMU batch began.

  2250. Reported: “The parser edge smoke cases matched the original engine. The run has moved on to the unknown-word terminator probe, which is the small but important “condition terminator accepts this parser state” case we added earlier.”

    Action/result: parser_edges_suite.json reported 3 matches, 0 mismatches, and 0 errors.

  2251. Reported: “The second parser QEMU probe matched too. Now it’s running the picture fuzz smoke set that specifically checks command-resume semantics for line, corner, and fill commands against the real interpreter.”

    Action/result: parser_unknown_terminator_suite.json reported 1 match, 0 mismatches, and 0 errors.

  2252. Reported: “The command-resume fuzz cases matched all three. The final smoke group is the raw-operand picture decoder behavior, which is useful for valid-but-odd byte streams without wandering into exploit/garbage- memory territory.”

    Action/result: command_resume_suite.json reported 3 matches, 0 mismatches, and 0 errors.

  2253. Reported: “The escalated QEMU smoke run finished successfully. I’m going to fold that result back into the project docs/progress tracker now, including the earlier sandbox-only failure so the evidence trail is honest.”

    Action/result: raw_operand_suite.json reported 3 matches, 0 mismatches, and 0 errors; the compatibility-suite process exited zero.

  2254. Reported: “The JSON report confirms every selected layer returned 0: local unit tests, mdBook, opcode evidence, and all four QEMU smoke commands. I’m updating the book and tracker with the suite-level result rather than leaving those as isolated probe runs.”

    Action/result: Updated the compatibility chapter, progress tracker, clean-room notes, and this progress log with the qemu_smoke_001.json suite result.

  2255. Reported: “With the smoke layer recorded, I’m moving to the next high-value item in PROGRESS.md: broad picture/view compatibility. First I’m checking the suite manifest so I know exactly which QEMU batches --include-qemu-broad will run.”

    Action/result: Confirmed that broad mode selects the local checks, QEMU smoke layer, eight-picture broad carousel, and 19-case view/object stress carousel.

  2256. Reported: “The broad flag includes the smoke layer plus two larger resource sweeps: the eight-picture timed carousel and the current 19-case view/object stress carousel. I’m going to run that as a suite report now; it’s exactly the kind of reusable validation layer PROGRESS.md is asking us to grow.”

    Action/result: Started python3 -B tools/compatibility_suite.py --include-qemu-broad --report build/compatibility-suite/qemu_broad_001.json.

  2257. Reported: “The broad suite is through the local layers and has started the smoke probes. Since --include-qemu-broad deliberately includes smoke too, this gives us a single report that proves both the targeted edge probes and the larger resource carousels in one pass.”

    Action/result: The local unit suite passed with 232 tests, mdBook built, opcode evidence checked cleanly, and the parser-edge smoke batch began.

  2258. Reported: “Smoke parser edges are matching again inside the broad run. I’m letting it continue into the picture-fuzz probes and then the broader picture/view carousel sweeps.”

    Action/result: Parser-edge smoke probes matched 3/3.

  2259. Reported: “The unknown-word terminator QEMU probe matched. The command-resume picture fuzz batch is underway now; after that, the broad resource sweeps should start.”

    Action/result: Unknown-word terminator smoke probe matched 1/1.

  2260. Reported: “Command-resume matched all three again. The raw-operand picture fuzz smoke batch is running now; this is the last small probe before the broad picture carousel.”

    Action/result: Command-resume picture fuzz smoke probes matched 3/3.

  2261. Reported: “The smoke layer is clean inside the broad run. We’re into the eight-picture timed carousel now, which keeps one original-engine process alive and polls captures until each expected picture appears.”

    Action/result: Raw-operand picture fuzz smoke probes matched 3/3; the broad picture carousel began.

  2262. Reported: “Five of the eight broad-picture carousel captures have matched so far. This is the useful fast path for future multi-game sweeps, because we’re validating several real resources without rebooting DOS per case.”

    Action/result: The broad picture carousel was progressing through the expected SQ2 picture list.

  2263. Reported: “The broad picture carousel matched all eight. The view/object stress carousel has started, and the first view case has already matched.”

    Action/result: picture_carousel_broad_suite.json matched 8/8 with 0 mismatches and 0 errors; the view/object stress carousel began.

  2264. Reported: “The view carousel is progressing cleanly through normal and clipping cases. I’m watching for priority/control edge cases later in the sequence, since those are the ones most likely to reveal renderer-contract mistakes.”

    Action/result: View/object stress carousel matched the first 6 cases.

  2265. Reported: “Twelve of nineteen view/object cases have matched, including the low-priority and several transparent-color variants. The remaining cases are the stress overlays and line/priority combinations.”

    Action/result: View/object stress carousel matched 12/19 cases.

  2266. Reported: “Seventeen of nineteen matched now. The broad suite is in the final stretch of the view/object carousel.”

    Action/result: View/object stress carousel matched 17/19 cases.

  2267. Reported: “The broad suite exited cleanly. It matched all eight broad picture cases and all nineteen current view/object stress cases from one-engine carousel runs, so I’m recording that suite-level report next.”

    Action/result: view_carousel_stress_suite.json matched 19/19 with 0 mismatches and 0 errors; qemu_broad_001.json recorded return code 0 for every selected command.

  2268. Reported: “I’m folding the broad suite into the docs now. After that I’ll go back to source-first renderer work, using these QEMU reports as regression evidence rather than as the primary discovery method.”

    Action/result: Updated the compatibility chapter, progress tracker, clean-room notes, and this progress log with the qemu_broad_001.json suite result.

  2269. Reported: “The broad suite is documented. Next I’m going source-first again: I’ll inspect the picture/view renderer progress rows and the symbolic labels to find a concrete edge where the disassembly can still sharpen the implementation contract.”

    Action/result: Re-read picture/view rows in PROGRESS.md, renderer labels in docs/src/symbolic_labels.md, and graphics/compatibility docs.

  2270. Reported: “The tracker is now pointing at two renderer gaps: picture edge semantics for valid streams, and broader view/object priority-control interactions. I hit two stale chapter names while searching, so I’m listing the docs and then I’ll drill into the current graphics chapter plus the disassembly labels.”

    Action/result: Confirmed the active graphics docs live in docs/src/graphics_object_pipeline.md and docs/src/resource_files.md.

  2271. Reported: “The existing docs already carry a lot of renderer detail. I’m checking the local renderer code and tests for explicit gaps now, because the best next step is likely to codify one source-backed edge as a deterministic test/model rather than adding another broad capture.”

    Action/result: Reviewed tools/agi_graphics.py, tests/test_graphics_rendering.py, tests/test_picture_fuzz.py, and tools/picture_fuzz.py.

  2272. Reported: “The local picture renderer is fairly complete, but one place worth source-checking is relative line decoding and clamping: it is simple in Python, yet edge behavior around signed nibbles and boundaries is exactly the sort of thing a future implementation can get subtly wrong. I’m going back to the disassembly for 0xf7 and the shared line helper before adding anything.”

    Action/result: Disassembled code.picture.cmd_draw_relative_lines (0x665e) and code.picture.draw_line (0x66e1).

  2273. Reported: “The source confirms a boundary nuance in 0xf7: negative relative deltas are unsigned 8-bit subtraction, then clamped only on the high side. That means underflow clamps to the right/bottom edge, not to 0, so the current Python helper is too “reasonable” for that edge. I’m going to patch the renderer and add tests/fuzz cases from the disassembly.”

    Action/result: Identified the source-backed relative-line underflow rule.

  2274. Reported: “I’m making the renderer match the source rather than the intuitive clamp. The key edit is small: relative deltas wrap in an 8-bit register before the helper clamps values above the screen maximum.”

    Action/result: Updated PictureRenderer.draw_relative_lines() and added local graphics tests plus two curated picture-fuzz cases.

  2275. Reported: “The code and deterministic tests are patched. I’m running the focused renderer/fuzz tests first; if those pass, I’ll run the new two-case QEMU batch to confirm the source reading against the original engine.”

    Action/result: tests.test_graphics_rendering passed with 62 tests and tests.test_picture_fuzz passed with 13 tests.

  2276. Reported: “The focused local tests passed: graphics is at 62 tests and picture fuzz at 13. Now I’m running the two new relative-underflow picture fixtures in QEMU as confirmation, with source still being the primary evidence.”

    Action/result: The first QEMU attempt failed before launching QEMU because the on-disk fuzz corpus did not yet include the new base-case directories.

  2277. Reported: “The QEMU batch didn’t reach QEMU yet: the corpus on disk predates the new base cases, so the harness couldn’t find their case.json files. I’m regenerating the local fuzz corpus, then I’ll rerun the same confirmation batch.”

    Action/result: Regenerated build/picture-fuzz/corpus; it now contains 1,062 cases, 1,060 marked safe for QEMU.

  2278. Reported: “The regenerated corpus now has 1,062 total cases, with 1,060 marked safe for QEMU. The two new relative-underflow cases are in that safe set, so I’m rerunning the original-engine confirmation batch.”

    Action/result: relative_underflow_001 matched both cases in QEMU with 0 mismatches and 0 errors.

  2279. Reported: “The original engine matched both relative-underflow fixtures. Since this is a compact, source-backed renderer edge, I’m going to promote it into the compatibility suite’s QEMU smoke layer so future runs don’t lose it.”

    Action/result: Added picture_fuzz_relative_underflow_qemu to tools/compatibility_suite.py and updated the suite manifest test.

  2280. Reported: “The new underflow probe is now part of the smoke manifest. I’m running the suite-manifest tests and then I’ll produce a fresh smoke report that includes this new command.”

    Action/result: tests.test_compatibility_suite passed and the smoke dry-run listed the new relative-underflow command.

  2281. Reported: “The manifest tests pass and the dry-run shows the new relative-underflow smoke command. I’m running the updated QEMU smoke suite now so the report reflects the current manifest, not the previous one.”

    Action/result: Started python3 -B tools/compatibility_suite.py --include-qemu-smoke --report build/compatibility-suite/qemu_smoke_002.json.

  2282. Reported: “The updated smoke suite is through the expanded local tests: 235 unit tests now pass, reflecting the new renderer and suite tests. It’s in the QEMU parser probes again.”

    Action/result: Local tests, mdBook, and opcode-evidence check completed inside the smoke runner.

  2283. Reported: “Parser smoke is progressing normally. I’m waiting it through to the new relative-line underflow QEMU command, which is the fresh evidence this run is meant to capture.”

    Action/result: Parser-edge smoke was in progress.

  2284. Reported: “The parser QEMU probes matched. The suite is now in the existing picture-fuzz smoke checks before it reaches the new underflow pair.”

    Action/result: Parser-edge smoke matched 3/3 and unknown-word terminator matched 1/1.

  2285. Reported: “Command-resume matched all three. Raw-operand smoke is running now; after this, the updated suite should execute the new relative-underflow command.”

    Action/result: Command-resume smoke matched 3/3.

  2286. Reported: “Raw-operand smoke matched, and the fresh relative-underflow command is now running inside the suite report. This is the one that proves the 8-bit wrap-and-clamp behavior against QEMU.”

    Action/result: Raw-operand smoke matched 3/3.

  2287. Reported: “The updated smoke suite passed, including the new relative-underflow command. I’m updating the renderer prose, compatibility evidence, and progress tracker so this edge is no longer just a local code change.”

    Action/result: qemu_smoke_002.json exited zero; the relative-underflow suite batch matched 2/2.

  2288. Reported: “One bit of bookkeeping: regenerating the corpus made the old “1,060-case corpus” wording stale. The new state is 1,062 total cases, 1,060 safe for QEMU, so I’m correcting that wherever it matters.”

    Action/result: Searched active docs and tracker for stale corpus/report wording.

  2289. Reported: “I found the expected stale corpus mentions in the progress tracker, compatibility chapter, and earlier raw-operand notes. I’m leaving historical progress-log entries alone unless they’re current documentation, but I’ll update the active spec/tracker text and add a new clean-room note for the relative-line source finding.”

    Action/result: Prepared documentation updates for the active compatibility and progress surfaces.

  2290. Reported: “I’m also going to add the relative-underflow rule to the compact runtime contract, not only the graphics chapter. That way a future implementation has the high-level rule in the spec section and the disassembly-backed detail in the evidence section.”

    Action/result: Located the exact runtime-model and graphics-pipeline anchors.

  2291. Reported: “I’ve got the exact anchors. The docs update will say the relative endpoint is produced by 8-bit register arithmetic, then clamped only if it exceeds the screen maximum; that phrasing avoids implying a symmetric clamp to zero.”

    Action/result: Updated docs/src/graphics_object_pipeline.md and docs/src/runtime_model.md.

  2292. Reported: “That patch was too broad and one context line had drifted, so I’m splitting it into smaller edits. Same content, just less brittle.”

    Action/result: Retried the documentation patch in smaller chunks.

  2293. Reported: “The renderer contract text is updated. I’m patching the compatibility chapter next, including the current smoke report name and the corpus count.”

    Action/result: Updated the compatibility-suite command and smoke report description to qemu_smoke_002.json.

  2294. Reported: “The current smoke report now points to qemu_smoke_002. I’m adding the specific relative-underflow batch to the picture-fuzz evidence list.”

    Action/result: Added relative_underflow_001 to the compatibility chapter’s fuzz evidence list.

  2295. Reported: “The edge batch is recorded. I’m correcting the corpus count in the same chapter now.”

    Action/result: Updated the active fuzz corpus count to 1,062 cases, 1,060 QEMU-safe.

  2296. Reported: “Compatibility chapter is current. I’m updating PROGRESS.md so the dashboard reflects both the new 0xf7 edge and the expanded smoke suite.”

    Action/result: Updated the picture and compatibility-suite rows in PROGRESS.md.

  2297. Reported: “The dashboard is updated. I’m adding the clean-room note with the exact disassembly commands and QEMU confirmation for the underflow rule.”

    Action/result: Added the clean-room note for the picture relative-line underflow edge.

  2298. Reported: “The progress log is caught up through the underflow work. I’m running the standard validation now: mdBook, opcode evidence freshness, whitespace, and the full unit suite.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest discover -s tests passed; the full unit suite ran 235 tests.

  2299. Reported: “Validation passed: mdBook, opcode evidence, diff check, and 235 unit tests are clean. I’m recording that result, then I’ll continue with the next source-first renderer/object item from PROGRESS.md.”

    Action/result: Added the validation result to this progress log.

  2300. Reported: “For the next renderer-side gap, I’m looking at the object placement/control acceptance path. The tracker says full object-record placement fixtures are optional, but the disassembly may still let us tighten the implementation-facing model without inventing new behavior.”

    Action/result: Re-read tools/agi_graphics.py, the control-acceptance tests, graphics docs, and disassembly for code.object.control_acceptance at 0x56b8.

  2301. Reported: “The control-acceptance helper already has a good source model, but I see a small unpinned edge in the local tests: high nibbles outside 0x00..0x30 are treated as nonzero/pass-through classes with final state (flag3=false, flag0=false). I’m adding direct tests for that source branch before hunting a larger placement fixture.”

    Action/result: Identified source-branch coverage for the other-nonzero class state and priority-15 bypass path.

  2302. Reported: “The test anchor shifted a little, so I’m reading the immediate test block and will insert the new cases at the right spot.”

    Action/result: Located the control-acceptance test block in tests/test_graphics_rendering.py.

  2303. Reported: “Found the right insertion point. I’m adding the direct source-branch tests now.”

    Action/result: Added local tests for other-nonzero high-nibble classes and priority-15 scan bypass/event-flag clearing.

  2304. Reported: “The source-branch tests are in. I’m running the graphics suite to make sure the local control-acceptance model still lines up cleanly.”

    Action/result: python3 -B -m unittest tests.test_graphics_rendering passed with 64 tests.

  2305. Reported: “Graphics tests pass at 64. I’m documenting this as a local source-model tightening rather than a QEMU result: it pins the remaining class-state branches in 0x56b8.”

    Action/result: Updated the progress tracker, compatibility chapter, clean-room notes, and this progress log for the control-acceptance branch tests.

  2306. Reported: “Since the broad suite includes the smoke layer, qemu_broad_001 is now slightly stale: it predates the relative-underflow smoke command. I’m going to run a fresh broad report so the top-level compatibility suite is current end to end.”

    Action/result: Started python3 -B tools/compatibility_suite.py --include-qemu-broad --report build/compatibility-suite/qemu_broad_002.json.

  2307. Reported: “The refreshed broad suite is through local checks with 237 tests now passing. It’s in the smoke probes first, and then it will rerun the broad picture and view carousels.”

    Action/result: Local tests, mdBook, and opcode-evidence check completed inside the refreshed broad runner.

  2308. Reported: “Parser edge smoke matched again inside the refreshed broad suite. The run is continuing through the rest of smoke before the resource carousels.”

    Action/result: Parser-edge QEMU probes matched 3/3.

  2309. Reported: “The unknown-word parser terminator probe matched. Command-resume picture fuzz is running now.”

    Action/result: Unknown-word terminator probe matched 1/1.

  2310. Reported: “Command-resume matched. Raw operands are next, then the refreshed relative-underflow pair.”

    Action/result: Command-resume picture fuzz probes matched 3/3.

  2311. Reported: “Raw operands matched. The relative-underflow pair is now running as part of the refreshed broad suite.”

    Action/result: Raw-operand picture fuzz probes matched 3/3.

  2312. Reported: “The refreshed smoke layer, including relative underflow, matched. The broad picture carousel has started and has already matched its first two pictures.”

    Action/result: Relative-underflow probes matched 2/2 and the broad picture carousel began.

  2313. Reported: “All eight picture carousel captures have appeared; it’s printing the per-case match summary now before moving into the view/object stress carousel.”

    Action/result: Broad picture carousel polling reached all 8 expected captures.

  2314. Reported: “The broad picture carousel matched all eight. The view/object carousel is underway and has matched the first four cases.”

    Action/result: picture_carousel_broad_suite.json matched 8/8 with 0 mismatches and 0 errors; view/object stress carousel began.

  2315. Reported: “Ten of nineteen view/object cases matched, including the clipping set and low-priority case. The transparent-color stress cases are next.”

    Action/result: View/object stress carousel matched 10/19 cases.

  2316. Reported: “Fifteen of nineteen matched. The broad suite is nearing the end of the view/object carousel.”

    Action/result: View/object stress carousel matched 15/19 cases.

  2317. Reported: “All nineteen view/object captures have appeared; it’s finishing the match summary now.”

    Action/result: View/object stress carousel polling reached all 19 expected captures.

  2318. Reported: “The refreshed broad suite exited cleanly: smoke, eight-picture carousel, and nineteen view/object cases all matched. I’m checking the report and then updating the docs from qemu_broad_001 to the current qemu_broad_002.”

    Action/result: Verified qemu_broad_002.json has return code 0 for every selected command.

  2319. Reported: “The report confirms every selected command returned zero, including the new picture_fuzz_relative_underflow_qemu. I’m updating the report references and broad-suite note now.”

    Action/result: Updated compatibility docs, PROGRESS.md, and clean-room notes to reference qemu_broad_002.json.

  2320. Reported: “The current broad report is documented. I’m adding the matching progress-log entries for the refreshed broad run.”

    Action/result: Added this progress-log batch for the refreshed broad compatibility run.

  2321. Reported: “With the suite current, I’m re-reading the remaining tracker items. I’ll keep taking the next source-backed item that can be advanced locally, and I’ll avoid turning optional future-version work into fake completion.”

    Action/result: Re-read the remaining PROGRESS.md subsystem rows and highest-value work list.

  2322. Reported: “The remaining list is now mostly conditional or future-input work. One still-actionable item is the final spec polish: turning dense notes into implementation-ready contracts. I’m going to start by checking whether the graphics/runtime chapters now duplicate or conflict after the new relative-line and control-acceptance additions.”

    Action/result: Searched active docs for stale report names, corpus counts, relative-underflow wording, and provisional/remaining-risk language.

  2323. Reported: “I found one active prose section that is lagging behind the evidence: the “Provisional areas” picture paragraph still says the renderer has only two real SQ2 resources, even though we now have all 74 via snapshot/chunked carousel. I’m tightening that section so the high-level status matches the tracker.”

    Action/result: Located the stale paragraph in docs/src/compatibility_testing.md.

  2324. Reported: “This is exactly the kind of spec-polish work left in PROGRESS: not new reversing, just removing stale or underspecified wording so another implementer gets the right confidence level.”

    Action/result: Updated the provisional picture-renderer paragraph with all-picture SQ2 coverage, raw operand, relative-underflow, and full-EGA mask-scope wording.

  2325. Reported: “The provisional picture paragraph now reflects the current all-picture SQ2 coverage and the new relative-underflow evidence. I’m logging that polish pass.”

    Action/result: Added this progress-log entry for the compatibility prose polish.

  2326. Reported: “Next I’m checking current_status.md, since that kind of summary page often drifts while the detailed chapters stay accurate. If it’s stale, I’ll bring it in line with the tracker and current suite reports.”

    Action/result: Reviewed docs/src/current_status.md and found stale focus/immediate-work text.

  2327. Reported: “current_status.md is indeed stale: it still says the immediate focus is “return to the logic interpreter,” while the tracker now says logic opcodes are covered and the active risk is renderer/spec polish/future-version breadth. I’m going to rewrite the handoff sections so the next agent starts from the actual current state.”

    Action/result: Updated the current-focus section with the completed logic opcode state, current compatibility-suite reports, relative-line underflow, and control-acceptance branch-test work.

  2328. Reported: “The top handoff summary is current now. I’m updating the graphics findings and immediate-next-work sections so they don’t send the next pass back toward already-completed opcode coverage.”

    Action/result: Updated confirmed graphics findings and immediate next work in docs/src/current_status.md.

  2329. Reported: “current_status.md still has a long old “Remaining Source-Backed Families” section underneath the handoff list. I’m checking the headings so I can either trim or rewrite it without accidentally dropping a useful later section.”

    Action/result: Confirmed the stale section was the final section of the file.

  2330. Reported: “The stale “Remaining Source-Backed Families” section is the final section, so I can replace it cleanly with a short deferred-work summary that points back to PROGRESS.md for detail.”

    Action/result: Replaced the stale long section with a compact deferred or conditional work summary.

  2331. Reported: “current_status.md is now a real handoff again: current suite reports, renderer edge state, immediate next work, and deferred items are all aligned with PROGRESS.md. I’m logging that documentation cleanup.”

    Action/result: Added this progress-log entry for the current-status cleanup.

  2332. Reported: “I’m doing a quick stale-reference scan now. If the active docs are clean, I’ll run validation again and then reassess whether the remaining PROGRESS.md items are genuinely blocked on future inputs or optional paths.”

    Action/result: Active summary/tracker docs had no stale qemu_smoke_001/qemu_broad_001 or old corpus-count references; old references remain only in chronological logs.

  2333. Reported: “The active summary pages are clean: stale qemu_smoke_001/ qemu_broad_001 and old corpus counts remain only in chronological logs, where they describe what happened at the time. I’m running the standard checks again after the handoff cleanup.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest discover -s tests passed; the full unit suite ran 237 tests.

  2334. Reported: “The standard checks passed again: mdBook, opcode evidence, diff check, and 237 tests. I’m updating PROGRESS.md’s high-value list so it reflects the current local stopping point instead of implying there is an obvious next SQ2 opcode batch still waiting.”

    Action/result: Rewrote the high-value remaining-work list to separate future-version blockers, source-first renderer edges, spec polish, compatibility-suite maintenance, and optional conditional paths.

  2335. Reported: “PROGRESS.md now separates real blockers from optional future work: additional interpreter inputs, new source-discovered renderer edges, continued spec polish, and manifest maintenance. I’m logging that and then I’ll run one final validation/status pass.”

    Action/result: Added this progress-log entry before the final validation pass.

  2336. Reported: “Final validation pass now: book, opcode-evidence check, whitespace check, full unit suite, then I’ll inspect the worktree status.”

    Action/result: mdbook build docs, python3 -B tools/logic_opcode_evidence.py --check, git diff --check, and python3 -B -m unittest discover -s tests passed; the full unit suite ran 237 tests.

  2337. Reported: “Final validation passed: mdBook, opcode evidence, diff check, and 237 tests. I’m recording that result, then doing a last status/diff-stat read.”

    Action/result: Added this final validation result to the progress log.

  2338. Reported: “Because the progress log changed after the validation pass, I’m doing a lightweight final docs/whitespace check plus status/diff stat.”

    Action/result: mdbook build docs and git diff --check passed. Git status shows the expected in-progress tracked edits plus untracked generated build/ artifacts and new project files.

  2339. Reported: “The lightweight check passed too, and the worktree is in the expected large in-progress state with generated build/ artifacts untracked. I’m adding one final progress-log note and then a last mdBook/whitespace check so the log itself is covered.”

    Action/result: Added this final progress-log note before the last lightweight check.

  2340. Reported: “I’ll stage the tracked project changes plus the new docs/tests/tools, leaving generated build/ artifacts and installer images out of the commit. I’ll make the commit message explicit that the work stops here because the remaining items are future-input dependent or optional/conditional rather than a concrete local task.”

    Action/result: Began the commit turn by reviewing status, diff stat, and this progress-log tail.

  2341. Reported: “I’ll treat this as a repo hygiene/setup task: first I’ll audit what build/ contains and what tracked files still point at it or the old MS-DOS installers, then I’ll replace those assumptions with a reproducible FreeDOS setup path before deleting anything.”

    Action/result: Started the build/MS-DOS cleanup pass by scanning active scripts, tests, docs, and git status.

  2342. Reported: “FreeDOS’ official download page currently presents FreeDOS 1.4 as the stable release, with a separate July 2026 test distribution. I plan to make the setup script pin stable 1.4 by default, with URL and checksum overrides so we can update it cleanly later.”

    Action/result: Chose FreeDOS 1.4 LiteUSB as the reproducible default for the setup script and planned override flags for future releases.

  2343. Reported: “I found the new external game copy under games/SQ2, and the only untracked top-level directories right now are games/, build/, and 002962_ms_dos_622/. I’ll keep games/ ignored and private, and make the scripts look there only through a configurable path.”

    Action/result: Confirmed the game data is outside tracked files and added ignore rules for private/generated directories.

  2344. Reported: “I’m going to add a small path helper rather than sprinkling new literals around: game data should come from AGI_GAME_DIR, while QEMU defaults should move from the old MS-DOS image to a generated FreeDOS image.”

    Action/result: Added tools/project_paths.py and began replacing direct SQ2/ and build/dos622/dos622.img assumptions in active tooling.

  2345. Reported: “For the disk image setup, I’m aiming for a script that downloads the FreeDOS USB image, verifies its checksum, extracts the raw image, and optionally copies a local game directory into it. I’m also teaching the QEMU helpers to detect the FAT partition offset instead of assuming the old sector-63 layout.”

    Action/result: Added mtools partition-offset auto-detection to the QEMU snapshot helpers and started the FreeDOS setup script.

  2346. Reported: “Understood. I’m correcting the half-step I made before the interruption: no games/SQ2 fallback. The tooling can accept AGI_GAME_DIR and I’ll add explicit --game-dir where practical, but an omitted game directory should fail loudly instead of silently picking SQ2.”

    Action/result: Removed the implicit SQ2 default and made game_dir() require either --game-dir PATH or AGI_GAME_DIR=PATH.

  2347. Reported: “I’m going to use a single project-level --game-dir PATH/ AGI_GAME_DIR mechanism for the existing tools. A few scripts import shared resource helpers before their own argument parsing runs, so the helper will consume the global --game-dir option early; that keeps the existing tool surface working without copying path plumbing through every file.”

    Action/result: Implemented project-wide early consumption of --game-dir for tools that import resource helpers before their own parsers run.

  2348. Reported: “Now I’m adding the FreeDOS image builder. It won’t copy a game unless a game directory is explicitly supplied, which keeps the base DOS setup separate from whichever interpreter/game we’re examining.”

    Action/result: Added tools/setup_freedos_image.py to download and verify FreeDOS LiteUSB, extract the raw image, patch prompt boot files, and optionally copy an explicit local game directory.

  2349. Reported: “Small cleanup while I’m here: the setup script should not inspect game options unless --copy-game is used, so I’m trimming an unnecessary check from its entrypoint.”

    Action/result: Removed an unnecessary entrypoint check from the FreeDOS setup script.

  2350. Reported: “I’m tightening decrypt_agi.py as well: if someone provides explicit loader/payload paths, it should not unnecessarily demand a game directory; otherwise it derives those paths from the required game parameter.”

    Action/result: Updated tools/decrypt_agi.py so --loader/--payload may be explicit, while path derivation requires --game-dir or AGI_GAME_DIR.

  2351. Reported: “For the setup script specifically, I’m making --game-dir an explicit documented option because it only matters when --copy-game is requested. That avoids hidden behavior in the bootstrap path.”

    Action/result: Added explicit --game-dir handling to tools/setup_freedos_image.py for --copy-game runs.

  2352. Reported: “I’m doing another pass over hard-coded paths now. The goal is not to rewrite historical evidence notes, but active scripts and setup docs should no longer imply SQ2 is the default or that the old MS-DOS image is required.”

    Action/result: Scanned active scripts/tests/docs for old SQ2, MS-DOS, and build/dos622 assumptions.

  2353. Reported: “I caught a small Python standard-library slip in the setup script before running it: URL parsing lives in urllib.parse, not urllib.request. Tiny, but exactly the kind of tiny that ruins a bootstrap script at step one.”

    Action/result: Fixed the FreeDOS setup script to use urllib.parse.urlparse.

  2354. Reported: “I’m updating the project-facing instructions next. This is where I’m making the multi-game expectation explicit: games/ is a private local area, tools require --game-dir or AGI_GAME_DIR, and generated DOS images live under disposable build/.”

    Action/result: Added root README.md and updated AGENTS.md for explicit game directories, ignored private game data, and FreeDOS image setup.

  2355. Reported: “I noticed the new docs want --print-mtools-image to be a harmless query when the image already exists. I’m adjusting the script so that option can be used safely in mtools examples without re-downloading or re-extracting anything.”

    Action/result: Made tools/setup_freedos_image.py --print-mtools-image return the existing image target without rebuilding when possible.

  2356. Reported: “One more nicety: querying the image target should not require mtools to already be installed. I’m moving that requirement to the setup/copy path.”

    Action/result: Delayed the mtools requirement check until the setup script actually needs to modify the image.

  2357. Reported: “I’m now updating the mdBook-facing setup references. Historical command logs can keep their old paths, but the current compatibility chapter should teach the new explicit game-directory workflow.”

    Action/result: Updated active mdBook pages and PROGRESS.md to clarify explicit --game-dir/AGI_GAME_DIR selection and remove old active setup assumptions.

  2358. Reported: “The docs now distinguish ‘this evidence came from SQ2’ from ‘the tooling should choose SQ2 automatically.’ I’m doing one more active-reference scan before I remove the generated directories.”

    Action/result: Scanned active instructions and compatibility commands for stale default-game and old DOS-image assumptions.

  2359. Reported: “I’m adding a small unit test for the no-default rule. That way a future cleanup can’t quietly reintroduce games/SQ2 as a fallback while we’re focusing on other interpreter versions.”

    Action/result: Added tests/test_project_paths.py covering required game parameters, environment selection, and global --game-dir consumption.

  2360. Reported: “At this point the generated outputs have been replaced by rebuild instructions and the old installer path is no longer needed for setup. I’m removing only build/ and 002962_ms_dos_622/, leaving games/ alone and ignored.”

    Action/result: Removed build/ and 002962_ms_dos_622/; retained the private ignored games/ directory.

  2361. Reported: “The old generated directories are gone. I’m logging the setup/cleanup steps now before validation, so the historical record includes both the path-policy correction and the artifact removal.”

    Action/result: Added progress-log entries for the FreeDOS setup and cleanup pass before validation.

  2362. Reported: “Validation pass now: syntax, unit tests with an explicit AGI_GAME_DIR, mdBook, opcode evidence, and whitespace. If anything still assumes a default game, this should flush it out quickly.”

    Action/result: python3 -B -m py_compile, AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests, mdbook build docs, AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check, and git diff --check all passed. The unit suite ran 242 tests.

  2363. Reported: “Validation passed: syntax compile, mdBook, opcode evidence, whitespace, and the full unit suite with AGI_GAME_DIR=games/SQ2 all succeeded. I’m checking the post-test worktree now, including whether any generated docs/build output appeared.”

    Action/result: Post-validation status showed the intended tracked edits and new files, plus only a generated tools/__pycache__/ cache.

  2364. Reported: “The checks created only Python bytecode under tools/__pycache__; I’m cleaning that generated cache out of the worktree. build/ and the old installer directory remain removed.”

    Action/result: Removed tools/__pycache__/.

  2365. Reported: “I’m adding the official FreeDOS download link to the README so the default URL/checksum choice is traceable without burying it inside the script. Then I’ll do the final lightweight checks again because the progress log and README changed after the first validation pass.”

    Action/result: Linked the README setup section to the official FreeDOS download page before the final lightweight validation pass.

  2366. Reported: “I’ll run the base FreeDOS setup first, without copying any game data, since that should prove the reproducible image bootstrap path independently of SQ2/LSL/KQ inputs. If the sandbox blocks the download, I’ll rerun with the required network approval.”

    Action/result: The sandboxed download failed with DNS resolution blocked, as expected for network-restricted shell commands.

  2367. Reported: “The first run reached the expected sandbox network wall: DNS resolution failed while downloading the FreeDOS zip. I’m rerunning the same setup command with network escalation so it can actually fetch the official image.”

    Action/result: The escalated download succeeded, but the script rejected the cached zip because the baked-in SHA-256 was wrong.

  2368. Reported: “The escalated download worked, and the failure is useful: my baked-in SHA-256 was wrong. The downloaded file matches FreeDOS’ official verify.txt value for FD14-LiteUSB.zip, so I’m correcting the script to the official hash and rerunning.”

    Action/result: Corrected FREEDOS_LITEUSB_SHA256 to 857dcd2ebf9d3d094320154db5fb5b830acba6fb98f981a95a0ca7ab3350338b.

  2369. Reported: “The cached zip is already present from the successful download, so the rerun should verify and extract without needing another network fetch. After that I’ll inspect the image with mtools and, if promising, do a quick QEMU boot screenshot.”

    Action/result: python3 -B tools/setup_freedos_image.py --force completed, extracted FD14LITE.img to build/freedos/freedos.img, and auto-detected the mtools target build/freedos/freedos.img@@32256.

  2370. Reported: “The setup script now completes: it extracted FD14LITE.img to build/freedos/freedos.img, and auto-detected the FAT partition at @@32256. I’m checking the filesystem contents next, then I’ll try a QEMU boot.”

    Action/result: mdir showed FreeDOS boot files, patched AUTOEXEC.BAT/FDAUTO.BAT, and about 13 MB free on the image.

  2371. Reported: “The image has the expected FreeDOS boot files and our patched AUTOEXEC.BAT/FDAUTO.BAT. I’m booting it under QEMU now and will capture the VGA output so we can see whether it lands somewhere usable.”

    Action/result: The sandbox blocked binding QEMU’s local VNC socket.

  2372. Reported: “QEMU itself started, but the sandbox blocked binding the local VNC socket. I’m rerunning QEMU with escalation for the local display socket so I can capture a screenshot.”

    Action/result: QEMU booted the generated FreeDOS image with local VNC enabled.

  2373. Reported: “QEMU is running now. I’m giving FreeDOS a few seconds to finish booting, then I’ll ask the monitor for a screenshot and shut it down cleanly.”

    Action/result: Captured build/freedos/boot.ppm and shut QEMU down.

  2374. Reported: “The screenshot was captured. I’m shutting QEMU down, then I’ll convert the PPM to PNG so we can inspect what FreeDOS actually displayed.”

    Action/result: Converted build/freedos/boot.ppm to build/freedos/boot.png; the screenshot shows FreeDOS at a plain C:\> prompt.

  2375. Reported: “The boot screenshot is good: FreeDOS reaches a plain C:\> prompt. I’m going to test the optional explicit game-copy path too, since that’s the README path people will use for manual engine runs.”

    Action/result: Checked games/SQ2 size and file count before the copy test.

  2376. Reported: “The local SQ2 copy is small enough for the LiteUSB image, so I’m rerunning setup with an explicit game directory and DOS target name.”

    Action/result: python3 -B tools/setup_freedos_image.py --force --copy-game --game-dir games/SQ2 --dos-game-dir SQ2 completed and copied the game to C:\SQ2.

  2377. Reported: “The explicit --copy-game --game-dir games/SQ2 path completed too. I’m checking the DOS directory listing, then I’ll do one launch screenshot of C:\SQ2\SIERRA under FreeDOS.”

    Action/result: mdir confirmed the SQ2 files inside C:\SQ2 with about 12 MB free remaining.

  2378. Reported: “The copied game directory looks right inside the FreeDOS image. I’m booting QEMU again and launching SIERRA from C:\SQ2 to confirm the generated image is actually usable for our engine runs.”

    Action/result: Booted the image in QEMU, typed cd \SQ2 and SIERRA through the monitor, and captured build/freedos/sq2_freedos.ppm.

  2379. Reported: “FreeDOS boots, and the copied SQ2 run reaches the game title screen under QEMU. I’m recording the checksum correction and validation screenshots in the progress log, then I’ll run a small final check on the edited script/docs.”

    Action/result: Converted build/freedos/sq2_freedos.ppm to build/freedos/sq2_freedos.png; the screenshot shows the SQ2 title screen under FreeDOS.

  2380. Reported: “I’ll add a short ‘manual image use’ section to the README: mtools target discovery, copying files/directories into the FAT image, and the exact QEMU command plus monitor basics.”

    Action/result: Added README instructions for --print-mtools-image, mdir, mmd, mcopy, manual QEMU boot, monitor sendkey, screendump, and ImageMagick conversion.

  2381. Reported: “I found the current FreeDOS section; it has launch basics but not enough mtools detail. I’m adding concrete copy/list examples that use the script’s --print-mtools-image output so nobody has to remember the partition offset.”

    Action/result: Expanded the README FreeDOS image section with concrete mtools examples that derive the image target from the setup helper.

  2382. Reported: “Got it: any dynamic validation I do from here will use the FreeDOS setup, and I’ll keep MS-DOS out of the workflow unless you explicitly ask for historical comparison. I’m going to continue the GR v3 pass source-first and only use QEMU/FreeDOS if I need behavior confirmation.”

    Action/result: Started the Gold Rush / AGI v3 comparison as a static disassembly/resource-format pass.

  2383. Reported: “The repo is clean apart from an untracked run.sh, and games/GR is present with the combined GRDIR/GRVOL.* layout. I’m checking the existing docs and resource scripts now so the v3 notes land in the right places and the tooling follows the game-dir parameterization rule.”

    Action/result: Checked git status --short, listed project files, and inventoried games/GR and games/SQ2.

  2384. Reported: “The existing resource chapter is SQ2/v2-specific, so I’ll add a GR/v3 section rather than disturb the v2 evidence. One useful contrast already holds up: SQ2 resource records are direct 5-byte-header payloads, while GR records use a 7-byte header with expanded and stored lengths plus transform selection.”

    Action/result: Read the resource, symbolic-label, and progress-tracker chapters plus current resource-related scripts.

  2385. Reported: “I’m going back to the disassembly now, particularly the GR directory loader and the two decompression call sites. The goal is to turn the first-pass observations into a concrete parser/decompressor, so we can inspect GR logic resources directly instead of treating compression as a black box.”

    Action/result: Verified the GR executable header and regenerated the decrypted SQ2 executable for side-by-side static comparison.

  2386. Reported: “I’ve confirmed the old offset rule still applies to GR: the MZ header is 0x200 bytes, so the stable “image offset” documentation maps to file offset image + 0x200. I’m using dd slices with explicit image origins to avoid the earlier ndisasm -e ambiguity.”

    Action/result: Disassembled focused GR windows around image offsets 0x30a0, 0x44d0, 0x07d0, and 0x9a40.

  2387. Reported: “The GR decompression paths are now clear enough to model locally. The “general” path is a reset-capable 9-to-11-bit dictionary stream with 0x100 as reset and 0x101 as end; the picture path is a nibble realigner that packs the color byte after 0xf0 and 0xf2 into half-bytes, then expands them back to ordinary picture opcodes.”

    Action/result: Interpreted GR image 0x07f4 as the dictionary decompressor and GR image 0x9a5b as the picture-nibble transform.

  2388. Reported: “I’m going to add a small resource module now rather than fold this into the SQ2-only disassembler. It will autodetect split v2 directories versus combined v3 directories, require an explicit game path at the CLI, and expose the GR decompression routines as reusable code for later interpreter-version comparisons.”

    Action/result: Added tools/agi_resources.py with v2/v3 directory detection, v3 record reading, dictionary expansion, and picture-nibble expansion.

  2389. Reported: “The first reusable parser/decompressor is in place. I’m running it against GR and SQ2 now; the useful check is that GR logic/view/sound payloads expand to plausible resource bytes, and picture resources expand back into the same ordinary picture opcode stream shape the v2 renderer already understands.”

    Action/result: The first CLI run exposed an argument-order bug: the project-wide --game-dir option was parsed after the tool’s local argparse parser.

  2390. Reported: “Tiny CLI snag: the project-wide --game-dir consumer needs to run before this tool’s own argparse parser. I’m fixing that plumbing now; the resource code itself hasn’t run yet.”

    Action/result: Moved configured_game_dir() before local argument parsing in tools/agi_resources.py.

  2391. Reported: “The new code expands every present GR resource successfully: 182 logic entries, 186 pictures, 247 views, and 44 sounds. The transform census is useful too: all pictures use the nibble path; logic/view mostly use the dictionary path; a few logic/sound resources are direct because stored length equals expanded length.”

    Action/result: Ran the GR parser over all present entries; the transform census was direct 5, dictionary 468, picture-nibble 186.

  2392. Reported: “I’ve added focused tests for the two decompression primitives plus GR/SQ2 container detection. Now I’m running the new test file first, then I’ll run the normal local suite with the explicit SQ2 game path so I don’t mask any regressions in the older tooling.”

    Action/result: Added tests/test_agi_resources.py; the new focused tests passed. The full suite exposed an unrelated read-only fixture-copy permission problem.

  2393. Reported: “The new GR tests pass. The full existing suite hits an unrelated fixture-copy problem: the private game files are read-only, and several fixture builders copy them into temp dirs and then try to overwrite LOGDIR or VOL.3 without changing permissions. I’m not going to blur that into the GR compression change unless it becomes necessary, but I’ll record it as a verification caveat.”

    Action/result: Recorded the full-suite failure as a fixture permission caveat rather than a GR parser failure.

  2394. Reported: “There’s another version-level difference beyond compression: the GR v3 dispatcher tables are larger. The action dispatcher checks through 0xb5 and calls AGIDATA table 0x0440; the condition dispatcher checks through 0x25 and calls table 0x0762. SQ2 v2 topped out at 0xaf actions and 0x12 conditions, so I’m doing a quick decoded-logic census to see which of those newer slots are actually used in Gold Rush.”

    Action/result: Disassembled GR dispatch code and ran a decoded-logic census using v3 table bases; all present GR logic parsed without errors.

  2395. Reported: “I’m also extending the local logic disassembler just enough to consume the new resource module. That gives us an immediate cross-version sanity check: decoded GR logic can be walked with the v3 table bases, while SQ2 still uses the existing v2 table bases.”

    Action/result: Updated tools/disassemble_logic.py to use tools/agi_resources.py for logic payload loading and to select v3 table bases for combined-directory games.

  2396. Reported: “The disassembler now has format-aware payload loading and table-base selection. I’m checking it on SQ2 first for backward compatibility, then on GR to confirm that compressed logic disassembles without parse errors.”

    Action/result: Disassembled SQ2 and GR logic 0 and ran tools/disassemble_logic.py --game-dir games/GR --stats successfully.

  2397. Reported: “The logic disassembler can now walk GR’s compressed logic resources. A decoded census found no parse errors across the present GR logic scripts; interestingly, although the v3 dispatcher supports action slots through 0xb5 and condition slots through 0x25, this Gold Rush data set only uses action opcodes through 0xa9 and conditions through 0x0e.”

    Action/result: Confirmed the GR logic opcode-use range from the stats output and kept the extra v3 slots marked as not yet behavior-specified.

  2398. Reported: “I’m updating the docs now: resource format chapter, executable notes, symbolic labels, progress dashboard, and progress log. I’ll keep the GR sections explicitly marked as v3/Gold Rush observations rather than rewriting the SQ2-centered spec as if every version behaves identically.”

    Action/result: Added a Gold Rush / AGI v3 resource-container comparison section and a clean-room evidence note.

  2399. Reported: “The evidence notes now capture the key commands, offsets, and the full-suite caveat. I’m adding the GR address associations to the symbolic label map next so future v3/v2 comparisons can refer to stable routine names rather than raw offsets.”

    Action/result: Added initial GR / AGI v3 symbolic label associations for dispatch tables, resource loaders, decompression routines, and directory globals.

  2400. Reported: “The symbolic map now has a GR/v3 association section for the resource loader, decompression routines, dispatch tables, and moved globals. I’m updating PROGRESS.md so the dashboard reflects that cross-version work has actually started rather than being purely future work.”

    Action/result: Updated PROGRESS.md with Gold Rush v3 resource coverage, v3 opcode-table caveats, and next highest-value work.

  2401. Reported: “PROGRESS.md now points at the next GR/v3 work directly, and it records the current full-suite blocker as a separate fixture permission issue. I’m appending the progress-log entries for this pass before running mdBook.”

    Action/result: Appended the progress-log entries for the Gold Rush v3 resource/decompression pass.

  2402. Reported: “The documentation trail is updated. I’m running the book build and focused tests now; after that I’ll check the diff for accidental noise, especially because build/ is disposable and run.sh was already untracked.”

    Action/result: mdbook build docs, tests/test_agi_resources.py, and focused SQ2 logic/sound tests all passed.

  2403. Reported: “The book builds cleanly and the focused tests pass. The only untracked file unrelated to me is still run.sh; the new untracked files are the resource module and its tests. I’m doing a final diff scan for accuracy before I wrap up.”

    Action/result: Reviewed git diff --stat, selected diffs, and a GR parser summary command.

  2404. Reported: “The diff scan looks coherent. One small thing I noticed from the stat: untracked files don’t show in git diff --stat, so I’m checking the new resource module and tests directly before the final status.”

    Action/result: Read tools/agi_resources.py, tests/test_agi_resources.py, and the final git status --short.

  2405. Reported: “I spotted two housekeeping bits before closing: make split-directory detection case-insensitive like the file opener, and log the final verification updates too. Then I’ll rerun the focused checks.”

    Action/result: Made split-directory detection case-insensitive in tools/agi_resources.py and recorded the final verification updates in this progress log.

  2406. Reported: “Everything I changed still verifies: mdBook passes, the new GR resource tests pass, and the existing SQ2 logic/sound focused tests pass. I’m checking final status now.”

    Action/result: Final status showed modified docs/tooling plus new tools/agi_resources.py and tests/test_agi_resources.py; pre-existing untracked run.sh remains unrelated.

  2407. Reported: “I’ll wire this in at two levels: first the documentation chapter and summary entry, then the fixture builders/tests that currently mutate copied read-only game files. I’ll start by finding the fixture code paths that touch games/ or preserve read-only permissions.”

    Action/result: Searched docs, tests, and tools for fixture builders, LOGDIR/VOL.3 patching, game-directory references, and copy/write operations.

  2408. Reported: “I found the likely permission caveat called out in PROGRESS.md, and the fixture tooling is centered in tools/qemu_fixture.py plus the probe wrappers. I’m reading those paths now so the fix lands in the shared primitive instead of scattering chmods through tests.”

    Action/result: Read tools/qemu_fixture.py, related tests, and wrapper probe code that uses copy_sq2_tree().

  2409. Reported: “The shared fixture builder is SQ2-v2-specific right now and writes patched LOGDIR/PICDIR/VOL.3 into whatever destination it’s given. The change I’m aiming for is to make the fixture destination a fresh, writable copy under build/, leaving games/* as immutable inputs.”

    Action/result: Identified copy_sq2_tree() and copy_minimal_picture_tree() as the shared copy points that should make generated copies writable.

  2410. Reported: “I’m also going to add a guard so fixture builders refuse destinations inside games/. That makes the intended contract executable: games/ is evidence input, build/ is where experimental copies and mutations happen.”

    Action/result: Planned a fixture-destination guard before patching copy logic.

  2411. Reported: “The docs already have a cross-version workflow chapter, so the new Versions chapter can be the dashboard: one row per observed interpreter/game version plus concrete deltas like resource container, compression, dispatch-table size, and fixture status. I’m adding that as a lightweight reference rather than burying it in the long evidence log.”

    Action/result: Read the existing mdBook summary, cross-version workflow, current status, and GR resource-container notes to shape the new chapter.

  2412. Reported: “I’ve got the edit shape now. I’m patching the fixture copy primitive to: clean generated destinations safely, copy source game files as writable files, preserve .ppm captures, and fail fast if someone points a fixture builder at games/.”

    Action/result: Updated tools/qemu_fixture.py with writable-copy helpers, generated-destination cleanup, .ppm preservation, and a guard rejecting destinations under games/.

  2413. Reported: “The core fixture copy path is patched. I’m adding focused tests for the two failure modes we care about: copied files from read-only game inputs must be writable, and generated fixtures must not be allowed inside games/.”

    Action/result: Added tests in tests/test_qemu_fixture.py for read-only source-file copying and games/ destination rejection.

  2414. Reported: “The fixture tests now cover the read-only-input bug directly. I’m running that slice before updating the docs so I can record the actual result rather than a hopeful one.”

    Action/result: Ran AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_qemu_fixture.py; all 39 tests passed.

  2415. Reported: “The new chapter is in the book index, and AGENTS.md now states the immutable-games/ rule explicitly. I’m updating the high-level tracker and evidence notes next, including the fact that the earlier fixture-copy permission caveat is now addressed in the shared builder.”

    Action/result: Added docs/src/versions.md, linked it from docs/src/SUMMARY.md, updated the docs README, and recorded the immutable input rule in AGENTS.md.

  2416. Reported: “The tracker still had the fixture permission problem listed as future work, so I’m changing that from a blocker into a completed infrastructure note. After that I’ll run the broader suite to see whether this uncovers anything else.”

    Action/result: Updated PROGRESS.md and docs/src/clean_room_executable_notes.md for the fixture-copy fix before broad verification.

  2417. Reported: “The code and docs are patched; now I’m verifying the book and the local suite. The important check is whether the full suite gets past the old read-only fixture-copy failure.”

    Action/result: Ran mdbook build docs and AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests. mdBook passed, and the full local suite passed 250 tests.

  2418. Reported: “Good result: mdBook passes, and the full local unit suite now passes 250 tests. I’m recording that in the clean-room notes and progress log so the previous caveat is closed rather than just fixed in code.”

    Action/result: Updated PROGRESS.md and docs/src/clean_room_executable_notes.md with the successful full-suite verification.

  2419. Reported: “One extra safety check fell out of the diff scan: the guard should also reject destinations that are parents of the selected game directory, because the cleanup step could otherwise delete the input. I’m adding that small hardening now.”

    Action/result: Hardened the fixture-destination guard and added a test that rejects parent directories of the selected game input.

  2420. Reported: “The guard is now a little harder to misuse, and I added a test for that parent-directory case. I’m rerunning the focused fixture tests and the full suite once more because this is exactly the kind of safety code where “probably fine” is too breezy.“

    Action/result: Reran AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_qemu_fixture.py and AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests; the focused fixture slice passed 40 tests and the full local suite passed 251 tests.

  2421. Reported: “I’ve got the static comparison pass well underway: the shared action/condition tables are mapped, the GR-only action slots are identified, and the resource/view/picture/object runtime slices have an initial normalized comparison. I’m going to tighten the docs and tracker now, then run the local checks so the evidence trail is solid rather than just ‘interesting notes in build/’.”

    Action/result: Resumed the GR/SQ2 static-comparison pass, reviewed the generated report, and prepared the tracker/doc update plan.

  2422. Reported: “The tracker still reflects the comparison as ‘in progress’ even though the generated report now has concrete classifications. I’m going to make the tracker reflect the evidence we actually have, and I’ll also correct the v3 condition-table boundary so the tooling does not overinterpret bytes that are really string/data territory.”

    Action/result: Re-read PROGRESS.md, the existing clean-room notes, progress log, symbolic labels, comparison helper, version ledger, and generated static report.

  2423. Reported: “The static report’s key caveat is important: GR’s predicate dispatcher bound is wider, but the bytes after the 19 shared condition entries decode as punctuation and filenames, not handler records. I’m going to encode that distinction in both code and docs so future comparisons don’t quietly inherit a false table size.”

    Action/result: Located the v3 condition-table size in tools/disassemble_logic.py and the older documentation claims that needed correction.

  2424. Reported: “There are a few older docs that phrase the GR condition range too strongly. I’m reading those exact spots now and will change only the GR-specific claims, leaving the SQ2 opcode evidence alone unless the local source actually requires it.”

    Action/result: Checked logic_bytecode.md, logic_opcode_evidence.md, resource_files.md, versions.md, and symbolic_labels.md; SQ2 wording already treated 0x13..0x25 as reserved, while GR-specific text needed correction.

  2425. Reported: “The current subsystem comparison is useful, but I want it a bit more complete before I call it solid. I’m going to extend the report to cover the full picture command table and more of the object/update-list surface, then I’ll regenerate it and fold the results back into the docs.”

    Action/result: Printed SQ2 and GR picture command dispatch tables, mapped additional view, picture, display, object, and motion routine addresses, and prepared to expand tools/compare_gr_sq2_static.py.

  2426. Reported: “The expanded scan is already paying off: GR’s core object/update-list routines sit at the same early-executable locations shifted by a few bytes, while object rectangle save/restore/draw calls target main-image routines instead of SQ2’s object overlay entry table. I am using that as a packaging difference, not a behavioral claim.”

    Action/result: Confirmed GR update-list routine addresses around 0x0351, 0x0428, 0x0457, and 0x0481, and noted GR main-image rectangle save/draw calls.

  2427. Reported: “I’m patching the comparison helper now. The main additions are a fuller picture-command sweep, the view group-table helper, object update-list and frame/motion routines, and a compact interpretation note for the shared handlers that actually differ.”

    Action/result: Extended tools/compare_gr_sq2_static.py with broader subsystem pairs and static notes for changed shared actions and GR-only actions.

  2428. Reported: “The helper now compares more of the interpreter surface, including all eleven picture commands and the core object update-list/motion routines. I’m making the parser boundary correction next, then I’ll regenerate the report to see which of those broader slices actually diverge.”

    Action/result: Changed the v3 condition-table count in tools/disassemble_logic.py from the dispatcher bound to the observed structured table size 0x13.

  2429. Reported: “That condition-table fix is deliberately conservative: it preserves the observed GR action table size, but treats only the 19 structured predicate entries as decodable table records. Now I’m regenerating the report and checking whether the expanded subsystem sweep exposes any new static differences.”

    Action/result: Regenerated build/gr-sq2-static/opcode_static_report.md and py-compiled the updated comparison and logic-disassembly tools.

  2430. Reported: “The expanded report found two kinds of differences: expected display-path divergences, and a couple of object/motion mismatches that may be either real or address/normalization artifacts. I’m checking those by reading the raw disassembly before I document them, because a table that confidently compares the wrong entry point is worse than no table.”

    Action/result: Read raw SQ2 and GR disassembly around frame timers, frame advancement, motion dispatch, and boundary checking.

  2431. Reported: “Those raw reads separate the real deltas from the straight-line disassembler noise: GR adds an object-frame group-count/version branch, and its motion dispatcher accepts one more mode value. The frame-advance and boundary-check ‘differences’ in the generated table are embedded jump-table bytes, so I’ll label them as manual-neutral rather than behavioral differences.”

    Action/result: Classified GR frame-timer and motion-dispatch differences as real static deltas, while treating frame-advance and boundary-check straight-line differences as embedded jump-table artifacts.

  2432. Reported: “I’m adding those manual caveats directly into the generated report notes, so the artifact says ‘real object-mode extension here’ and ‘jump-table bytes here’ without making someone rediscover that distinction later.”

    Action/result: Added subsystem-pair notes for GR display refresh omissions, object frame-timer branching, extra motion selector, and embedded jump-table caveats.

  2433. Reported: “The comparison helper is patched with the manual caveats; I’m regenerating the report one more time so the build artifact matches the code and the docs I’m about to write.”

    Action/result: Regenerated build/gr-sq2-static/opcode_static_report.md from the updated helper.

  2434. Reported: “I’m updating PROGRESS.md now: the new section is moving from ‘todo list’ to an evidence dashboard, including the 17 changed shared action snippets, the six GR-only action slots, and the object/view/picture static deltas.”

    Action/result: Updated the GR/SQ2 static comparison tracker and cross-version remaining-work sections in PROGRESS.md.

  2435. Reported: “I’m updating the clean-room notes with a dated static-comparison section now. This will record the commands, the generated artifact path, and the corrected condition-table interpretation so the clean-room trail doesn’t depend on memory.”

    Action/result: Added a new clean-room executable-notes section for the GR/SQ2 static opcode, object, view, and picture comparison, and corrected the earlier v3 condition-slot wording.

  2436. Reported: “The evidence notes now have a concrete comparison section. I’m sweeping the version/resource/symbol tables next so older wording doesn’t still imply that GR has real predicate handlers through 0x25.”

    Action/result: Updated docs/src/versions.md and docs/src/resource_files.md with the corrected GR condition-table boundary and static-comparison summary.

  2437. Reported: “The version ledger is corrected. I’m making the same adjustment in the resource chapter and then I’ll extend the GR address map with the object/view/picture routine associations from the static pass.”

    Action/result: Completed the resource chapter correction and began extending the GR address-association table.

  2438. Reported: “I’m extending the symbolic map with the GR addresses for the routines the report now compares. The notes include the SQ2 anchor offsets so later interpreter comparisons can keep using the same labels rather than chasing raw addresses.”

    Action/result: Added GR address associations for compared view, picture, display, object, and motion routines in docs/src/symbolic_labels.md.

  2439. Reported: “The symbolic map now has GR associations for the compared view, picture, object, display, and motion routines. I’m scanning for stale ‘condition through 0x25’ wording and then I’ll update the progress log before running checks.”

    Action/result: Scanned for stale GR condition-range claims and found only historical progress-log wording plus older SQ2-specific notes.

  2440. Reported: “I found one stale progress-log entry from the earlier GR pass that records the old condition-slot hypothesis; I’ll add today’s correction and the comparison updates after the current tail rather than rewriting history. After that, I’ll run the documentation and unit checks.”

    Action/result: Added progress-log entries for the static comparison pass and left the older progress-log hypothesis intact as historical context.

  2441. Reported: “The docs and tracker are patched. I’m running the local verification now: mdBook for documentation integrity, the unit suite for parser/disassembler regressions, and then I’ll clean up generated Python cache files so the worktree only shows intentional files plus the pre-existing run.sh.”

    Action/result: Ran mdbook build docs, AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests, and a stale-wording scan. mdBook passed, and the unit suite continued running.

  2442. Reported: “mdBook passed. The stale wording scan now only finds a historical progress-log entry from the earlier hypothesis, which I’m leaving as history because the new entries and evidence notes record the correction. The unit suite is still running, so I’m waiting for that before the final cleanup.”

    Action/result: Waited for the full unit suite, which passed 251 tests.

  2443. Reported: “The full local unit suite passed 251 tests. I’m running the project compatibility-suite wrapper as a final belt-and-suspenders check because it also covers the book/opcode-evidence checks that aren’t obvious from plain unittest output.”

    Action/result: Ran python3 -B tools/compatibility_suite.py; it failed because no explicit game directory was supplied, causing import-time AGI_GAME_DIR guard errors.

  2444. Reported: “The wrapper failed without AGI_GAME_DIR, which is the project’s explicit-game-directory guard doing its job. I’m rerunning the same compatibility-suite command with AGI_GAME_DIR=games/SQ2, matching the successful unit-suite invocation.”

    Action/result: Reran AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py; it passed 251 tests and mdBook.

  2445. Reported: “The compatibility wrapper passes once the explicit SQ2 input is provided: 251 tests and mdBook both green. I’m cleaning the generated tools/__pycache__ now and then I’ll do the final diff/status sanity pass.”

    Action/result: Removed generated tools/__pycache__.

  2446. Reported: “I’m recording the verification results in the evidence notes and progress log now, including the one failed wrapper attempt without AGI_GAME_DIR and the successful rerun with the explicit game directory.”

    Action/result: Added verification commands and outcomes to the clean-room executable notes.

  2447. Reported: “I’ll take this from the GR-only slots first: pin down each handler, then chase any state writes to their consumers so we can separate real behavior from reserved/no-op entries. I’ll keep this as a source-first pass and only reach for QEMU if the disassembly leaves a genuine ambiguity.”

    Action/result: Re-read the GR/SQ2 static comparison state and began a focused source pass over GR-only action slots 0xb0..0xb5.

  2448. Reported: “I’m drilling into the two non-trivial slots now. One writes a word near the low runtime globals, and the other pairs with GR’s changed 0xad; the useful bit is finding who reads those bytes afterward.”

    Action/result: Located GR action handlers 0xb1 at image 0x970b and 0xb5 at image 0x63b0, then searched local disassembly for consumers of [0x0403] and [0x0405].

  2449. Reported: “I’ve got the first solid shape: four of the GR-only action slots are deliberately inert after argument consumption, while the two real ones wire into existing menu/input state. I’m going back through the surrounding routines now so the names don’t outrun the evidence.”

    Action/result: Confirmed handler 0x5286 is a generic no-op/return handler and inspected the surrounding GR menu and keyboard routines.

  2450. Reported: “A useful anchor popped out: GR’s regular menu-building opcodes still make the same linked menu structures, and opcode 0xb1 is a separate gate that lets the menu interaction routine run at all. That makes it a control switch, not another menu item/heading builder.”

    Action/result: Read GR menu-build handlers around 0x9457..0x970a and menu interaction routine 0x9724; identified [0x0403] as an interaction gate checked before menu drawing/input.

  2451. Reported: “The paired input gate is clearer now too: GR’s 0xad sets a byte that the keyboard interrupt hook checks before enqueueing a (type=2, value=0) event on certain key-release paths; 0xb5 clears that same byte. That’s the strongest ‘extra opcode’ behavioral finding so far, because the consumer is right there in the disassembly.”

    Action/result: Traced [0x0405] from GR action 0xad and GR-only action 0xb5 to the keyboard interrupt hook at 0x63b8.

  2452. Reported: “I’m going to patch the tooling and docs now: add conservative v3 names for the extra slots, tighten the static report notes, and record the source evidence with the exact offsets. I’m leaving the SQ2 opcode catalog semantics intact because those bytes are still invalid in SQ2; the GR names live as v3-specific labels.”

    Action/result: Began updating disassembly tooling, static report notes, and documentation for the GR-only action slots.

  2453. Reported: “The disassembler now keeps SQ2’s ACTION_NAMES exactly as the tests expect, but when it sees a v3-sized table it can call out 0xb1 as the menu gate and 0xb5 as the key-release-event gate clearer. Small change, but it’ll save future archaeology.”

    Action/result: Added v3-specific action-name overrides in tools/disassemble_logic.py.

  2454. Reported: “PROGRESS.md now treats the extra slots as source-backed rather than just ‘interesting.’ I’m adding the same evidence into the mdBook pages next, with the distinction that these are Gold Rush/v3 observations, not SQ2 rules.”

    Action/result: Updated the GR/SQ2 static comparison tracker in PROGRESS.md.

  2455. Reported: “The logic-bytecode chapter now has a dedicated GR/v3 extra-slot table. I’m adding stable symbolic labels too, so later notes can say data.menu.interaction_gate_0403 instead of scattering raw offsets everywhere.”

    Action/result: Updated docs/src/versions.md and docs/src/logic_bytecode.md with source-backed GR extra opcode details.

  2456. Reported: “The symbolic map now has the new GR action labels plus the two state globals. I’m writing the clean-room evidence trail now, including the exact disassembly ranges and the decision not to run QEMU yet because this particular pass can be proven from local source paths.”

    Action/result: Added GR action/global labels to docs/src/symbolic_labels.md.

  2457. Reported: “The clean-room notes have the core evidence now. I’m appending the user-facing progress entries before I regenerate the report and run checks, so the historical trail stays aligned with what you saw during the work.”

    Action/result: Added this progress-log block for the GR extra opcode pass.

  2458. Reported: “The new log entries are ASCII-clean now. I’m regenerating the GR/SQ2 static report so the build artifact carries the refined 0xb1/0xb5 notes, then I’ll run the focused parser/docs checks.”

    Action/result: Regenerated build/gr-sq2-static/opcode_static_report.md.

  2459. Reported: “The report regenerated cleanly. I’m doing a quick readback of the generated extra-slot section and a GR disassembler stats run next, mostly to catch naming/report drift before the heavier checks.”

    Action/result: Confirmed the generated report includes the refined [0x0403]/[0x0405] notes; GR logic stats parsed with no errors.

  2460. Reported: “The focused checks are clean so far: the report contains the refined notes, GR logic still parses with no errors, and both touched Python tools compile. I’m running the book and unit suite now; those are the real regression guards for this repo.”

    Action/result: Ran mdbook build docs and AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests; both passed, with 251 unit tests.

  2461. Reported: “Book build and the 251-test unit suite both passed. I’m running the compatibility wrapper with explicit SQ2 input now, plus a whitespace diff check; after that I’ll clean any generated cache files and record the verification in the notes.”

    Action/result: Ran AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py and git diff --check; both passed.

  2462. Reported: “The compatibility wrapper passed too, and git diff --check is clean. A Python cache directory appeared from the runs; I’m removing just that generated cache and then I’ll do one final status/diff pass.”

    Action/result: Removed generated tools/__pycache__.

  2463. Reported: “The cache cleanup is done. I’m adding the verification results to the evidence notes and progress log now, then I’ll check the final diff so the close-out is concrete.”

    Action/result: Added verification results to docs/src/clean_room_executable_notes.md.

  2464. Reported: “The final diff matches the shape of the work: docs/tracker updates, v3 naming in the disassembler, and the comparison helper notes. I’m recording the successful validation commands now.”

    Action/result: Inspected git status --short, git diff --stat, and selected diffs for the extra opcode pass.

  2465. Reported: “The clean-room notes now include the validation results. I’m adding the matching progress-log tail entries and then I’ll do the last status check.”

    Action/result: Added the final verification progress-log entries.

  2466. Reported: “I’ve got the thread back in my head. I’m continuing on the v3 fixture writer path now: first removing the remaining hidden selected-game coupling in the fixture tool, then adding a direct-record v3 resource patch path we can use for Gold Rush probes.”

    Action/result: Resumed from the top PROGRESS.md item and inspected the existing v2 fixture builder, v3 resource parser, and fixture tests.

  2467. Reported: “The fixture code has a clean insertion point: the old v2 record writer and directory patcher can stay as-is, while v3 gets its own combined-directory patcher and direct-record writer. I’m going to keep the old function names for existing callers, but route source-game selection through an explicit parameter or AGI_GAME_DIR.”

    Action/result: Confirmed tools/qemu_fixture.py still imported a global SQ2 path from tools/disassemble_logic.py and identified the copy helpers as the place to parameterize source selection.

  2468. Reported: “I’m editing the fixture tool now. The useful shape is: copy_game_tree(..., game_dir=...) for any evidence input, old v2 helpers unchanged for current SQ2 harnesses, and a new v3 helper that appends a direct uncompressed record to an existing prefixed volume and patches the combined directory.”

    Action/result: Removed the qemu-fixture module’s import-time dependency on SQ2, added explicit source-game selection, and added v3 direct record / combined-directory patch helpers.

  2469. Reported: “The synthetic test game for v3 is tiny, but it exercises the same directory shape Gold Rush uses: one GRDIR, prefixed GRVOL.1, section offsets in the first eight bytes, and exact ff ff ff absent entries. That gives us a local test without touching private game files.”

    Action/result: Added fixture tests for v3 direct-record wrapping, combined-section entry patching, and logic-record replacement in a synthetic Gold Rush-style resource container.

  2470. Reported: “The first pass of the code is in place. I’m running the focused tests now; if the direct-record assumption or any import cleanup is wrong, this should catch it before I write docs around it.”

    Action/result: Ran python3 -B -m py_compile tools/qemu_fixture.py tools/agi_resources.py, which passed, and a focused unittest run, which found a missing end_action import in the new test.

  2471. Reported: “The focused suite found only a test import miss (end_action), not a resource-format issue. I’m fixing that and rerunning.”

    Action/result: Imported end_action in tests/test_qemu_fixture.py and reran the focused fixture/resource tests successfully.

  2472. Reported: “The focused fixture/resource tests are green. I’m doing a quick source scan next for stale SQ2 assumptions in the fixture path, then I’ll update PROGRESS.md and the docs with exactly what changed and what remains provisional.”

    Action/result: Scanned the fixture code and docs for stale selected-game assumptions; remaining fixture-module SQ2 references are compatibility names or v2-specific fixture paths rather than import-time defaults.

  2473. Reported: “The scan shows the fixture module itself no longer imports SQ2, and the remaining SQ2 references there are compatibility-oriented function names or v2-specific fixture builders. I’m updating the tracker now so the next work item becomes targeted v3 behavior probes, not ‘build the v3 writer.’”

    Action/result: Updated PROGRESS.md to mark the basic v3 direct logic fixture writer as available and to make targeted GR behavior probes the top remaining item.

  2474. Reported: “One more small improvement before the docs: I’m adding a CLI entry for the v3 logic fixture writer, so it’s not just a Python helper. It will still require an explicit selected game via --game-dir or AGI_GAME_DIR; no default game path.”

    Action/result: Added tools/qemu_fixture.py v3-logic with payload, logic-number, optional volume, explicit game directory, and output arguments.

  2475. Reported: “The CLI is in place too. I’m moving the documentation from ‘not v3-aware yet’ to the more precise state: basic v3 direct logic fixtures exist; picture/view v3 fixture packing and the behavioral probes are still next.”

    Action/result: Updated docs/src/versions.md and AGENTS.md with the v3 direct logic fixture command and current limitations.

  2476. Reported: “The evidence notes now record the commands and the decision boundary for v3 fixtures. I’m appending the matching progress-log entries, then I’ll run the full local checks.”

    Action/result: Added this v3 fixture writer progress-log block.

  2477. Reported: “The tracker and docs are caught up. I’m running the regression set now: full unit suite with explicit SQ2 input, mdBook build, compatibility wrapper, and whitespace checks.”

    Action/result: Ran AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests (254 tests), mdbook build docs, AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py, and git diff --check; all passed.

  2478. Reported: “The full unit suite is now 254 tests and passed; mdBook and the compatibility wrapper passed too, and git diff --check is clean. I’m doing a final compile/status pass after the CLI addition so the close-out is precise.”

    Action/result: Ran python3 -B -m py_compile tools/qemu_fixture.py tools/agi_resources.py successfully and inspected git status --short plus git diff --stat.

  2479. Reported: “The final status picked up a generated tools/__pycache__ from the test runs. I’m removing only that generated cache, then I’ll record the verification results in the log.”

    Action/result: Removed generated tools/__pycache__ and recorded the final verification result in docs/src/clean_room_executable_notes.md.

  2480. Reported: “The v3 fixture writer is done and verified, so I’m rolling straight into the first targeted delta: GR’s switch_room_like #0x7e..0x80 remap. I’ll start source-first by making the generated fixtures for #0x7e and #0x49, then use QEMU only if the existing harness/image is ready to compare their visible result.”

    Action/result: Inspected games/GR, QEMU snapshot helpers, and the current FreeDOS build state.

  2481. Reported: “There isn’t currently a build/freedos/freedos.img, so a QEMU confirmation would need the FreeDOS setup step before it can run. I’m still going to prepare the source-level fixture/test pieces first; if the image setup is quick and local, I’ll use it for confirmation.”

    Action/result: Confirmed the build directory lacked the FreeDOS image and read tools/setup_freedos_image.py / README setup notes.

  2482. Reported: “For the room-remap probe, the cleanest observable check is pairwise: build one fixture whose first logic switches to 0x49, and another whose first logic switches to 0x7e. If GR really remaps 0x7e to 0x49, those two generated copies should converge to the same room behavior under the original engine.”

    Action/result: Disassembled GR logic 73 and logic 0, confirming that room 0x49 has real picture/setup behavior and that original logic 0 dispatches the current room later in its script.

  2483. Reported: “The FreeDOS builder needs to recreate build/freedos/freedos.img before any QEMU probe can run. I’m trying the normal setup first; if the sandbox blocks the download, I’ll request the narrow approval for that command.”

    Action/result: The sandboxed FreeDOS setup failed with DNS blocked while downloading the official FreeDOS LiteUSB zip.

  2484. Reported: “The sandbox blocked DNS for the FreeDOS download, as expected. I’m asking for approval to run the same setup command outside the sandbox so it can fetch the official FreeDOS image and build the local QEMU disk.”

    Action/result: Reran python3 -B tools/setup_freedos_image.py --force with approval; it downloaded and extracted FD14LITE.img to build/freedos/freedos.img.

  2485. Reported: “The FreeDOS setup is running with approval now. While that finishes, I’m setting up the room-remap helper shape so the dynamic probe can be repeatable instead of a one-off manual run.”

    Action/result: Added tools/gr_v3_behavior_probe.py with room-remap fixture generation and optional QEMU comparison.

  2486. Reported: “I’m going to make the v3 patcher a little more reusable before writing the probe: build_v3_logic_fixture will remain the copy-then-patch entry point, and a new lower-level helper will patch additional logic resources in an already-copied v3 fixture. That lets the room-remap test replace both logic 0 and the destination room logic.”

    Action/result: Added patch_v3_logic_resource() and tests proving an already-generated v3 fixture can be patched again.

  2487. Reported: “I’m adding a small GR-specific behavior probe tool now. It will build two copied v3 fixtures: one switches to room 0x49, the other to 0x7e; both replace logic 0x49 with the same deterministic picture-display logic so the QEMU oracle can compare the two captures directly.”

    Action/result: Added structural tests for the GR v3 room-remap probe fixture builder.

  2488. Reported: “The room-remap probe tool and local structural tests are added. I’m checking them now, and I’ll see whether the FreeDOS image build has finished so I can run the original-engine comparison.”

    Action/result: python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py and focused room-remap/qemu-fixture tests passed.

  2489. Reported: “Good, the FreeDOS image rebuilt successfully, and the new probe tests pass. I’m running the room-remap probe in build-only mode first to inspect the generated fixtures, then I’ll run the QEMU capture comparison.”

    Action/result: Verified the FreeDOS root with mdir; the first build-only probe run exposed an argument-consumption issue.

  2490. Reported: “I found the real issue: importing qemu_snapshot computes its default DOS image through project_paths, and that path currently asks for a selected game too early. I’m adjusting the probe so build-only mode does not import QEMU snapshot helpers until --run-qemu is actually requested.”

    Action/result: Narrowed the failure further to project-wide --game-dir consumption during imports below qemu_fixture.

  2491. Reported: “More precisely, the project-wide --game-dir hook is being consumed during import by older resource helpers below qemu_fixture, before this probe’s own parser sees it. I’ll make the probe accept the project convention by reading that selected game through project_paths.game_dir() when argparse no longer has --game-dir.”

    Action/result: Reworked the new probe to lazy-import qemu_fixture and QEMU helpers so its own --help and parser work before game selection is consumed.

  2492. Reported: “I’ve made the probe imports lazy so --help and argument parsing work normally. Now I’m rerunning the build-only fixture generation and reading the JSON report back.”

    Action/result: gr_v3_behavior_probe.py --help and the build-only room-remap report succeeded.

  2493. Reported: “Build-only generation works now. The two fixtures are ready; I’m checking the report and then launching the QEMU comparison from the rebuilt FreeDOS image.”

    Action/result: Read back generated fixtures; logic 0 differed only in switch_room_like(#0x49) vs switch_room_like(#0x7e), and logic 0x49 matched in both.

  2494. Reported: “The fixture readback is exactly what we want: logic 0 differs only by switch_room_like #0x49 versus #0x7e, and logic 0x49 in both fixtures is the same deterministic picture-display script. I’m launching the QEMU comparison now.”

    Action/result: The sandbox blocked the local VNC bind for QEMU when run from the Python probe.

  2495. Reported: “QEMU built the snapshot disk but the sandbox blocked the VNC socket bind from inside the Python probe. I’m rerunning the same probe outside the sandbox with a narrow approval for this probe command.”

    Action/result: The approved QEMU run completed and wrote build/gr-v3-behavior/room_remap_qemu_001.json.

  2496. Reported: “The QEMU room-remap probe completed successfully. I’m reading the report and inspecting the captures so I can document exactly what was validated.”

    Action/result: Found both captures byte-identical but all black, so the run was not accepted as behavioral evidence.

  2497. Reported: “The first QEMU pair matched, but both captures are just a blank black screen, so that is not strong evidence yet. I’m treating that as a harness/probe issue, not a behavioral conclusion, and I’ll adjust the marker logic so the destination room visibly draws a known resource.”

    Action/result: Checked that GR pictures 1 and 73 are present and nonblank via the v3 resource reader and PictureRenderer.

  2498. Reported: “The old render_picture.py path is still split-directory-only, so it can’t render GR directly. I’ll use the v3 resource reader plus the existing PictureRenderer class directly to inspect marker picture choices.”

    Action/result: Rendered GR picture payloads directly from agi_resources; picture 1 has 14 visual colors and picture 73 has 8.

  2499. Reported: “The real GR direct records use the same shape we write: header byte equals the volume for direct records, with equal expanded/stored lengths. I’m checking the selected marker picture next; it may simply be blank, which would make the equality test technically true but useless.”

    Action/result: Confirmed the generated direct-record header matches real GR direct logic/sound records.

  2500. Reported: “The marker pictures are not blank locally, so the black QEMU result means the script probably is not reaching/drawing the marker. I’m rerunning with picture 1 and a longer wait to separate ‘bad picture choice’ from ‘room-switch fixture flow.’”

    Action/result: A longer QEMU room-remap run with picture 1 still produced equal all-black captures.

  2501. Reported: “The adjusted QEMU run is still in progress. If it still comes back black, I’ll switch from ‘room switch then destination logic draws’ to a probe that records the remapped room number through a more direct engine-visible side effect.”

    Action/result: The adjusted run completed successfully but still did not produce useful visual evidence.

  2502. Reported: “The picture-1 run completed. I’m inspecting whether that produced a real screen or just the same black false-positive.”

    Action/result: inspect_ppm again reported one unique color and no non-background bounding box.

  2503. Reported: “The longer picture-1 run is still black, so the current remap probe is not yet valid. I’m adding a one-case direct-draw mode to the same tool: patch logic 0 to show a picture immediately. That will tell us whether v3 direct logic replacement executes at all under GR in QEMU.”

    Action/result: Added --probe direct-draw mode to tools/gr_v3_behavior_probe.py.

  2504. Reported: “Direct-draw mode is added. I’m compiling it and running one QEMU capture with picture 1; this is a sanity check for whether our patched v3 logic 0 executes and draws at all.”

    Action/result: Direct-draw pycompile and tests passed; the QEMU capture ran successfully.

  2505. Reported: “The direct-draw QEMU sanity run completed. I’m inspecting the capture; a nonblank image means the v3 fixture writer is executing correctly and the room-remap blankness is specifically about the switch flow.”

    Action/result: Direct draw produced a nonblank 640x400 capture with 14 unique colors.

  2506. Reported: “The direct-draw capture is nonblank, so v3 direct logic replacement works. The black room-remap captures make sense now: logic 0 was switching rooms every cycle, likely preventing the destination room logic from ever settling. I’m changing the switch-room fixture to guard the switch so it fires once, then lets the destination logic run.”

    Action/result: Added a guard variable to the switch-room payload and reran the room-remap probe; captures remained black.

  2507. Reported: “Even guarded, the switch pair stays black, while direct-draw works. That points back to the room-switch helper resetting or short-circuiting more state than the simple guard assumed. I’m going back to the GR disassembly for the room switch path before trying another fixture shape.”

    Action/result: Re-read GR logic 0 and found the original global dispatcher calls call_logic_var(v0) near bytecode offset 0x0ca0.

  2508. Reported: “The GR global logic dispatches the current room near the end with call_logic_var(v0). Since replacing logic 0 removes that dispatch, a room switch alone will never call our patched destination room logic. I’m adjusting the probe: after the one-shot switch, logic 0 should explicitly call call_logic_var(v0) each cycle, matching the original dispatcher shape.”

    Action/result: Updated switch_room_payload() to append call_logic_var(v0) before end.

  2509. Reported: “The switch probe now preserves the essential dispatcher tail: after the guarded switch, logic 0 calls call_logic_var(v0). I’m rerunning the local test and then the QEMU pair.”

    Action/result: Local tests and build-only generation passed.

  2510. Reported: “The dispatcher-tail build is ready and the tests pass. Now I’m running the QEMU comparison again; this one should finally exercise the remap through the original room-switch code and then visibly call the destination room script.”

    Action/result: The dispatcher-tail QEMU run completed successfully and wrote room_remap_dispatch_qemu_pic001_001.json.

  2511. Reported: “The updated QEMU probe completed. I’m inspecting the screen stats now; if both captures match and are nonblank, this becomes our first dynamic v3 behavioral delta test.”

    Action/result: Both captures were byte-identical and nonblank: 14 unique colors, non-background bounding box (0, 0, 639, 399), and matching PPM SHA-256 45518c409f738a1fb2f4233db202f64d2e0e94011a9559e8ace0d952362814ab.

  2512. Reported: “That did it: the dispatcher-tail room-remap probe produced identical nonblank captures for direct 0x49 and alias 0x7e. I’m updating the tracker/docs now, including the false start, because the ‘black-screen equality’ was an important harness lesson.”

    Action/result: Updated PROGRESS.md, docs/src/versions.md, AGENTS.md, docs/src/symbolic_labels.md, and docs/src/clean_room_executable_notes.md with the v3 room-remap probe result.

  2513. Reported: “The high-level docs now show the v3 room-remap result. I’m also tagging the symbolic label entry so future cross-version work sees that the 0x7e case is no longer just source-backed; it has a QEMU evidence artifact.”

    Action/result: Updated docs/src/symbolic_labels.md and added the promoted probe note to docs/src/compatibility_testing.md.

  2514. Reported: “The v3 room-remap docs and tracker are updated. I’m running the full local validation set again, including the new probe tests, then I’ll clean generated caches and report the exact state.”

    Action/result: Ran AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests (257 tests), mdbook build docs, AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py, python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py, and git diff --check; all passed.

  2515. Reported: “Validation is clean: 257 tests, mdBook, compatibility wrapper, pycompile, and diff whitespace all passed. I’m removing generated Python cache directories and doing the final status/diff readback.”

    Action/result: Found and removed generated tools/__pycache__; status still has the pre-existing untracked run.sh plus the new probe/test files and edited docs/tools.

  2516. Reported: “Only tools/__pycache__ is generated; run.sh is still the pre-existing untracked file, so I’m leaving it alone. I’m removing the cache and then I’ll add the final verification note to the docs.”

    Action/result: Added the final verification summary to docs/src/clean_room_executable_notes.md.

  2517. Reported: “I’ll keep moving from the tracker’s top item: extend the GR v3 room-remap probe from the validated 0x7e -> 0x49 case to the remaining aliases 0x7f and 0x80, then promote that into the docs/tests if QEMU gives us nonblank matches.”

    Action/result: Re-read PROGRESS.md, tools/gr_v3_behavior_probe.py, and tests/test_gr_v3_behavior_probe.py.

  2518. Reported: “The current probe shape is already right; it just has a one-alias case list. I’m expanding that case list to cover 0x7e, 0x7f, and 0x80 in one QEMU batch, with the report recording per-alias equality against the direct 0x49 capture.”

    Action/result: Updated tools/gr_v3_behavior_probe.py to generate direct 0x49 plus aliases 0x7e, 0x7f, and 0x80, and to report matches_direct per alias.

  2519. Reported: “The code now builds direct 0x49 plus all three alias fixtures. I’m updating the tests to assert the whole alias set and the patched payloads, then I’ll run the probe.”

    Action/result: Updated tests/test_gr_v3_behavior_probe.py to assert fixture labels and switch payloads for all three aliases.

  2520. Reported: “The local expectations now cover alias_7e, alias_7f, and alias_80. I’m running the focused tests and a build-only report before launching QEMU.”

    Action/result: Focused pycompile/tests passed and build/gr-v3-behavior/room_remap_all_build_001.json was generated.

  2521. Reported: “The focused tests and build-only report passed. I’m reading the report and payloads once, then running the four-case QEMU batch.”

    Action/result: Verified generated logic payloads differ only by target bytes 0x49, 0x7e, 0x7f, and 0x80.

  2522. Reported: “The generated fixture payloads are exactly differentiated by the room byte. I’m launching the QEMU run now; it should produce four captures and compare every alias back to the direct 0x49 case.”

    Action/result: Started the approved QEMU run for room_remap_all_qemu_pic001_001.json.

  2523. Reported: “The expanded QEMU run is in progress. This one has four fixtures, so it takes a little longer than the previous two-case run.”

    Action/result: The QEMU run completed and wrote build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json.

  2524. Reported: “The four-case QEMU run completed. I’m inspecting the per-alias match flags and screen stats now before promoting the result.”

    Action/result: The report shows alias_7e, alias_7f, and alias_80 all match direct 0x49; all four captures are nonblank with 14 unique colors and identical PPM SHA-256 45518c409f738a1fb2f4233db202f64d2e0e94011a9559e8ace0d952362814ab.

  2525. Reported: “All three aliases matched the direct room target with nonblank captures. I’m promoting the probe from ‘one alias validated’ to ‘full 0x7e..0x80 range validated’ across the tracker and docs, and I’ll update the probe label so future reports say what they actually cover.”

    Action/result: Updated the probe report label and began updating PROGRESS.md, AGENTS.md, and mdBook chapters to reference the all-alias evidence.

  2526. Reported: “The tracker now treats the whole room-alias range as covered. I’m updating the command references and evidence text next.”

    Action/result: Updated AGENTS.md, docs/src/versions.md, docs/src/compatibility_testing.md, docs/src/symbolic_labels.md, and docs/src/clean_room_executable_notes.md for the all-alias room-remap evidence.

  2527. Reported: “I’ve got the thread state back. I’m at the validation pass for the expanded Gold Rush v3 room-remap probe, then I’ll update the evidence log with the exact checks and give you the compact outcome.”

    Action/result: Ran the validation pass after the expanded room-remap probe and documentation updates.

  2528. Reported: “The core validation pass is clean: 257 unit tests, mdBook, pycompile, and whitespace checks all passed. I’m running the compatibility suite now because it catches the cross-doc opcode/evidence consistency that the plain test run doesn’t cover.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests, mdbook build docs, python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py, and git diff --check all passed.

  2529. Reported: “Compatibility suite passed as well. A generated tools/__pycache__ appeared from the runs, so I’m cleaning only that generated directory and then I’ll record the validation result in the project log.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py passed, and the generated tools/__pycache__ directory was removed.

  2530. Reported: “The detailed evidence note already contains the final verification block, so I only need to add the matching progress-log entries. After that I’ll rerun the lightweight checks touched by the log edit.”

    Action/result: Added the validation and cleanup entries to this log.

  2531. Reported: “The expanded room-remap pass is wrapped and validated. I’m going back to PROGRESS.md now and taking the next highest-value item, staying source-first and using QEMU only if there’s a concrete behavior worth confirming.”

    Action/result: Re-read PROGRESS.md, the GR/SQ2 static comparison helper, and relevant key/menu/save/object references.

  2532. Reported: “The next item is the GR v3 key/menu event delta: 0x79, 0xad, 0xb1, and 0xb5. I’m going to pin down the disassembled routines first, because here the useful outcome is a portable description of the event tables and gates rather than just a visual probe.”

    Action/result: Located the existing GR v3 key/menu source-map notes and symbolic labels for code.input.map_key_event, code.menu.interact, and the key-release gate globals.

  2533. Reported: “The docs already contain a solid source map for the key/menu gates; what’s missing is a focused behavioral fixture. I’m reading the current v3 probe builder and the existing SQ2 key/menu probes so I can extend the same harness without inventing a separate path.”

    Action/result: Read tools/gr_v3_behavior_probe.py, tools/logic_interpreter_probe.py, tools/qemu_fixture.py, and the focused GR v3 probe tests.

  2534. Reported: “There’s a nice fit here: the existing SQ2 logic probe already has code snippets for mapped-key/status-byte tests. I’m extracting the minimal bytecode patterns from that rather than building a new comparison mechanism.”

    Action/result: Reused the existing 0x79 mapped-key/status-byte fixture shape as the model for the GR v3 capacity probe.

  2535. Reported: “For the key-map capacity probe, I can avoid any generated graphics: show one original GR picture when the mapped status byte fires, and compare that capture to a direct draw of the same picture. That keeps this about interpreter event behavior, not renderer fidelity.”

    Action/result: Chose original GR picture 1 as the marker image for both direct and keyed captures.

  2536. Reported: “Before I wire the fixture, I’m re-reading the GR disassembly around the four handler entry points so the code and docs are anchored to exact local evidence, not just the earlier summary text.”

    Action/result: Re-read GR disassembly around 0x4e98, 0x63a8, 0x63b0, 0x63b8, 0x970b, and 0x9724.

  2537. Reported: “The disassembly confirms the key-map capacity is a literal loop bound change to 0x31, and the gate pair is very small: 0xad writes one byte, 0xb5 clears it, and the IRQ hook checks that byte. I’m going to add a probe that exercises the expanded 49th mapping slot, since that is the most observable part of this cluster.”

    Action/result: Planned a slot-48 key-map capacity fixture while leaving raw release/menu gate timing as source-backed follow-up work.

  2538. Reported: “The key-map probe I’m adding fills 48 dummy mapping slots, then puts the real x -> status 7 mapping in the final GR slot. Under the source model this should work in GR v3 (0x31 slots) and would fail under the SQ2-sized 0x27 table.”

    Action/result: Started extending tools/gr_v3_behavior_probe.py and tests/test_gr_v3_behavior_probe.py.

  2539. Reported: “The first patch missed because the file had shifted from the context I targeted. I’m splitting the edit into smaller hunks so it stays readable and avoids touching unrelated sections.”

    Action/result: Re-opened tools/gr_v3_behavior_probe.py with line numbers and resumed with smaller patches.

  2540. Reported: “The file is compact, so the extension is straightforward. I’m adding constants and the payload/builder first, then I’ll add the CLI branch.”

    Action/result: Added key-map slot-count constants and post-launch key metadata to ProbeCase.

  2541. Reported: “Constants and case metadata are in place. Next I’m adding the actual bytecode payload: 48 filler 0x79 mappings, then the real x mapping in slot 48, followed by a per-cycle status check that displays an original GR picture.”

    Action/result: Added key_map_capacity_payload().

  2542. Reported: “The payload is now expressed as reusable bytecode, which makes it testable without booting QEMU. I’m adding the fixture builder and making the QEMU runner honor post-launch keystrokes.”

    Action/result: Added build_key_map_capacity_fixtures() and taught run_qemu_cases() to pass post-launch key fields into SnapshotFixtureCase.

  2543. Reported: “The runner can now type the probe key after launch. I’m wiring the --probe key-map-capacity branch so it produces the same style of JSON report as the room-remap probe.”

    Action/result: Added the key-map-capacity CLI branch and report fields.

  2544. Reported: “The CLI branch is in. I’m updating the focused tests to assert the generated v3 fixture really puts the live mapping in the last GR slot and patches only copied fixture data.”

    Action/result: Added tests for the key-map payload and copied fixture contents.

  2545. Reported: “The unit tests now cover the new payload and fixture builder. I’m running those focused checks and a build-only probe before trying QEMU.”

    Action/result: Focused GR v3 tests, pycompile, and build-only key_map_capacity_build_001.json generation passed.

  2546. Reported: “The focused tests and build-only run are clean. I’m inspecting the generated report and payload once, then I’ll launch the QEMU validation for the final-slot mapping.”

    Action/result: Confirmed the generated payload had 49 map actions, with the target x mapping after 48 prior 0x79 opcodes.

  2547. Reported: “The generated logic has exactly 49 0x79 actions and the target mapping is after 48 filler slots, which is the edge we wanted. I’m running the original GR interpreter now and comparing the final-slot key capture to a direct picture draw.”

    Action/result: Ran the first QEMU key-map capacity probe, producing build/gr-v3-behavior/key_map_capacity_qemu_pic001_001.json.

  2548. Reported: “The QEMU run completed successfully. I’m checking both the JSON match flag and the actual capture stats so we know this wasn’t an all-black false positive.”

    Action/result: The keyed slot-48 capture matched the direct capture; both had the same PPM SHA-256 as the known nonblank picture-1 capture.

  2549. Reported: “The match flag is true and the two PPM files have the same SHA-256 as the earlier nonblank picture-1 capture. I tried to pass both files to inspect_ppm.py at once, but that helper accepts one image at a time, so I’m rerunning it separately.”

    Action/result: Re-ran inspect_ppm.py separately; both captures were 640x400, 14 unique colors, with full-screen non-background bounds.

  2550. Reported: “I’m going to tighten this one more notch: add a paired no-key fixture using the same slot-48 table. The positive case should match the direct picture only after x; the no-key case should not. That guards against a preexisting status byte or accidental initial draw.”

    Action/result: Added a slot_48_no_key fixture using the same payload without post-launch key input.

  2551. Reported: “The no-key fixture is added. I’m updating the report logic so this probe has explicit expected true and expected false comparisons instead of treating every case as ‘should match.’”

    Action/result: Added expected_matches_direct and passed fields for the key-map capacity report.

  2552. Reported: “The report now distinguishes positive and negative expectations. I’m adjusting the tests for the extra fixture and then rerunning the focused checks.”

    Action/result: Updated tests to verify the direct, keyed, and no-key fixtures.

  2553. Reported: “The tests now expect both the positive and no-key controls. I’m rerunning the focused suite and the QEMU validation with the three-case batch.”

    Action/result: Focused tests, pycompile, and build-only key_map_capacity_build_002.json passed.

  2554. Reported: “Focused tests and build-only generation are still clean. I’m launching the updated QEMU run; this one should show direct = keyed slot-48, and no-key different.”

    Action/result: Started the three-case QEMU key-map capacity batch.

  2555. Reported: “The three-case QEMU batch is still running. It boots once, saves the DOS prompt, then restores that snapshot for each fixture, so this should finish shortly.”

    Action/result: The QEMU batch completed and wrote build/gr-v3-behavior/key_map_capacity_qemu_pic001_002.json.

  2556. Reported: “The three-case QEMU run finished. I’m checking the expected true/false matches and the capture stats now.”

    Action/result: The report passed: slot_48_key_map matched direct, slot_48_no_key did not; direct/keyed were nonblank with 14 colors, and no-key was blank.

  2557. Reported: “The capacity probe passed: keyed slot 48 matches the direct picture, while the no-key control stays blank. I’m promoting this into the tracker/docs now, keeping the key-release/menu gates as source-backed unless we later need raw scan-code timing evidence.”

    Action/result: Began promoting the key-map capacity result to PROGRESS.md and the mdBook evidence.

  2558. Reported: “I’m updating the docs in the places that act as navigational anchors: PROGRESS.md, AGENTS.md, versions, logic_bytecode, compatibility_testing, symbolic labels, and the clean-room evidence note. The detailed opcode rows stay SQ2-specific; the GR behavior goes into the cross-version notes.”

    Action/result: Read the relevant documentation anchors for targeted updates.

  2559. Reported: “I found the existing v3 compatibility section, so I’ll extend that rather than creating a duplicate. The new evidence note will sit after the room-remap section because it uses the same fixture harness.”

    Action/result: Patched PROGRESS.md to record QEMU-validated GR key-map capacity and narrow the remaining v3 behavioral work list.

  2560. Reported: “PROGRESS.md now treats key-map capacity as QEMU-validated and leaves the raw release/menu gate timing as optional follow-up. I’m adding the runnable command to AGENTS.md next so future sessions can reproduce it.”

    Action/result: Added the key-map capacity QEMU command and pass criteria to AGENTS.md.

  2561. Reported: “The reproduction command is in AGENTS.md. I’m updating the mdBook version and bytecode notes so someone reading the spec sees both the source delta and the QEMU confirmation.”

    Action/result: Started updating docs/src/versions.md.

  2562. Reported: “The versions chapter wording was slightly different than expected, so I’m patching the exact nearby lines now.”

    Action/result: Updated the GR key/menu row and dynamic probe paragraph in docs/src/versions.md.

  2563. Reported: “The versions chapter now records both the source delta and the final-slot QEMU evidence. I’m making the same concise update in the logic bytecode cross-version note.”

    Action/result: Updated the GR cross-version key/menu row in docs/src/logic_bytecode.md.

  2564. Reported: “The bytecode chapter now points readers at the observable slot-48 behavior. Next I’m extending the compatibility testing chapter with the exact command and pass criteria.”

    Action/result: Added the key-map capacity command and expected positive/no-key control behavior to docs/src/compatibility_testing.md.

  2565. Reported: “Compatibility testing now has the reproducible command and acceptance criteria. I’m adding a short symbolic-label note on the GR code.input.map_key_event association so the evidence is discoverable from the address map too.”

    Action/result: Updated docs/src/symbolic_labels.md for GR code.input.map_key_event.

  2566. Reported: “The symbolic map now links 0x4e98 to the slot-48 QEMU evidence. I’m appending the detailed chronological evidence note with the disassembly anchors, fixture shape, and capture stats.”

    Action/result: Added the Gold Rush v3 key-map capacity behavior probe section to docs/src/clean_room_executable_notes.md.

  2567. Reported: “The evidence note has the full source/QEMU record. I’m appending the progress-log entries for this work block now; it’s a little long because it preserves the intermediate updates you asked to keep.”

    Action/result: Added progress-log entries for the GR v3 key-map capacity work block.

  2568. Reported: “The docs and log are updated. I’m running the full local validation pass now; after that I’ll record the verification result and clean any generated cache.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed with 259 tests, mdbook build docs passed, python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py passed, and git diff --check passed.

  2569. Reported: “The first validation group is clean: 259 tests, mdBook, pycompile, and diff hygiene passed. I’m running the compatibility suite as the final consistency check.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py passed.

  2570. Reported: “The compatibility suite passed too. A generated tools/__pycache__ came back from the run, so I’m removing just that generated cache and then I’ll add the final verification note for this key-map pass.”

    Action/result: Removed generated tools/__pycache__.

  2571. Reported: “The cache is cleaned. I’m adding the validation commands/results to the evidence note and progress log, then I’ll run the final mdBook and diff checks after those edits.”

    Action/result: Added the final verification block to docs/src/clean_room_executable_notes.md.

  2572. Reported: “The key-map evidence note now includes its verification block. I’m adding the matching progress-log entries for validation and cleanup.”

    Action/result: Added validation and cleanup entries to this log.

  2573. Reported: “The final mdBook and diff checks are clean. I’m continuing to the next PROGRESS item: the GR v3 save-envelope/object-inventory XOR delta. I’ll start by reading the existing save probe and the GR/SQ2 disassembly notes before deciding whether to add a QEMU save-file check.”

    Action/result: Read tools/save_roundtrip_probe.py, save-related docs, the GR/SQ2 source-pass notes, and tools/agi_save.py.

  2574. Reported: “The existing notes already identify the GR save handler and XOR helper, but this needs to become more implementation-ready. I’m checking whether we can make a targeted original-engine save-file probe for GR, and if that’s too much for this pass I’ll at least codify the XOR transform with source-backed tests.”

    Action/result: Checked local GR files, GR logic usage of save/restore opcodes, and existing SQ2 save-roundtrip harness assumptions.

  2575. Reported: “GR logic 0 itself calls 0x8f(#3), so the save prefix/signature likely comes from a local message rather than being hard-coded to SQ2. I’m reading that logic resource/message and the exact save handler bytes next, because a GR save probe needs the right signature setup to produce a parseable file.”

    Action/result: Read GR logic 0, exact-offset disassembly for save action 0x29e5, XOR helper 0x07bc, and restore action 0x2792.

  2576. Reported: “The first exact-offset read confirmed the save handler calls 0x07bc before entering the selector and later around the write path. One helper command failed because I passed a string where the resource reader expected a configured/Path game directory, so I’m rerunning the message extraction with Path(...).”

    Action/result: Re-ran the local reads with Path("games/GR"); exact save block disassembly showed the XORed range is written as the third block.

  2577. Reported: “GR’s XOR key is not an ASCII string; the helper resets DI to image offset 0x072c and uses those local bytes until the first zero byte, then wraps. That means the clean-room spec should describe it as observed key bytes, not as a named text key.”

    Action/result: Found the first zero byte at image 0x0767, making the observed repeating XOR sequence 59 bytes long.

  2578. Reported: “The GR save layout differs from SQ2’s fixed block sizes: the exact handler writes a third block from [0x07d6] with length [0x07da], and that is the range XORed before and after the save path. I’m going to capture that as a source-backed transform first; a full GR save UI probe can build on it once we know the right signature/prompt sequence.”

    Action/result: Chose to implement a source-backed helper/test model before attempting full GR save-file extraction.

  2579. Reported: “For this save pass I’m adding a small version-specific transform helper instead of forcing the whole GR save UI immediately. The source-backed contract is the important bit: block 2 is XORed on disk with the observed 59-byte sequence and the same pass restores memory afterward.”

    Action/result: Added GR_V3_OBJECT_INVENTORY_XOR_KEY, xor_with_repeating_key(...), and gr_v3_object_inventory_save_xor(...) to tools/agi_save.py.

  2580. Reported: “The save helper now has the GR v3 object/inventory XOR transform as a named function with the observed key bytes. I’m adding focused tests for round-trip behavior and the 59-byte wrap point.”

    Action/result: Started updating tests/test_save_resources.py.

  2581. Reported: “The save tests have a slightly different import block than I expected. I’m opening the file and patching against its actual shape.”

    Action/result: Re-opened tests/test_save_resources.py and patched the imports/test methods against its current layout.

  2582. Reported: “I’ve got the exact test file layout now. I’m adding the imports and the three transform tests at the end of the existing save-resource test class.”

    Action/result: Added tests for GR transform round-trip, 59-byte key wrap, and empty-key rejection.

  2583. Reported: “The transform tests are in place. I’m running the focused save tests and pycompile before documenting this source-backed model.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_save_resources.py passed with 10 tests, and python3 -B -m py_compile tools/agi_save.py passed.

  2584. Reported: “Focused save tests pass. I’m documenting this as a source-backed GR save model: the transform is now executable in the local helpers, while a future full GR save UI probe can check the on-disk block bytes directly.”

    Action/result: Updated PROGRESS.md to treat the save XOR model as source-backed and tested, with full GR save extraction left optional.

  2585. Reported: “PROGRESS.md now treats the save XOR as source-modeled with tests, not as an unstarted item. I’m adding the concrete key/range details to the version and opcode docs.”

    Action/result: Updated docs/src/versions.md.

  2586. Reported: “The versions chapter now includes the 59-byte save transform detail. I’m making the bytecode cross-version note match.”

    Action/result: Updated docs/src/logic_bytecode.md.

  2587. Reported: “The bytecode note is aligned. I’m adding a short runtime-model note so the portable spec distinguishes SQ2’s plain save blocks from GR’s transformed object/inventory block.”

    Action/result: Updated docs/src/runtime_model.md.

  2588. Reported: “The runtime model now has the GR v3 save-block note. I’m checking the surrounding markdown so I didn’t accidentally split a table in an awkward place.”

    Action/result: Verified the new runtime-model paragraph sits after the save/restore selector table and before the lifecycle table.

  2589. Reported: “The note sits cleanly between the selector table and lifecycle table. I’m adding symbolic labels for the GR save block start/length and key bytes so future comparisons can track if another v3 interpreter moves them.”

    Action/result: Updated docs/src/symbolic_labels.md with GR save block and XOR key data labels.

  2590. Reported: “The symbolic map now records the GR save block pointer, length, and key region. I’m adding a chronological evidence section for the save XOR model, including the exact disassembly calls and the new local tests.”

    Action/result: Added the Gold Rush v3 save object/inventory XOR model section to docs/src/clean_room_executable_notes.md.

  2591. Reported: “The detailed evidence note now captures the save transform. I’m adding a concise compatibility-testing note so the helper tests show up in the suite narrative.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2592. Reported: “The compatibility chapter now includes the GR v3 save transform tests. I’m appending progress-log entries for this save-XOR block, then I’ll run the full validation again with the new helper/tests/docs.”

    Action/result: Added progress-log entries for the GR v3 save XOR work block.

  2593. Reported: “The save-XOR work block is logged. I’m running the full validation pass again now that both code and docs changed.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed with 262 tests, mdbook build docs passed, python3 -B -m py_compile tools/agi_save.py tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py passed, and git diff --check passed.

  2594. Reported: “The main validation group is clean again: 262 tests, mdBook, pycompile, and diff hygiene passed. I’m running the compatibility suite as the final check for this save-XOR update.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py passed.

  2595. Reported: “The compatibility suite passed. I’m removing the generated cache again and then recording the save-XOR verification result in the evidence note/log.”

    Action/result: Removed generated tools/__pycache__.

  2596. Reported: “The cache is gone. I’m adding the final verification line for the save-XOR pass, then I’ll run the two lightweight doc hygiene checks.”

    Action/result: Added the final verification block to docs/src/clean_room_executable_notes.md.

  2597. Reported: “The evidence note has the final verification block. I’m adding the corresponding progress-log entries now.”

    Action/result: Added validation and cleanup entries to this log.

  2598. Reported: “The final doc checks are clean. I’m moving on to the next remaining v3 delta, restart prompt-marker preservation, and I’ll stay with the same source-first pattern.”

    Action/result: Read PROGRESS.md, restart/prompt-marker references, and exact GR disassembly for 0x26e0, 0x3b00, 0x3ab0, and 0x3ad9.

  2599. Reported: “The GR restart source slice has an important nuance: canceled restart preserves hidden prompt-marker state, but accepted restart still calls the show-marker helper before returning zero into the reset flow. I’m going to correct that wording and add a tiny tested truth table so the spec is precise.”

    Action/result: Searched for stale GR restart wording and planned a tested truth-table helper.

  2600. Reported: “The search found one stale sentence saying GR redraws only when restart was canceled and the marker had been visible; the disassembly shows accepted OR previously-visible. I’m adding a tiny agi_restart helper and tests to pin that truth table down.”

    Action/result: Added tools/agi_restart.py with gr_v3_restart_redraws_prompt_marker(...).

  2601. Reported: “The restart helper is in place. I’m adding a focused test file for the four prompt-marker redraw cases.”

    Action/result: Added tests/test_restart_model.py.

  2602. Reported: “The restart truth table has a local test now. I’m running the focused restart test and pycompile before I patch the docs.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_restart_model.py passed, and python3 -B -m py_compile tools/agi_restart.py passed.

  2603. Reported: “Focused restart checks pass. I’m correcting the stale wording now: the GR v3 branch redraws after accepted restart, and after canceled restart only if the marker had been visible beforehand.”

    Action/result: Updated tools/compare_gr_sq2_static.py.

  2604. Reported: “The static comparison note is corrected. I’m updating the tracker and version docs to reflect the tested truth table.”

    Action/result: Updated PROGRESS.md.

  2605. Reported: “PROGRESS.md now narrows the remaining v3 behavioral item to object motion mode 4, with restart QEMU left optional. I’m patching the mdBook wording in the cross-version sections.”

    Action/result: Updated docs/src/versions.md.

  2606. Reported: “The versions table now has the precise accepted/canceled restart behavior. I’m updating the logic bytecode note and runtime model next.”

    Action/result: Updated docs/src/logic_bytecode.md.

  2607. Reported: “The bytecode cross-version note is updated. I’m adding a concise GR v3 note in the restart lifecycle section of the runtime model.”

    Action/result: Opened the restart lifecycle section and patched docs/src/runtime_model.md after adjusting for the current table layout.

  2608. Reported: “The restart lifecycle section has drifted slightly from the context I targeted, so I’m opening that block and patching the actual text.”

    Action/result: Inspected docs/src/runtime_model.md around the restart lifecycle table.

  2609. Reported: “The lifecycle table doesn’t have a separate fatal row label I expected. I’m inserting the GR restart note right after the table, before the picture decoder section.”

    Action/result: Added the GR v3 restart prompt-marker truth-table note to docs/src/runtime_model.md.

  2610. Reported: “The runtime model now has the GR truth table note. I’m correcting the stale clean-room note and symbolic label text, then I’ll add a compact evidence section for the restart model.”

    Action/result: Corrected stale GR restart wording in docs/src/clean_room_executable_notes.md and updated docs/src/symbolic_labels.md.

  2611. Reported: “The stale wording is fixed. I’m adding the restart truth-table tests to the compatibility narrative and then a short chronological evidence section.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2612. Reported: “Compatibility testing now mentions the restart truth table. I’m appending the chronological source-model note for the GR restart branch.”

    Action/result: Added the Gold Rush v3 restart prompt-marker truth-table section to docs/src/clean_room_executable_notes.md.

  2613. Reported: “The restart evidence note is in place. I’m logging this restart-model block, then I’ll run validation again with the new helper/test/docs.”

    Action/result: Added progress-log entries for the GR v3 restart prompt-marker work block.

  2614. Reported: “The restart-model block is logged. I’m running the full validation pass with the new helper and docs.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed with 263 tests, mdbook build docs passed, python3 -B -m py_compile tools/agi_restart.py tools/agi_save.py tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py tools/compare_gr_sq2_static.py passed, and git diff --check passed.

  2615. Reported: “The first restart validation group is clean: 263 tests, mdBook, pycompile, and diff hygiene passed. I’m running the compatibility suite one more time.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py passed.

  2616. Reported: “The compatibility suite passed too. I’m cleaning the generated cache, adding the restart verification note, and then I’ll do the final lightweight checks.”

    Action/result: Removed generated tools/__pycache__.

  2617. Reported: “The cache is cleaned. I’m recording the restart validation results now.”

    Action/result: Added the final verification block to docs/src/clean_room_executable_notes.md.

  2618. Reported: “The restart evidence note has its verification block. I’m adding the matching progress-log validation entries.”

    Action/result: Added restart validation and cleanup entries to this log.

  2619. Reported: “I’ll use PROGRESS.md as the steering file, then pick the highest-value unfinished item that can be advanced from the local disassembly and existing tools. First I’m refreshing the dashboard and nearby docs so I don’t drift from the current evidence trail.”

    Action/result: Re-read PROGRESS.md, AGENTS.md, current git status, and v3-related docs. The dashboard pointed to GR object motion mode 4 and the frame-selection gate as the top remaining v3 behavior items.

  2620. Reported: “The dashboard points at one main unfinished v3 behavior item: Gold Rush object motion mode 4, plus the related frame-selection gate. I’m going source-first now: inspect the existing comparison helper outputs, disassembly labels, and v3 probe scaffolding before deciding what fixture to add.”

    Action/result: Re-read tools/gr_v3_behavior_probe.py, tools/qemu_fixture.py, the static GR/SQ2 comparison helper, and build/gr-sq2-static artifacts.

  2621. Reported: “I found the existing docs already treat mode 4 as source-backed but not behavior-validated. There’s one minor shell wart in my search because Markdown backticks were interpreted inside a quoted pattern; the useful matches are intact, and I’ll avoid that quoting shape for the next searches.”

    Action/result: Confirmed current docs mention GR mode 4 and avoided that shell quoting pattern in later searches.

  2622. Reported: “The v3 fixture builder can already patch direct logic into a copied GR directory, which is enough for a behavior probe if the original game’s existing picture/view resources are suitable. Next I’m reading the exact GR motion dispatch and frame-timer snippets around the documented addresses.”

    Action/result: Read GR/SQ2 ndisasm around code.motion.dispatch_mode_step and code.object.frame_timer_update.

  2623. Reported: “The disassembly confirms the narrow delta: SQ2 dispatch decrements mode and accepts resulting values 0..2 (modes 1..3), while GR accepts 0..3 and the fourth jump-table slot lands on the same target-direction handler as mode 3. I’m looking at the existing movement harness now so the v3 probe reuses the proven object setup pattern instead of inventing a new one.”

    Action/result: Re-read tools/object_movement_probe.py for the existing persistent-object movement setup and comparison patterns.

  2624. Reported: “There’s an important source question before writing a probe: normal logic opcodes set motion modes 1, 2, and 3, but I don’t yet have a script-visible setter for +0x22 = 4. I’m searching the GR image for the write sites that create mode 4; that will determine whether the fixture should be pure logic, saved-state based, or keyboard/input driven.”

    Action/result: Found GR mode-4 write-like code at image 0x1975, plus mode-4 special cases at 0x18cf, room switch cleanup, and action 0x84.

  2625. Reported: “The mode-4 setter is not a logic opcode write. GR writes +0x22 = 4 inside the input/motion area around image 0x198e, and several helpers special-case mode 4 afterward. I’m opening that neighborhood now to map what observable path leads there.”

    Action/result: Inspected GR image 0x1888..0x1a84, including target-direction helper 0x1888, completion helper 0x18cf, helper-shaped code at 0x1975, and room switch cleanup.

  2626. Reported: “The action table only routes room-switch opcodes 0x12/0x13 into that neighborhood; no action points at the mode-4 helper. If a bytecode opcode routes to it we can write a clean pure-logic probe; if not, the route is likely through input/click handling.”

    Action/result: Parsed the local GR action table; only 0x12 and 0x13 point near 0x19d4/0x19eb, not at 0x1975.

  2627. Reported: “Rizin needed the seek form for xrefs here, and it also treats this GR executable as a flat image, which matches the ndisasm evidence. I’m correcting that and continuing with the local disassembly as the primary source.”

    Action/result: Tried rizin xrefs, then relied on direct ndisasm and a byte-level call scan because rizin did not produce useful xrefs for this flat 16-bit image.

  2628. Reported: “The mode-4 setter at 0x1975 is a helper, not a direct call in the linear disassembly. I’m checking the action table next, because if a bytecode opcode routes to it we can write a clean pure-logic probe; if not, the route is likely through input/click handling.”

    Action/result: Confirmed again that no action table entry directly routes to 0x1975.

  2629. Reported: “The byte-level scan shows no direct near calls to the mode-4 target seeder. The address appears only as data-like words, so I’m inspecting those references to avoid inventing a path that the interpreter doesn’t actually use.”

    Action/result: Scanned direct near-call targets and little-endian word occurrences. No direct calls to image 0x1975 were found.

  2630. Reported: “The action table confirms mode 4 is generated outside normal logic, and the setter’s body uses screen coordinates plus the object’s dimensions to seed target bytes. That looks like mouse/joystick-click style ego movement: set object0 mode 4, set target X/Y, preserve current step, then let the ordinary target-direction machinery run.”

    Action/result: Recorded the hypothesis that 0x1975 is an input-driven first-object target seeder, while keeping the natural entry path unresolved.

  2631. Reported: “I’m not finding a script-visible or direct-call path into the mode-4 seeder, so I’m treating that as evidence rather than forcing it. Before patching anything, I’m checking the GR file set and resource scripts for any local inputs that might naturally exercise the mode-4 path.”

    Action/result: Listed games/GR and ran tools/disassemble_logic.py --game-dir games/GR --stats; local GR scripts use motion actions heavily but no direct mode-4 setter exists.

  2632. Reported: “GR scripts use the normal motion opcodes heavily, but no local script directly creates mode 4; that reinforces that it’s an engine/input state. I also confirmed my attempted --opcode option doesn’t exist on the local disassembler, so I’m staying with --stats and raw disassembly for this pass.”

    Action/result: Noted the disassembler CLI limitation and continued with source-first ndisasm reads.

  2633. Reported: “There is no opcode that writes arbitrary object bytes, so a pure bytecode fixture cannot set +0x22 = 4. The cleanest validation route I see is an explicitly instrumented copied interpreter: change the move_object_to setup byte in the build copy from mode 3 to mode 4, then compare its capture against the unmodified mode-3 path. That would test the GR dispatcher while clearly separating it from normal game-data compatibility evidence.”

    Action/result: Chose an instrumented copied-interpreter probe rather than presenting mode 4 as ordinary script-visible behavior.

  2634. Reported: “I’m going to build this as an ‘instrumented mode-4 dispatch probe,’ not as ordinary unmodified-engine compatibility. First I need a compact GR picture/view pair that renders cleanly with existing resources, so I’m scanning the local GR resource directory and renderer support.”

    Action/result: Used tools/agi_resources.py to list present GR resources and selected picture 1/view 0/group 0/frame 0 for the probe.

  2635. Reported: “The older graphics helper is SQ2-oriented for direct rendering, but the resource parser can read GR v3 records. For this probe I can avoid local rendering entirely: compare original-engine captures between unmodified mode-3 setup and the instrumented mode-4 setup, with a stationary control.”

    Action/result: Designed the three-case capture comparison: stationary, unmodified mode 3, and instrumented mode 4.

  2636. Reported: “View 0 is a good candidate: it has a compact 7-pixel-wide walking figure with multiple groups, and picture 1 is present. I’m going to implement the probe around picture 1/view 0, with three cases: stationary control, unmodified mode 3, and instrumented mode 4.”

    Action/result: Implemented the motion-mode payload and fixture builder in tools/gr_v3_behavior_probe.py.

  2637. Reported: “I’m going to keep the interpreter patch local to the v3 behavior probe rather than generalizing it into the fixture builder. It’s a deliberately instrumented test, so keeping that sharp edge near the report code makes the evidence trail clearer.”

    Action/result: Added a local helper that patches only generated GR fixture copies and checks the expected action-0x51 byte context.

  2638. Reported: “One small cleanup before tests: the script’s global --picture default was room-remap-specific. I’m making the per-probe default explicit so the new motion probe naturally uses picture 1 unless told otherwise.”

    Action/result: Changed --picture to use per-probe defaults.

  2639. Reported: “Now I’m adding focused unit coverage for the new probe: payload shape, fixture construction, and the safety check that only a copied GR interpreter with the expected byte sequence gets patched.”

    Action/result: Added focused tests in tests/test_gr_v3_behavior_probe.py.

  2640. Reported: “The focused test failed at import time because one of the legacy graphics modules still demands AGI_GAME_DIR during module import. I’ll rerun with an explicit local game path for now, and then check whether the test invocation should be made independent.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe passed with 8 tests.

  2641. Reported: “The focused tests pass with AGI_GAME_DIR=games/SQ2, matching the repo’s current legacy import pattern. I’m running the new GR motion-mode probe through QEMU now; it will produce a JSON report under build/gr-v3-behavior/.”

    Action/result: Started QEMU motion-mode probe build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.json.

  2642. Reported: “Good catch from the harness: the documented address is a loaded-image offset, while the real GR file has a 0x200-byte MZ header. I’m fixing the patch helper to translate image offsets to file offsets instead of assuming the two are identical.”

    Action/result: Corrected the patch helper to apply the MZ header size when patching copied GR executables.

  2643. Reported: “The patch helper now handles both flat test files and real MZ-wrapped GR executables. I’m rerunning the focused tests, then I’ll retry the QEMU probe.”

    Action/result: Focused tests passed again.

  2644. Reported: “The focused tests are green again. I’m rerunning the QEMU probe with the corrected image-to-file offset translation.”

    Action/result: Reran the GR motion-mode QEMU probe.

  2645. Reported: “QEMU is running the three-case snapshot batch now. I’ll wait for the report and then inspect the comparisons, especially whether the stationary control differs from the moving captures.”

    Action/result: QEMU finished and wrote build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.json.

  2646. Reported: “The QEMU run finished and wrote the report. I’m reading the JSON now to see whether the instrumented mode-4 capture matched mode 3 and whether the stationary control stayed distinct.”

    Action/result: The JSON report passed: mode4_instrumented_move_object_to_matches_mode3 was true and stationary_control_matches_mode3 was false.

  2647. Reported: “The report passed: instrumented mode 4 produced the same capture as unmodified mode 3, and the stationary control did not match. I’m updating the docs carefully so this is recorded as an instrumented dispatcher probe, not as an unmodified-game-data behavior claim.”

    Action/result: Began documentation updates in PROGRESS.md, compatibility docs, runtime/object docs, version notes, symbolic labels, and clean-room notes.

  2648. Reported: “The worktree has the expected GR mode-4 changes plus an old untracked run.sh; I’m leaving that alone. One tiny shell got bitten by Markdown backticks in the search pattern, so I’m switching to simpler file reads for the last doc pass.”

    Action/result: Confirmed the changed files and left the pre-existing untracked run.sh untouched.

  2649. Reported: “I found one remaining stale place in the logic bytecode chapter: it mentioned GR preserving mode 4, but not the new instrumented dispatcher validation. I’m adding that caveat there, then I’ll record the documentation checkpoint in the progress log.”

    Action/result: Added a logic-bytecode note explaining that mode 4 dispatcher behavior is source-backed and instrumented-QEMU-validated, but no ordinary bytecode setter has been observed.

  2650. Reported: “That chapter now makes the distinction explicit: source-level dispatcher behavior is validated, but the script-visible path for creating mode 4 remains unobserved. I’m appending the same checkpoint to the historical log before running checks.”

    Action/result: Updated the historical progress log for the GR mode-4 documentation pass.

  2651. Reported: “The notes are caught up enough to validate. I’m running the full local unit suite with an explicit AGI_GAME_DIR, building the mdBook, compiling the touched probe, and checking the diff for whitespace damage.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 267 tests; mdbook build docs, python3 -B -m py_compile tools/gr_v3_behavior_probe.py tests/test_gr_v3_behavior_probe.py, and git diff --check all passed.

  2652. Reported: “The validation set is green so far: 267 unit tests passed, mdBook built, the touched Python compiled, and git diff --check found nothing. I’m running the project compatibility wrapper too, since it includes the opcode-evidence checks the tracker cares about.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py passed, rerunning the 267-test suite and rebuilding the book.

  2653. Reported: “The compatibility wrapper also passed; it reran the 267-test local suite and rebuilt the book successfully. I’m doing a final status/diff review now so I can tell you exactly what changed and where the evidence landed.”

    Action/result: Final diff review found a duplicated remaining-work block in PROGRESS.md, which was removed.

  2654. Reported: “The tracker duplication is fixed. The only untracked generated directories are Python __pycache__ folders from the checks, so I’m going to remove those generated caches and then rerun the lightweight sanity checks affected by the last doc edit.”

    Action/result: Removed generated tools/__pycache__ and tests/__pycache__ directories from the workspace.

  2655. Reported: “Generated caches are gone. I’m adding the final validation notes to progress_log.md; since that chapter is part of the book, I’ll rebuild mdBook one last time afterward.”

    Action/result: Added final validation and cleanup entries to the historical progress log.

  2656. Reported: “I’ll continue from the next PROGRESS.md item: the GR v3 frame-selection gate for exactly-four-loop and more-than-four-loop views. I’ll start source-first by reading the tracker, existing static comparison notes, and the probe scaffolding before deciding whether a QEMU fixture is warranted.”

    Action/result: Began the GR frame-selection gate pass from PROGRESS.md.

  2657. Reported: “The tracker points squarely at code.object.frame_timer_update: SQ2 and GR are both labeled, and the previous comparison already calls out a real branch delta. I’m narrowing the disassembly to just that routine and the adjacent group-selection tables so I can turn the vague ‘gate’ wording into exact branch semantics.”

    Action/result: Read the tracker and symbolic-label context for SQ2 0x0563 and GR 0x055c.

  2658. Reported: “The first slice landed a bit late in the file, so I’m switching to address-anchored searches instead of line guesses. The labels are in place; now I need the exact instructions around 0x0563/0x055c and the table bytes they index.”

    Action/result: Switched to address-anchored ndisasm reads and found the exact SQ2/GR frame-timer branch ranges.

  2659. Reported: “Now the branch is crisp: GR inserts a flag test before using the four-plus-groups table, and it treats exactly 4 loops differently from SQ2. I’m reading the tail of the same routine to make sure this branch only affects automatic group selection and not frame-timer advancement.”

    Action/result: Confirmed the GR delta sits before frame-timer advancement and affects automatic direction group selection only.

  2660. Reported: “The GR fixture builder already copies the selected game into build/ before patching, so it’s a good fit. I’m checking the bytecode helpers now; if they can set flags, direction, and object group cleanly, this can be an unmodified-interpreter QEMU probe rather than another instrumented one.”

    Action/result: Confirmed the v3 fixture path can use copied GR resources and ordinary bytecode.

  2661. Reported: “The needed script operations are present: set flag, set/clear object bit 0x2000, set object direction, and one-shot persistent object setup. I’m checking existing opcode helpers for the exact bytes so the GR probe stays in the same style as the SQ2 movement probes.”

    Action/result: Reused the established object setup pattern from the SQ2 direction/group-selection probes.

  2662. Reported: “This can stay uninstrumented: we can use 0x2e to allow auto-selection, 0x56 to set direction byte +0x21, and flag action 0x0c for GR flag 20. I’m scanning GR’s view resources next to pick concrete four-loop and five-plus-loop views whose groups render differently enough for capture comparisons.”

    Action/result: Planned an ordinary-bytecode QEMU probe using GR flag 0x14, object direction 6, and copied fixtures.

  2663. Reported: “One local scan tripped over the older agi_graphics import requiring an explicit game directory. That’s harmless; I’m rerunning it with AGI_GAME_DIR=games/GR and also checking whether the existing SQ2 probe code has exactly the setup pattern we need to port.”

    Action/result: Re-ran the GR resource scan with an explicit game directory and reviewed the SQ2 probe setup pattern.

  2664. Reported: “Interesting: the local GR resource set itself has no views with four or more groups, so the branch is present in the interpreter but not directly exercised by stock GR view data. That means this is exactly the case where we need a generated v3 view payload in a copied fixture, rather than modifying games/GR.”

    Action/result: Initial scan used the wrong view header byte and was later corrected; no generated v3 view payload was ultimately needed.

  2665. Reported: “The old renderer helper assumes split VIEWDIR files, so it can’t load GR directly. I’m doing the frame comparison from the decoded v3 payloads instead, using the same row decoding rules already in agi_graphics.py.”

    Action/result: Compared candidate GR view frames from decoded v3 payloads without relying on split-directory helpers.

  2666. Reported: “The corrected GR census shows plenty of four-plus-loop views; my first scan had used the wrong header byte. Static reread also corrected the wording: exactly four groups bypasses the new flag gate and still uses the four-plus direction table; only more-than-four groups require flag 20. I’m choosing two views whose group-0 and group-1 frames differ enough for clean capture comparisons.”

    Action/result: Corrected the interpretation and selected local GR view 177 for the exact-four case and view 39 for the more-than-four case.

  2667. Reported: “For exact-four behavior, view 177 has clearly different group 0 and group 1 frames. For more-than-four, view 33 is a nice contrast: group 0 is tiny and group 1 is visibly larger. I’m checking actual visible-pixel counts so the controls won’t be ambiguous.”

    Action/result: Checked visible-pixel counts; kept view 177 for exact-four and chose view 39 for a clearer more-than-four group-0/group-1 contrast.

  2668. Reported: “I have enough to implement the probe. I’m adding a frame-selection-gate mode to the GR v3 behavior tool: it will build copied GR fixtures for group-0/group-1 controls, exact-four flag-clear/flag-set cases, and more-than-four flag-clear/flag-set cases, then compare QEMU captures against the controls.”

    Action/result: Added frame-selection payload and fixture builders to tools/gr_v3_behavior_probe.py.

  2669. Reported: “The fixture builders are in place. I’m wiring that into the CLI report now, including explicit expected comparisons so the JSON tells us exactly which branch behavior was proven.”

    Action/result: Added --probe frame-selection-gate CLI handling and report comparisons.

  2670. Reported: “The CLI now knows the expected exact-four versus more-than-four outcomes. I’m adding focused unit coverage so the generated bytecode and fixture labels don’t drift.”

    Action/result: Added tests for frame-selection payloads, controls, and fixture construction.

  2671. Reported: “The focused tests now cover the new payloads and fixture set. I’m running them before trying QEMU, because any bytecode-shape mistake is much cheaper to catch here than inside DOS.”

    Action/result: Focused tests and Python compilation passed.

  2672. Reported: “Focused tests passed. I’m running the new GR frame-selection batch through QEMU now: eight copied fixtures, one bootable snapshot, and capture comparisons against group-0/group-1 controls.”

    Action/result: Started the QEMU batch that wrote build/gr-v3-behavior/frame_selection_gate_qemu_001.json.

  2673. Reported: “QEMU is still running the snapshot batch; I’ll let it finish and then inspect the JSON comparisons before changing the docs.”

    Action/result: Waited for the eight-case QEMU run.

  2674. Reported: “Still waiting on the eight-case QEMU run. This one is slower than the small motion-mode batch, but it’s doing exactly the useful thing: one original-engine pass per copied fixture, then byte-for-byte capture comparisons.”

    Action/result: Continued waiting for QEMU to finish.

  2675. Reported: “The QEMU batch completed and wrote the report. I’m reading the comparisons now; if the exact-four correction is right, those cases should match group 1 regardless of flag 20, while the more-than-four cases should depend on flag 20.”

    Action/result: Parsed frame_selection_gate_qemu_001.json; all expected comparisons passed.

  2676. Reported: “The report passed and it corrected the previous wording nicely: exact-four views auto-select through the four-plus table even when flag 20 is clear; more-than-four views require flag 20. I’m updating PROGRESS.md and the spec docs to reflect that source-first correction plus the QEMU evidence.”

    Action/result: Began documentation updates for the validated GR frame-selection gate.

  2677. Reported: “One more shell search got nicked by backticks in a Markdown pattern, but it still exposed the stale spots. I’m correcting the exact-four wording across the dashboard and subsystem docs now.”

    Action/result: Located stale wording in PROGRESS.md, version notes, symbolic labels, runtime model, logic bytecode, object pipeline, and compatibility docs.

  2678. Reported: “I’m patching the docs in the same terms as the disassembly and QEMU report: SQ2 says ‘4 or more uses the table’; GR says ‘exactly 4 still uses it, greater than 4 uses it only when flag 20 is set.’”

    Action/result: Updated PROGRESS.md to mark the frame-selection gate complete and revise highest-value remaining work.

  2679. Reported: “PROGRESS.md is updated: the frame-selection gate has moved out of remaining work, with the exact-four correction captured. I’m carrying the same correction into the implementation-facing chapters and evidence sections.”

    Action/result: Updated docs/src/versions.md and docs/src/symbolic_labels.md.

  2680. Reported: “The version matrix and label table now cite the exact QEMU report. I’m updating the object pipeline and opcode chapter next, since those are the places an implementer is most likely to read for runtime semantics.”

    Action/result: Began object-pipeline and logic-bytecode updates.

  2681. Reported: “The object-pipeline patch missed because of a small wording/line-wrap mismatch. I’m reopening that paragraph and patching around the actual text.”

    Action/result: Reopened the object-pipeline paragraph for a targeted patch.

  2682. Reported: “I’ve got the exact paragraph now; the update will sit directly after the shared SQ2 behavior and add the GR-specific branch as a version delta.”

    Action/result: Updated docs/src/graphics_object_pipeline.md.

  2683. Reported: “The object pipeline now states the GR branch precisely. I’m mirroring that in the logic bytecode notes near the 0x2d/0x2e group-selection explanation.”

    Action/result: Updated docs/src/logic_bytecode.md.

  2684. Reported: “The logic chapter has the version-specific rule and QEMU evidence. I’m adding the reusable command/result to compatibility testing so the probe can be rerun later across interpreter versions.”

    Action/result: Added the frame-selection QEMU command and result to docs/src/compatibility_testing.md.

  2685. Reported: “The runtime model still had the old ‘pending’ frame-gate wording, so I’m replacing that with the validated rule and keeping the mode-4 caveat intact.”

    Action/result: Updated docs/src/runtime_model.md.

  2686. Reported: “The runtime model now has the validated version rule. I’m appending a clean-room evidence note with the disassembly offsets, local resource choice, QEMU command, and result so the reasoning trail is complete.”

    Action/result: Added the Gold Rush v3 frame-selection gate section to docs/src/clean_room_executable_notes.md.

  2687. Reported: “I’m adding the new frame-selection evidence immediately after the mode-4 note, since both are GR v3 object-runtime deltas from the same static comparison pass.”

    Action/result: Placed the new clean-room evidence after the mode-4 probe section.

  2688. Reported: “The clean-room notes now include the static branch walk, local view-resource choices, QEMU command, and conclusion. I’m catching the historical progress log up with the updates from this pass before final validation.”

    Action/result: Appended progress-log entries for the GR frame-selection pass.

  2689. Reported: “The progress log is caught up. Before validation, I’m doing a quick stale-wording scan without shell-sensitive backticks, then I’ll run the test/book checks.”

    Action/result: Stale-wording scan found one earlier clean-room note with the old exact-four wording.

  2690. Reported: “The stale scan only found old historical log entries plus one earlier clean-room note that may need context. I’m checking that clean-room paragraph; historical progress-log wording can remain because it records what I thought at the time.”

    Action/result: Reviewed the earlier clean-room comparison note around the GR frame-timer delta.

  2691. Reported: “That earlier clean-room note still had the incorrect exact-four conclusion. I’m correcting it in place and pointing forward to the later QEMU section that proves the correction.”

    Action/result: Corrected the earlier clean-room note: exactly four groups use the table without the new flag gate; more than four groups require flag 0x14.

  2692. Reported: “The stale clean-room note is fixed. I’m running the validation set now: full unit suite, mdBook, compatibility wrapper, Python compile, and whitespace check.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 270 tests; mdbook build docs, python3 -B -m py_compile tools/gr_v3_behavior_probe.py tests/test_gr_v3_behavior_probe.py, and git diff --check passed.

  2693. Reported: “The main validation is green: 270 tests passed, mdBook built, Python compile passed, and git diff --check is clean. I’m running the compatibility wrapper as the final project-level check.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py passed, rerunning the 270-test suite and rebuilding the book.

  2694. Reported: “The compatibility wrapper passed too. I’m adding those validation results to the progress log, then I’ll rebuild the book once more because the log itself changed.”

    Action/result: Added final validation results for the GR frame-selection pass to the progress log.

  2695. Reported: “I’ll pick up from the GR v3 work and re-anchor on PROGRESS.md so the next slice follows the tracker instead of wandering. I’ll keep this source-first and only use QEMU if there’s a behavior worth confirming.”

    Action/result: Read git status, PROGRESS.md, versions.md, and remaining-work references.

  2696. Reported: “The tracker now has the GR frame-selection and mode-4 items in the validated bucket. The next useful work is to turn one of the source-backed v3 deltas into a tighter behavioral artifact; I’m going after the GR save-file extraction because it affects a real persisted file format and is higher value than text/UI-only redraw quirks.”

    Action/result: Selected GR save extraction as the next highest-value PROGRESS item.

  2697. Reported: “I’ve got the target: GR’s save code is already source-modeled, but not yet checked against bytes emitted by the original interpreter. I’m reading the existing SQ2 save harness and v3 fixture writer so I can add the smallest GR-specific extraction path without touching games/.”

    Action/result: Reviewed tools/save_roundtrip_probe.py, tools/gr_v3_behavior_probe.py, tools/qemu_fixture.py, and tools/agi_save.py.

  2698. Reported: “The GR fixture can probably reuse the same interpreter save UI action sequence as SQ2, but with v3 resource patching and a GR signature message. Before coding that, I’m checking the local binary/logic evidence for the save-name convention and the block transform so the probe asserts something meaningful, not just ‘a file appeared.’”

    Action/result: Searched local docs, strings, and GR logic opcode usage for save, restore, and verifier evidence.

  2699. Reported: “GR’s own scripts use verify_game_signature once before save/restore, so the v3 path does share that filename/signature mechanism. I’m going to inspect that local logic resource and the nearby v3 save routine one more time, then wire a probe that extracts the produced save and checks the XORed third block against the decoded live runtime block captured from the copied fixture.”

    Action/result: Disassembled GR logic 0 and reread the source-backed save notes before implementation.

  2700. Reported: “The source notes confirm the third save block is the transformed one. One nuance: the existing local save parser assumes SQ2 fixed block lengths, but the GR proof only needs generic length-prefixed parsing plus a block-3 transform check, so I can keep this narrowly scoped.”

    Action/result: Decided to use the generic five-block parser and GR-specific third-block XOR checks rather than SQ2 fixed lengths.

  2701. Reported: “The disassembler prints opcode references but not decoded message text. I’m doing a local resource parse for GR logic 0’s message table so the synthetic fixture can use the same signature prefix the original game initializes.”

    Action/result: First local parse hit the explicit AGI_GAME_DIR guard.

  2702. Reported: “That parse hit the project’s ‘explicit game directory’ guard through an import side effect. I’ll rerun it with AGI_GAME_DIR set, keeping the same local-only evidence path.”

    Action/result: Reran the message-table parse with AGI_GAME_DIR=games/GR.

  2703. Reported: “The GR logic message bytes did not decode with the SQ2 message key, which is itself useful: I won’t assume the SQ2 message text transform for v3 logic messages. For the save probe I can still use our fixture writer’s known-encoded synthetic message and verify whether GR’s action 0x8f accepts it; if not, I’ll fall back to the blank-prefix save path and still inspect the block transform.”

    Action/result: Identified GR logic message handling as a version-specific fixture concern.

  2704. Reported: “I caught the message parse mistake: the offset table is plain and only the message text bytes are XORed. Re-parsing that should tell us the local GR signature string cleanly.”

    Action/result: Corrected the local message-table parser, then found GR resource message text is already readable without applying the SQ2 XOR.

  2705. Reported: “The message bytes still look encoded, so GR likely uses either a different message key phase or a different message transform than SQ2. That matters for any v3 fixture with messages, so I’m going source-first into the GR message decode routine rather than papering over it.”

    Action/result: Continued into GR message data/source inspection; later checks showed the observed local GR messages are plain in resource bytes.

  2706. Reported: “The v2 message key location lives in AGIDATA.OVL; GR has its own AGIDATA.OVL, so I’m checking that local data segment and the GR image labels next. If the key moved, fixing the fixture encoder will also help future v3 tests that need messages.”

    Action/result: Confirmed GR AGIDATA.OVL contains Avis Durgan at offset 0x072c, while observed GR logic messages are plain.

  2707. Reported: “GR logic messages are plain in the observed resource bytes. That gives me the missing piece for a v3 save fixture: I’ll extend the local fixture builder so it can emit plain-text message tables for v3 while keeping the existing encrypted v2 default intact.”

    Action/result: Added encrypt_messages to logic_resource(...), default True.

  2708. Reported: “Before I choose the signature text, I’m checking GR’s 0x8f verifier handler in the local image for the embedded comparison string. That avoids guessing whether the prefix should be GR, GOLD, or something else.”

    Action/result: Inspected GR image bytes/disassembly around 0x108c and 0x5ede; found embedded GR\0 bytes.

  2709. Reported: “The embedded GR signature string is present locally as GR\\0; I’m correcting for the MZ header when reading the handler bytes, then I’ll use GR as the fixture’s signature message and expected filename stem GRSG.”

    Action/result: Corrected image/file-offset handling and verified the 0x8f copy/compare helper shape.

  2710. Reported: “I’m making the fixture-format change first: one optional parameter on logic_resource controls message encryption, defaulting to the existing v2 behavior. The GR-specific code will opt into plain messages explicitly.”

    Action/result: Patched tools/qemu_fixture.py.

  2711. Reported: “I’m adding two tiny unit checks around message table encoding: existing encrypted behavior stays unchanged, and v3/plain mode leaves the text bytes readable in the resource.”

    Action/result: Added message-encryption tests to tests/test_qemu_fixture.py.

  2712. Reported: “I’m adding the GR save probe as a new --probe save-xor-extract mode rather than mixing it into the older SQ2 save tool. It still reuses the generic QEMU snapshot and save extraction helpers, but the fixture construction stays v3-specific.”

    Action/result: Added initial GR save extraction payload, fixture builder, QEMU runner, and CLI branch to tools/gr_v3_behavior_probe.py.

  2713. Reported: “Now I’m extending the focused GR probe tests so the new v3 save fixture is checked without requiring private game writes or QEMU. The QEMU run will be the behavioral confirmation after the fixture bytes look right.”

    Action/result: Added GR save payload/fixture tests.

  2714. Reported: “The code/tests are patched. I’m running the focused GR and fixture tests now, then I’ll try one real QEMU extraction if the local checks pass.”

    Action/result: Focused tests found one test-structure error; py_compile passed.

  2715. Reported: “The compile passed; one test failure is just my patch landing in the wrong spot inside an existing assertion block. I’m fixing the test structure now.”

    Action/result: Inspected and repaired the misplaced frame-selection assertion.

  2716. Reported: “The final frame-selection assertion was orphaned into the new save test. I’m moving it back under the frame-selection fixture test.”

    Action/result: Moved the assertion back to the correct test.

  2717. Reported: “The unit-test structure is fixed; I’m rerunning the focused suite before starting QEMU.”

    Action/result: Focused GR/fixture tests passed 59 tests; py_compile passed.

  2718. Reported: “Focused tests pass. I’m going to run one QEMU save extraction now; this should build a disposable copied GR fixture, drive the save UI, pull GRSG.1 back out of the qcow, and compare the third block through the source-modeled XOR helper.”

    Action/result: First QEMU attempt failed because GRSG.1 was not present.

  2719. Reported: “The QEMU run completed far enough to convert the disk, but GRSG.1 was not present. I’m inspecting the disposable DOS directory now to see whether the engine wrote a blank-prefix SG.1, didn’t save, or saved under another stem.”

    Action/result: mdir found no save file in GRS0; capture had only two colors.

  2720. Reported: “There’s no save file at all, and the capture is a two-color text/error screen, so the fixture likely exits before reaching the save UI. I’m converting the capture so I can see which guard failed.”

    Action/result: Converted the PPM capture to PNG and inspected fixture logic bytes.

  2721. Reported: “The synthetic logic has the intended 8f 01 7d and a plain GR\\0 message table, so the verifier text itself looks right. I’m going to view the capture to identify the failure path before changing the probe.”

    Action/result: Viewed the capture; it showed the typed description at DOS after the game exited before saving.

  2722. Reported: “The capture shows the game exited before the save UI, and the later keystrokes landed at DOS. Since the fixture bytes look structurally valid, I’m comparing its tiny message table against an original GR table and the resolver’s expected layout; this smells like an offset/table convention mismatch, not the save action itself.”

    Action/result: Confirmed the generated message table shape and read the GR resolver/copy helper disassembly.

  2723. Reported: “Since the verifier path is still exiting, I’m checking how the original GR scripts call 0x8f: which logic, which message number, and what raw message text sits there. That will tell us whether the synthetic message table is the issue or whether this probe should use the blank-prefix save path for now.”

    Action/result: Found the original GR 0x8f use in logic 101 with message 55a.

  2724. Reported: “Original GR’s own verifier call is in logic 101 with message 55a, not GR. That means 0x8f is more of a version/build compatibility check than a save prefix initializer in this interpreter, despite the shared slot being reused by filename code. I’m going to switch the GR save extraction to omit 0x8f and intentionally extract the blank-prefix SG.1, which is enough to validate the v3 save block transform.”

    Action/result: Decided to promote a blank-prefix save extraction and leave GR’s verifier/save-prefix path for future source-first work.

  2725. Reported: “I’m adjusting the GR save probe to make the blank-prefix behavior explicit: no verifier action, no message table, expected output SG.1. The report will still state the signature-prefix check as ‘blank prefix observed,’ so the limitation is clear rather than hidden.”

    Action/result: Changed the GR save probe default to omit 0x8f and use save stem SG.

  2726. Reported: “I’m updating the tests to pin both paths: the default extraction fixture omits the verifier and writes no message table, while the optional verifier form still emits a plain v3 message if a future probe needs it.”

    Action/result: Updated GR save tests for blank-prefix default and optional plain-message verifier payload.

  2727. Reported: “The blank-prefix save fixture is now explicit. I’m rerunning the focused tests, then I’ll rerun QEMU looking for SG.1.”

    Action/result: Focused tests passed 60 tests; py_compile passed.

  2728. Reported: “Focused tests pass again. I’m rerunning the extraction with the default SG stem now.”

    Action/result: QEMU extraction passed and wrote build/gr-v3-behavior/save_xor_extract_qemu_001.json.

  2729. Reported: “The QEMU extraction passed this time. I’m inspecting the JSON and save bytes so I can document the exact block lengths and what the XOR check proved.”

    Action/result: Inspected save_xor_extract_qemu_001.json, SG_001.1, capture metadata, and the DOS directory listing.

  2730. Reported: “The extracted GR save has block lengths [1028, 989, 1811, 100, 12], and the third block round-trips under the 59-byte XOR helper. I’m checking its relationship to the local OBJECT file because the length being 1811 versus OBJECT size 1814 is a nice concrete clue for the subsystem spec.”

    Action/result: Compared encoded/decoded third-block bytes with games/GR/OBJECT; neither form directly matches the source file.

  2731. Reported: “The third block does not directly match the local OBJECT file after either form, which makes sense because the runtime object/inventory table has already been initialized and relocated before saving. I’ll document the direct evidence narrowly: original GR wrote a five-block save, block 3 length 1811, and the source-modeled XOR is a reversible transform over that emitted block.”

    Action/result: Scoped the conclusion to the emitted save envelope and XOR transform.

  2732. Reported: “I’m moving into documentation now: tracker first, then the subsystem/version chapters, then the clean-room notes and progress log with the exact QEMU command/result.”

    Action/result: Began docs/tracker update pass.

  2733. Reported: “I’m going to keep the documentation precise: ‘GR save XOR extraction is QEMU-validated using a blank-prefix SG.1 fixture,’ and separately note that the verifier/signature path was not part of this probe. That keeps the behavioral model honest rather than overclaiming.”

    Action/result: Chose wording for the blank-prefix caveat.

  2734. Reported: “The tracker text has line wraps that didn’t match my patch. I’m reading the exact snippets and applying smaller edits.”

    Action/result: Reread exact PROGRESS.md snippets around snapshot, action deltas, object runtime, and highest-value work.

  2735. Reported: “I’ve got the exact tracker text now. I’m applying the progress update in smaller patches so the line-wrapped Markdown stays clean.”

    Action/result: Updated the current snapshot in PROGRESS.md.

  2736. Reported: “The snapshot now mentions the extracted GR save. I’m updating the detailed v3 action-delta paragraph and remaining-work list next.”

    Action/result: Added the SG.1 extraction result and narrowed remaining GR save work to signature-prefixed save/restore behavior.

  2737. Reported: “The detailed action-delta status is updated with the extracted block lengths and XOR check. I’m clearing the old ‘full save extraction’ item from highest-value remaining work, replacing it with the narrower signature-prefixed save/restore question.”

    Action/result: Updated Highest-Value Remaining Work.

  2738. Reported: “Next I’m updating versions.md so the concise version ledger reflects that GR save extraction is now observed, with the blank-prefix caveat.”

    Action/result: Updated the GR room/state action row in versions.md.

  2739. Reported: “I’m adding a short paragraph below the existing GR probe descriptions with the exact save extraction command/result.”

    Action/result: Added the GR save extraction paragraph to versions.md.

  2740. Reported: “I’m updating the runtime model’s save section now: the normative rule stays source-first, and the QEMU sentence records the blank-prefix validation artifact.”

    Action/result: Updated the save/restore section in runtime_model.md.

  2741. Reported: “I’m also updating the v3 opcode-delta note so 0x7d points at the promoted QEMU extraction instead of only the helper model.”

    Action/result: Updated the GR v3 opcode-delta table in logic_bytecode.md.

  2742. Reported: “I’m revising the symbolic label entry for GR’s save action with the QEMU report path and the observed third-block length.”

    Action/result: Updated docs/src/symbolic_labels.md.

  2743. Reported: “The logic resource chapter still read as if every observed interpreter uses encrypted message text. I’m adding a version note for local GR resources and the new fixture builder option.”

    Action/result: Added the GR message plaintext/version note to logic_resources.md.

  2744. Reported: “I’m updating the compatibility chapter in two places: the local save helper section now says the GR transform has original-engine extraction evidence, and the v3 probe section gets the rerunnable command.”

    Action/result: Updated the save helper paragraph in compatibility_testing.md.

  2745. Reported: “The save helper section is current. I’m inserting the exact save-xor-extract command alongside the other GR v3 QEMU probes.”

    Action/result: Added the GR save extraction command/result to compatibility_testing.md.

  2746. Reported: “The v3 probe section has a ‘recent attempted’ block immediately after mode 4, so I’m inserting the save extraction note just before that.”

    Action/result: Placed the new compatibility note before the attempted fixture registry.

  2747. Reported: “Now I’m adding the detailed clean-room note: it includes the failed verifier attempt as a documented harness observation, then the promoted blank-prefix extraction and what it proves.”

    Action/result: Added the Gold Rush v3 Save Extraction Probe section to clean_room_executable_notes.md.

  2748. Reported: “Before validation, I’m checking whether the promoted GR save probe should be added to the compatibility suite manifest or left as a documented targeted command. If the suite already has version-specific optional layers, I’ll hook it in there.”

    Action/result: Inspected tools/compatibility_suite.py and its tests.

  2749. Reported: “The suite currently has local, SQ2 QEMU smoke, and SQ2 broad layers. I’m adding a separate opt-in qemu-v3 layer for the GR save extraction so it doesn’t surprise normal SQ2 runs or public users without private GR files.”

    Action/result: Added gr_save_xor_extract_qemu and the --include-qemu-v3 flag to tools/compatibility_suite.py.

  2750. Reported: “I found the suite tests. I’m adding the qemu-v3 layer with tests so the manifest is discoverable but still explicit.”

    Action/result: Updated tests/test_compatibility_suite.py.

  2751. Reported: “I’m adding the new --include-qemu-v3 suite invocation to the compatibility docs so future runs can reproduce the GR save check from the manifest.”

    Action/result: Updated the command set in docs/src/compatibility_testing.md.

  2752. Reported: “I’m patching the command set with the v3 opt-in examples and a note that it needs games/GR.”

    Action/result: Documented the separate private-input qemu-v3 layer.

  2753. Reported: “Since the suite manifest now has an opt-in v3 layer, I’m updating PROGRESS.md so the compatibility-suite tracker reflects it.”

    Action/result: Updated the compatibility-suite section of PROGRESS.md.

  2754. Reported: “I’m adding the qemu-v3 layer to the compatibility-suite evidence paragraph without marking it as a full-suite run yet; I’ll validate the manifest locally after docs are done.”

    Action/result: Clarified qemu-v3 status in PROGRESS.md.

  2755. Reported: “I’m adding the suite-manifest change into the clean-room note’s implementation list so the evidence trail includes the new opt-in runner path.”

    Action/result: Updated the GR save extraction clean-room note.

  2756. Reported: “Docs and code are updated. I’m running the validation set now: full unit suite, mdBook, py_compile, manifest dry-run, and whitespace check first; then I’ll run the local compatibility wrapper and the named v3 QEMU suite command.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 275 tests; mdbook build docs, py_compile, AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --dry-run --include-qemu-v3, and git diff --check passed.

  2757. Reported: “The first validation pass is green: 275 tests, mdBook, py_compile, v3 dry-run, and diff check all passed. I’m running the compatibility wrapper now, then the named qemu-v3 save extraction from the suite manifest.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_002.json passed.

  2758. Reported: “The local compatibility wrapper passed and wrote build/compatibility-suite/local_002.json. Now I’m running the single named v3 QEMU suite command to prove the manifest entry itself works.”

    Action/result: The first sandboxed wrapper attempt failed before booting QEMU because VNC socket binding was not permitted from the sandboxed child process.

  2759. Reported: “The named v3 suite command failed before booting because QEMU could not bind its VNC socket from inside the sandboxed wrapper process. The same probe worked when run directly; I’m rerunning the suite wrapper with escalated permissions so its child QEMU process has the same socket access.”

    Action/result: Escalated rerun of the named gr_save_xor_extract_qemu suite command passed and wrote build/compatibility-suite/qemu_v3_save_001.json.

  2760. Reported: “The escalated suite run passed and wrote the v3 save extraction report. I’m checking the suite report and the probe JSON, then I’ll add the validation results to the log and do the final book/diff checks.”

    Action/result: Inspected qemu_v3_save_001.json, save_xor_extract_suite.json, local_002.json, and git status.

  2761. Reported: “Validation produced only generated __pycache__ directories as untracked noise. I’m removing those generated caches, then I’ll log the validation results and rerun the final doc/diff checks because the log will change.”

    Action/result: Removed generated tools/__pycache__ and tests/__pycache__ directories.

  2762. Reported: “The cache directories are gone. Since the v3 suite command passed, I’m updating the tracker/docs from ‘layer exists’ to ‘named v3 suite run passed’ with the report path.”

    Action/result: Updated PROGRESS.md and compatibility_testing.md with build/compatibility-suite/qemu_v3_save_001.json.

  2763. Reported: “The tracker now names the passing v3 suite report. I’m updating the compatibility chapter’s command-set status similarly.”

    Action/result: Recorded the passing v3 suite report in docs/src/compatibility_testing.md.

  2764. Reported: “I’m adding the suite report path to the clean-room save extraction note too, so the direct probe and manifest-level validation are both recorded.”

    Action/result: Added the suite-level report path to docs/src/clean_room_executable_notes.md.

  2765. Reported: “I’m appending the validation results to the progress log now, then I’ll rebuild mdBook and run the final diff check.”

    Action/result: Added validation entries for the GR save extraction and v3 suite-manifest pass.

  2766. Reported: “I’ll pick back up from PROGRESS.md and the current tree, then choose the next highest-value item rather than waiting for another nudge. First I’m checking the tracker and repo state so I don’t step on anything freshly changed.”

    Action/result: Read PROGRESS.md, docs/src/versions.md, and git status. The only pre-existing untracked file was run.sh.

  2767. Reported: “PROGRESS.md says the current interesting frontier is still GR/v3: source-backed deltas exist for save/signature, restart, key-release/menu gates, and v3 resource packing. I’m going to inspect the probe/tooling around those so the next step is driven by the disassembly-backed paths rather than random behavior poking.”

    Action/result: Re-read tools/gr_v3_behavior_probe.py, tests, and relevant tracker/doc references.

  2768. Reported: “The cleanest next bite is the GR 0x8f verifier/save-prefix path: the last probe deliberately avoided it, and the failed synthetic attempt is a clue rather than a conclusion. I’m going to map that path from the GR disassembly first, then decide whether a QEMU confirmation is warranted.”

    Action/result: Located GR verifier references around image offsets 0x108c, 0x245e, 0x5035, and 0x5ec2..0x5eff.

  2769. Reported: “The prior notes already point to GR image offsets around 0x5035 and 0x5ede for the verifier. I’m re-reading those bytes and the local logic use, because the failed 0x8f(\"GR\") fixture probably means the expected signature text is subtler than the filename stem.”

    Action/result: Confirmed helper 0x5ede compares DS:0x0002 against embedded GR\0.

  2770. Reported: “The verifier bytes are direct: GR has a GR\0 string in the code segment and a routine comparing DS:0002 against it. The puzzle is likely in how the generated v3 logic message address is being resolved, so I’m checking the original GR script call and our v3 fixture layout next.”

    Action/result: Disassembled GR logic 101 and found 0x8f(#3) at bytecode offset 0x0004.

  2771. Reported: “My first disassemble_logic call was too broad and dumped the whole game, which is noisy but harmless. I’m narrowing with a small local parser so I can inspect just logic 101’s header, code prefix, and message table bytes.”

    Action/result: Re-ran the disassembler without --limit and inspected logic 101 only.

  2772. Reported: “Logic 101 confirms the original script does call 0x8f with message 3 before normal setup, so using the verifier in a synthetic fixture is legitimate in principle. I’m checking how the message lookup routine expects offsets, because a one-byte mismatch there would explain the early exit.”

    Action/result: Re-read the message resolver and bounded-copy routines.

  2773. Reported: “The quick Python snippet tripped the project’s ‘no default game’ guard, so I’ll pass AGI_GAME_DIR explicitly even for local byte inspection. That keeps the tooling behavior aligned with the repo rule.”

    Action/result: Re-ran local message parsing with AGI_GAME_DIR=games/GR.

  2774. Reported: “The verifier handler copies at most six bytes from the selected message into DS:0002, then compares that string with the embedded GR\0. That means the old ‘plain GR messages’ observation is worth re-testing against the exact message table, especially logic 101 message 3.”

    Action/result: Parsed logic 101’s message table with table-base-relative offsets and decrypted from the message text-region start.

  2775. Reported: “This turned up a useful correction: GR logic messages are encrypted with the same local message-text XOR pattern; logic 101’s message 3 decrypts to GR\0. The earlier synthetic verifier failed because the v3 fixture wrote GR in plain text, so I’m going to fix that and promote a signed GR save probe rather than leaving the verifier path unresolved.”

    Action/result: Confirmed encrypted message bytes for logic 101 message 3 are 35 35 61, decrypting to GR\0.

  2776. Reported: “I’ll keep the blank-prefix probe for the focused XOR evidence, and add a signed variant as a separate QEMU-v3 command. That gives us both: the isolated save-block transform and the real 0x8f -> GRSG.N filename/signature path.”

    Action/result: Chose to add --verify-signature rather than replacing the existing blank-prefix probe.

  2777. Reported: “I’m about to edit the probe/tooling: the verifier path will use encrypted message text, the save extractor will understand expected blank versus GR prefixes, and the suite will gain an explicit signed GR save case.”

    Action/result: Updated tools/gr_v3_behavior_probe.py with signed fixture generation, expected prefix checks, and default GRSG save stem selection when --verify-signature is used.

  2778. Reported: “The probe now has a signed mode. I’m tightening the tests around the message encoding so this correction stays pinned: GR’s verifier message should be encrypted in the resource, not stored as plain GR.”

    Action/result: Updated tests/test_gr_v3_behavior_probe.py to assert encrypted fixture bytes decrypt to GR\0.

  2779. Reported: “I’m adding the signed fixture assertion too, so the generated copy is checked for both the opcode sequence and the patched v3 resource payload.”

    Action/result: Added a test for build_gr_save_extract_fixture(..., verify_signature=True).

  2780. Reported: “Now I’m wiring the signed probe into the optional qemu-v3 layer as a separate named command, with its own fixture root and report paths.”

    Action/result: Added gr_signed_save_xor_extract_qemu to tools/compatibility_suite.py.

  2781. Reported: “I’m updating the suite manifest test so it knows about the new signed GR case and keeps qemu-v3 opt-in only.”

    Action/result: Updated tests/test_compatibility_suite.py.

  2782. Reported: “The code changes are in. I’m running the focused unit tests and a syntax check before trying QEMU, since this path touches fixture generation and suite selection.”

    Action/result: Focused unit tests for GR behavior probes, qemu fixtures, and compatibility suite passed; py_compile passed.

  2783. Reported: “Focused tests passed. Next I’m checking the manifest selection, then I’ll run the signed GR probe under QEMU to see whether the original engine writes GRSG.1 and embeds the expected prefix in block 0.”

    Action/result: compatibility_suite.py --dry-run --include-qemu-v3 showed both blank and signed GR commands; signed dry-run fixture generation wrote save_xor_extract_signed_dryrun.json.

  2784. Reported: “The dry run shows the signed fixture being generated cleanly and the suite keeping both GR cases in the opt-in layer. I’m launching the signed QEMU probe now; this may take a little while because it boots DOS, saves, snapshots the disk, and extracts the save.”

    Action/result: Direct signed QEMU probe passed and wrote build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json.

  2785. Reported: “Signed QEMU passed. I’m reading the JSON report and extracted save metadata now so the docs can record exact evidence rather than just ‘it worked.’”

    Action/result: Confirmed expected file GRSG.1, first-block prefix 47 52 00, block lengths 1028, 989, 1811, 100, 12, and matching third-block XOR hashes.

  2786. Reported: “The signed save is exactly what the source predicted: GRSG.1, block 0 starts 47 52 00, and the XOR block hashes match the blank-prefix save. I’m correcting the docs now, including the earlier mistaken ‘plain v3 messages’ note.”

    Action/result: Searched the docs for stale plain-message and signature-save remaining-work references.

  2787. Reported: “I found the stale doc text: it says GR messages were plain and still lists signed saves as remaining work. I’m replacing that with the corrected source observation and the new QEMU evidence.”

    Action/result: Updated PROGRESS.md to mark signed GR save extraction as covered and narrow remaining save work to signed restore if needed.

  2788. Reported: “PROGRESS.md now treats signed GR saving as done and narrows the remaining v3 save question to restore-side confirmation. I’m carrying the same correction into the version ledger and runtime model.”

    Action/result: Updated docs/src/versions.md.

  2789. Reported: “The logic-resource chapter had the most important stale statement, so I’m replacing it with the exact GR evidence: encrypted text region, table offsets relative to the table base, and logic 101 message 3 decrypting to GR.”

    Action/result: Updated docs/src/logic_resources.md.

  2790. Reported: “Now I’m updating the runtime model so the implementation-facing save section describes both filename/signature cases and does not imply the blank-prefix probe is the whole story.”

    Action/result: Updated docs/src/runtime_model.md.

  2791. Reported: “The opcode chapter’s GR delta row now needs the signed-save report too, so 0x7d points at both original-engine confirmations.”

    Action/result: Updated docs/src/logic_bytecode.md.

  2792. Reported: “I’m updating the symbolic label map so future version comparisons can anchor the signed GR path by label instead of rediscovering this address behavior.”

    Action/result: Updated docs/src/symbolic_labels.md.

  2793. Reported: “The compatibility chapter gets two small updates: the qemu-v3 layer now has both blank and signed save commands, and the signed command gets its reproducible invocation recorded.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2794. Reported: “I’m also noting that the signed save did not change the state-block shape or XOR hashes, which is a nice small sanity check on the prefix path.”

    Action/result: Added signed-vs-blank save comparison details to the compatibility docs.

  2795. Reported: “I’m fixing the clean-room notes next. Rather than hiding the old mistaken observation, I’ll record it as corrected evidence: the raw bytes looked readable only because the wrong offset/decryption basis was used.”

    Action/result: Corrected the earlier GR save extraction note.

  2796. Reported: “This is the evidence trail bit where being explicit matters: I’m writing down the failed signed attempt, the corrected encrypted-message interpretation, and the successful signed QEMU run as a follow-up correction.”

    Action/result: Added Gold Rush v3 Signed Save Extraction Correction to docs/src/clean_room_executable_notes.md.

  2797. Reported: “Docs now include the correction and the signed run. I’m running the full local validation set next, then the signed suite command through the compatibility runner so the manifest path itself is exercised.”

    Action/result: Full unit suite passed 276 tests; mdbook build docs and py_compile passed.

  2798. Reported: “Full unit tests passed at 276 tests, and mdBook plus syntax checks are green. I’m doing the manifest dry-run and whitespace check, then I’ll run the signed QEMU command through compatibility_suite.py.”

    Action/result: compatibility_suite.py --dry-run --include-qemu-v3 and git diff --check passed.

  2799. Reported: “Dry-run and diff check passed. The full tests created only __pycache__ noise; I’ll clean that up after the remaining QEMU-suite run.”

    Action/result: The sandboxed suite wrapper attempt failed before booting QEMU because the child QEMU process could not bind its local VNC socket.

  2800. Reported: “The suite wrapper hit the same sandboxed VNC bind problem we saw before: QEMU itself never got to boot. I’m rerunning the identical named suite command with elevated permission so the child QEMU process can bind the local VNC socket.”

    Action/result: Escalated named suite command gr_signed_save_xor_extract_qemu passed and wrote build/compatibility-suite/qemu_v3_signed_save_001.json.

  2801. Reported: “The escalated suite run passed and wrote the signed suite report. I’m checking the report contents, then I’ll remove generated caches and update the log/tracker with final validation details.”

    Action/result: Inspected qemu_v3_signed_save_001.json and save_xor_extract_signed_suite.json; both record return code zero and the expected GRSG.1 checks.

  2802. Reported: “The signed suite report is clean: return code 0, GRSG.1, prefix 47 52 00, and the expected XOR checks. I’m removing the generated __pycache__ directories now so the working tree only shows source/doc changes plus the existing untracked run.sh.”

    Action/result: Removed generated tools/__pycache__ and tests/__pycache__.

  2803. Reported: “With generated caches removed, I’m updating the tracker to name the passing signed suite report and the current unit-test count.”

    Action/result: Updated PROGRESS.md with build/compatibility-suite/qemu_v3_signed_save_001.json and the 276-test local run.

  2804. Reported: “I’m adding the suite-level signed report path to the compatibility chapter too, so direct and manifest validation are both recorded.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2805. Reported: “The final book and diff checks are clean, and no cache directories remain. I’m running the local compatibility wrapper once more so the opcode-evidence freshness check is covered alongside tests and mdBook.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_signed_save_001.json passed. The report records 276 passing tests, mdbook build docs, and tools/logic_opcode_evidence.py --check.

  2806. Reported: “The local compatibility wrapper passed too, including 276 tests, mdBook, and opcode-evidence freshness. I’m cleaning the cache noise it created and recording that final validation in the log.”

    Action/result: Inspected local_signed_save_001.json; no __pycache__ directories were present after the -B run.

  2807. Reported: “I’ll pick this back up from the GR v3 restore thread: first I’m going to rescan the current tree and the relevant probe/docs so I’m not leaning on stale state, then I’ll implement the next PROGRESS.md item source-first and use QEMU only as confirmation.”

    Action/result: git status --short showed only existing untracked run.sh; PROGRESS.md still listed signed GR restore as high-value remaining v3 work.

  2808. Reported: “The tree is clean except for the existing untracked run.sh. The dashboard still has the signed GR restore behavior as the most useful source-backed v3 item, so I’m reading the probe internals around save extraction and QEMU snapshot execution before patching it.”

    Action/result: Re-read tools/gr_v3_behavior_probe.py, tools/save_roundtrip_probe.py, tools/qemu_fixture.py, tools/qemu_snapshot.py, and focused tests.

  2809. Reported: “The current GR probe already has the save-side pieces and a reusable save extractor. I’m going to add the restore round-trip as a separate v3 probe rather than changing the existing save probe, so the evidence stays split between ‘file envelope’ and ‘restore behavior.’”

    Action/result: Designed a separate signed-restore-roundtrip probe that generates GRSG.1, restores it in a second fixture, and compares against direct saved/unrestored controls.

  2810. Reported: “Before I edit the harness, I’m going back to the disassembly for the restore routine so the probe is tied to the actual v3 code path, not just the SQ2 harness pattern. The key thing I’m checking is where the first block and object/inventory block land, and whether GR transforms the latter after reading.”

    Action/result: Read existing restore/save notes and tried local disassembly around the documented GR restore/XOR offsets.

  2811. Reported: “That rizin call defaulted to the host architecture, so I’m discarding that excerpt and rerunning with explicit 16-bit x86 settings. Tiny mistake, easy correction.”

    Action/result: Discarded the incorrect host-architecture rizin output.

  2812. Reported: “The GR executable is an MZ-style image with the code image starting after the header, so the raw disassembler view is about 0x202 bytes later than the image labels used in our docs. I found the restore prologue at raw 0x2994, which matches documented image 0x2792; now I’m pulling the relevant raw-offset snippets.”

    Action/result: Re-ran rizin -q -a x86 -b 16 at raw offsets 0x2994, 0x2b44, 0x2ac8, and 0x09be, confirming GR restore reads five blocks and calls the same XOR helper over [0x07d6]..+[0x07da].

  2813. Reported: “I’m going to patch tools/gr_v3_behavior_probe.py now with a signed-restore round-trip probe: generate a real GRSG.1 through the v3 engine, boot a restore fixture that starts in a deliberately different visual state, and compare the restored capture against a direct ‘saved state’ fixture.”

    Action/result: Added bytecode helpers and signed restore save/restore/ direct payload builders to tools/gr_v3_behavior_probe.py.

  2814. Reported: “The bytecode payload side is in place now. I’m adding fixture builders next, keeping the generated save and restore copies under build/ and leaving games/GR as read-only evidence input.”

    Action/result: Added generated GR fixture builders for save production, restore from generated GRSG.1, and saved/unrestored direct controls.

  2815. Reported: “The builder now creates three fixture roles: the save producer, the restore-under-test, and two comparison captures. I’m wiring the QEMU runner so the save file produced by the original engine becomes the input to the restore fixture.”

    Action/result: Added run_gr_signed_restore_qemu(...) to chain original save extraction, comparison captures, and restore capture comparison.

  2816. Reported: “The QEMU runner is now chained: save extraction first, then restore validation, then capture comparisons. I’m adding the CLI surface and report shape so it can be run directly and later from the compatibility suite.”

    Action/result: Added --probe signed-restore-roundtrip to tools/gr_v3_behavior_probe.py.

  2817. Reported: “The CLI branch is ready. I’m adding focused unit tests now so the generated logic bytes, encrypted signature message, and copied save-file fixture behavior are pinned down before I try booting QEMU.”

    Action/result: Added focused tests for signed restore payloads and fixture construction in tests/test_gr_v3_behavior_probe.py.

  2818. Reported: “The focused tests are in. I’m running the lightweight validation now; if that’s clean, I’ll do a build-only probe report and then the QEMU confirmation.”

    Action/result: py_compile passed; running the test without AGI_GAME_DIR hit the project-wide game-directory guard.

  2819. Reported: “The pycompile passed, but the unittest import path is hitting the project’s ‘game directory required’ guard before the fixture tests run. I’m checking which import is calling the configured game directory so I can keep the tests synthetic and parameterized.”

    Action/result: Confirmed the guard is expected from shared project path helpers; reran with AGI_GAME_DIR=games/SQ2 and the 18 focused tests passed.

  2820. Reported: “With AGI_GAME_DIR set, the synthetic fixture tests pass. That’s consistent with the project-wide path guard; I’ll use the explicit environment for validation commands and keep the new probe’s own --game-dir games/GR parameter explicit.”

    Action/result: Build-only signed restore probe wrote build/gr-v3-behavior/signed_restore_roundtrip_dryrun_001.json.

  2821. Reported: “The dry run generated the fixture report cleanly. I’m going to run the full QEMU probe now; this should boot three short batches: save producer, direct comparison controls, and restore-under-test.”

    Action/result: Started direct QEMU probe signed_restore_roundtrip_qemu_001.json.

  2822. Reported: “The QEMU probe is still running; it got past fixture generation and is in the emulator-driven part. I’ll let it finish and inspect the JSON rather than guessing from the runtime length.”

    Action/result: Direct QEMU probe continued running.

  2823. Reported: “Still running. This is the cost of using the original engine as the oracle, but the probe is structured so a passing run gives us a much stronger restore claim than a parser-only check.”

    Action/result: Direct QEMU probe completed and wrote build/gr-v3-behavior/signed_restore_roundtrip_qemu_001.json.

  2824. Reported: “The QEMU run completed and wrote its report. I’m reading the checks and capture hashes now to see whether the restored screen matched the saved-state control.”

    Action/result: Report checks all passed: restored capture matched direct saved-state control and differed from the unrestored control.

  2825. Reported: “The QEMU report passed: restored capture equals the saved-state direct capture and differs from the unrestored control; the generated save still has the GR signature prefix and XOR-round-tripping third block. I’m promoting this into the optional qemu-v3 suite next.”

    Action/result: Added named compatibility-suite command gr_signed_restore_roundtrip_qemu.

  2826. Reported: “The suite manifest now has a named signed-restore command. I’m updating the manifest tests so future changes keep it in the explicit opt-in v3 layer.”

    Action/result: Updated tests/test_compatibility_suite.py for the new qemu-v3 command.

  2827. Reported: “The suite test now tracks the new command. I’m running the focused tests plus a qemu-v3 dry run; that checks command selection without spending another emulator pass yet.”

    Action/result: Focused tests passed 24 tests, and compatibility_suite.py --dry-run --include-qemu-v3 showed the new signed-restore command.

  2828. Reported: “The dry run shows the new named command in qemu-v3. Since the direct QEMU probe already passed, I’m running the suite wrapper for just that command to make sure the manifest path itself is executable too.”

    Action/result: Sandboxed suite-wrapper run failed before boot because QEMU could not bind the local VNC socket.

  2829. Reported: “The suite wrapper hit the usual sandboxed VNC bind failure before DOS boot. I’m rerunning the same named command with escalation so QEMU can bind its local VNC socket.”

    Action/result: Started escalated named suite command gr_signed_restore_roundtrip_qemu.

  2830. Reported: “The escalated suite run is in progress now. It’s repeating the same save/control/restore chain, this time through the compatibility-suite entry.”

    Action/result: Escalated suite command continued running.

  2831. Reported: “Still running under the suite wrapper. No output yet is normal for these QEMU probes; I’ll inspect the report as soon as it returns.”

    Action/result: Escalated suite command completed successfully and wrote build/gr-v3-behavior/signed_restore_roundtrip_suite.json.

  2832. Reported: “The suite command completed successfully and wrote the probe JSON. I’m checking both the suite report and the underlying probe report now, then I’ll update the docs and tracker.”

    Action/result: Inspected build/compatibility-suite/qemu_v3_signed_restore_001.json and build/gr-v3-behavior/signed_restore_roundtrip_suite.json; return code was zero and all restore checks passed.

  2833. Reported: “The docs still describe signed restore as remaining, so I’m replacing that with the source/QEMU result. I’ll keep the language careful: the model is for valid save data and the probe confirms the ordinary signed restore path, not malformed save/exploit behavior.”

    Action/result: Began updating PROGRESS.md, runtime docs, symbolic labels, compatibility notes, and clean-room notes.

  2834. Reported: “I’m editing the tracker and docs in one pass now: PROGRESS gets the dashboard update, symbolic labels gets the GR restore routine association, and the evidence chapters get the commands/results needed for clean-room provenance.”

    Action/result: Updated the GR/SQ2 tracker summary and remaining-work list in PROGRESS.md.

  2835. Reported: “I found a second dashboard paragraph and the ‘Highest-Value’ list still naming signed restore as future work. I’m updating those too so the tracker doesn’t contradict the new evidence.”

    Action/result: Updated compatibility-suite and highest-value sections in PROGRESS.md.

  2836. Reported: “The runtime model now includes the restore-side XOR and visual round-trip result. I’m adding the corresponding symbolic label entry for GR 0x7e so future version comparisons can anchor the routine by role instead of by raw address.”

    Action/result: Added GR code.save.restore_game_state association to docs/src/symbolic_labels.md.

  2837. Reported: “The compatibility chapter currently says the v3 layer only covers save extraction. I’m updating that to include the restore round-trip command and suite report.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2838. Reported: “I’m adding a dedicated clean-room note section for this pass. It records the disassembly offsets, the fixture shape, the direct QEMU command, the suite command, and the exact pass conditions from the report.”

    Action/result: Added Gold Rush v3 Signed Restore Round Trip to docs/src/clean_room_executable_notes.md.

  2839. Reported: “The clean-room evidence section is in place. I’m checking the Versions chapter too, because this GR restore behavior is a version-specific delta and should be visible in the concise ledger as well.”

    Action/result: Updated docs/src/versions.md with the GR signed restore delta and report path.

  2840. Reported: “I’m also adding the new signed-restore command to AGENTS.md’s local tool notes, so future sessions can rerun it without rediscovering the flags.”

    Action/result: Added the signed GR save/restore round-trip command to AGENTS.md.

  2841. Reported: “The reusable command is now in AGENTS.md. I’m appending the progress log entries for this whole pass, including the discarded bad rizin architecture read and the sandbox/VNC rerun, so the historical trail stays complete.”

    Action/result: Appended progress-log entries for the signed GR restore implementation, QEMU validation, suite promotion, and documentation pass.

  2842. Reported: “The log tail is from the previous signed-save pass, so I’m appending the new signed-restore entries after that rather than rewriting history.”

    Action/result: Continued the numbered progress log from entry 2955.

  2843. Reported: “Docs and tracker are updated. I’m moving into final validation: full unit discovery, mdBook, syntax checks, and then the local compatibility wrapper so opcode evidence freshness is covered too.”

    Action/result: Full unit discovery passed 279 tests; mdbook build docs and py_compile checks passed.

  2844. Reported: “Full unit discovery passed at 279 tests, mdBook built, and syntax checks are clean. I’m updating the tracker’s test count, then I’ll run the local compatibility manifest.”

    Action/result: Updated PROGRESS.md to record the 279-test local run.

  2845. Reported: “The test count in PROGRESS.md now reflects the 279-test run. I’m running the local compatibility wrapper next; this should cover unit tests, mdBook, and opcode-evidence freshness in one report without booting QEMU.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_signed_restore_001.json passed.

  2846. Reported: “The local compatibility wrapper passed and produced the expected unit/mdBook output. I’m checking the JSON report, then I’ll do whitespace checks and clean any generated cache directories.”

    Action/result: Inspected local_signed_restore_001.json; all three local commands returned zero. git diff --check passed.

  2847. Reported: “The checks are clean, and only generated __pycache__ directories plus the existing run.sh are untracked. I’m removing the cache directories so the remaining diff is source/docs only.”

    Action/result: Removed generated tests/__pycache__ and tools/__pycache__.

  2848. Reported: “The cache directories are removed. I’m doing one final status/diff summary and then I’ll add the last validation entries to the progress log.”

    Action/result: Final status showed source/doc/test changes plus the existing untracked run.sh; no cache directories remained.

  2849. Reported: “I’ll pick this back up from the current tracker rather than guessing from memory. First I’m checking the live status and PROGRESS.md, then I’ll choose the next highest-value item and keep the evidence trail updated.”

    Action/result: Read git status --short, PROGRESS.md, the progress-log tail, and the Versions chapter. The existing signed-restore pass was still uncommitted, with only pre-existing run.sh untracked.

  2850. Reported: “The live tree is already carrying the signed-restore pass, and PROGRESS.md now points to the remaining work as mostly optional GR probes plus broader renderer/UI/audio/spec polishing. I’m going to locate the explicit remaining-work sections and pick something source-led rather than launching another blind QEMU sweep.”

    Action/result: Searched remaining-work markers and GR restart/menu/key references. The restart prompt-marker truth table was already source-modeled and listed as an optional observable confirmation target.

  2851. Reported: “There are two threads worth pursuing next: optional observable probes for GR’s remaining source-backed deltas, or polishing the implementation spec. I’m going to start with the smallest source-first delta, the GR restart prompt-marker behavior, because the model already exists and a focused original-engine confirmation should either retire that optional item or reveal where the source reading needs tightening.”

    Action/result: Re-read the restart truth-table section, tools/agi_restart.py, and tests/test_restart_model.py.

  2852. Reported: “The restart truth table is already modeled from source and tested locally, but it remains marked optional for observable confirmation. I’m reading the existing GR fixture machinery and the older SQ2 restart/input probes so I can add this as a compact v3 probe instead of inventing a parallel harness.”

    Action/result: Read tools/gr_v3_behavior_probe.py, relevant logic_interpreter_probe.py cases, and fixture helper functions.

  2853. Reported: “The accepted-restart side is awkward to capture because the engine resets and can immediately re-enter the generated logic. The cancel side is much cleaner: make the marker visible or hidden, call 0x80, press Escape, and compare the final screen against direct visible/hidden controls. That still tests the GR-specific conditional branch that differs from the simple SQ2 description.”

    Action/result: Chose a four-fixture QEMU design: hidden control, visible control, hidden then Escape-canceled restart, and visible then Escape-canceled restart.

  2854. Reported: “One small harness gap surfaced: the v3 probe case type can type characters, but it doesn’t yet expose named keys like esc, while the shared QEMU snapshot runner already supports them. I’m going to wire that through, then add the restart-cancel cases and compare visible/hidden prompt-marker controls.”

    Action/result: Confirmed SnapshotFixtureCase already supports post_launch_key_names, then planned to forward that field from ProbeCase.

  2855. Reported: “For the actual pass/fail signal, I’ll avoid relying only on whole-screen equality. The shared tools can downsample QEMU captures into logical EGA nibbles, so the probe can count prompt-row foreground pixels and still include whole-capture comparisons as extra evidence.”

    Action/result: Reused downsample_qemu_picture_nibbles() and selected logical prompt rectangle (0,40)..(39,47) for foreground-pixel counts.

  2856. Reported: “I’ve got the shape now. I’m going to add a restart-prompt-marker probe with four fixtures: direct hidden, direct visible, cancel-after-hidden, and cancel-after-visible. The QEMU check will compare prompt-row foreground counts, with capture equality reported but not used as the sole oracle.”

    Action/result: Added the new v3 restart probe to tools/gr_v3_behavior_probe.py, including named-key forwarding, fixture builders, prompt-row counting, CLI handling, and JSON reporting.

  2857. Reported: “The probe code is in. I’m adding fixture-level tests next so the new bytecode stays pinned: visible uses 0x78, hidden uses 0x77, restart cases use 0x80, and the cancel cases carry the named esc input through the snapshot runner.”

    Action/result: Added focused tests for restart prompt-marker payloads and generated fixture cases in tests/test_gr_v3_behavior_probe.py.

  2858. Reported: “The local tests now cover the generated restart fixture shapes. I’m also adding this as an opt-in qemu-v3 suite command so it can be rerun by name and won’t surprise the default local suite.”

    Action/result: Added gr_restart_prompt_marker_qemu to tools/compatibility_suite.py.

  2859. Reported: “The suite manifest has the named command now. I’m updating the manifest test so future changes don’t accidentally drop the v3 restart probe from the opt-in layer.”

    Action/result: Updated tests/test_compatibility_suite.py to assert the new command is in the qemu-v3 layer.

  2860. Reported: “I’ve finished the first edit pass. Now I’m running focused syntax and unit checks before trying the QEMU case, because if the fixture bytes are wrong it’s nicer to catch that locally.”

    Action/result: py_compile passed for the edited tools/tests. Focused unit run over GR behavior, compatibility suite, and restart model tests passed 28 tests.

  2861. Reported: “The focused checks are clean: syntax passed and the three focused test modules ran 28 tests. I’m generating the dry-run fixture report next, then I’ll boot QEMU for the original-engine confirmation if the report shape looks right.”

    Action/result: Built dry-run report build/gr-v3-behavior/restart_prompt_marker_dryrun_001.json and confirmed the suite dry-run lists gr_restart_prompt_marker_qemu under qemu-v3.

  2862. Reported: “The report shape is good: four cases, the two cancel cases send esc, and the prompt row is recorded as logical Y 40..47. I’m launching the original-engine QEMU run now; if the sandbox blocks the VNC listener I’ll rerun it with the existing QEMU approval path.”

    Action/result: Started the direct QEMU run for build/gr-v3-behavior/restart_prompt_marker_qemu_001.json.

  2863. Reported: “QEMU is still running the four-case restart batch. This one boots once, saves a DOS-prompt snapshot, then restores between the hidden/visible control and Escape-cancel cases.”

    Action/result: The direct QEMU process continued and then completed successfully.

  2864. Reported: “The QEMU run completed successfully. I’m inspecting the JSON now, especially the prompt-row counts, because that’s the actual behavioral signal for the restart branch.”

    Action/result: Direct report passed. Hidden control and hidden cancel both had 0 prompt-row foreground pixels and matching captures; visible control and visible cancel both had 8 prompt-row foreground pixels and matching captures.

  2865. Reported: “The direct QEMU result is exactly the signal we wanted: hidden control and hidden cancel both have 0 prompt-row foreground pixels; visible control and visible cancel both have 8, and the full captures match their respective controls. I’m running the named compatibility-suite command once so the manifest path is verified too.”

    Action/result: Initial named suite run failed before the DOS snapshot because nested QEMU could not bind VNC from the sandbox: Failed to bind socket: Operation not permitted.

  2866. Reported: “The suite wrapper hit the expected sandbox VNC bind restriction, not a probe failure. I’m rerunning that exact named suite command with escalation so QEMU can bind its local VNC socket.”

    Action/result: Reran AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_restart_prompt_marker_qemu --report build/compatibility-suite/qemu_v3_restart_prompt_001.json with escalation.

  2867. Reported: “The escalated suite run is in progress. It’s the same four-case QEMU batch, just launched through the manifest so the reusable command path is covered.”

    Action/result: The named suite command passed and wrote build/compatibility-suite/qemu_v3_restart_prompt_001.json plus build/gr-v3-behavior/restart_prompt_marker_suite.json.

  2868. Reported: “The named suite command passed after the VNC permission rerun. I’m checking the suite report and underlying probe JSON, then I’ll update the docs/tracker to retire the optional restart confirmation item.”

    Action/result: Confirmed the suite return code was zero and the suite probe repeated the 0-vs-8 prompt-row foreground count result.

  2869. Reported: “The restart probe is now original-engine validated. I’m updating the evidence docs and tracker in the same style as the prior GR probes: source reading first, then QEMU as confirmation, with the suite report path recorded for reruns.”

    Action/result: Updated PROGRESS.md, docs/src/runtime_model.md, docs/src/versions.md, and docs/src/compatibility_testing.md.

  2870. Reported: “The rerun command is now in AGENTS.md. I’m adding a short clean-room evidence section with the exact commands, the direct disassembly-based model it confirms, and the QEMU counts/hashes.”

    Action/result: Added the restart prompt-marker QEMU command to AGENTS.md and appended a clean-room evidence section to docs/src/clean_room_executable_notes.md.

  2871. Reported: “The evidence log now has the QEMU confirmation, including the expected sandbox/VNC rerun note. I’m doing one small symbolic-label tweak so the restart routine’s GR association points to the new observable evidence too.”

    Action/result: Updated docs/src/symbolic_labels.md for GR code.restart.confirm_restart_action.

  2872. Reported: “The symbolic map now points to the restart QEMU report. I’m preserving the intermediate work log before the final validation pass, so the sequence from tracker read through QEMU confirmation is recorded rather than just the end result.”

    Action/result: Appended progress-log entries 2997 through 3019 for the GR restart prompt-marker probe pass.

  2873. Reported: “The progress log now records this pass through the docs updates. I’m moving into validation: full unit discovery, mdBook, syntax checks, the local compatibility wrapper, and whitespace checks.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 282 tests; mdbook build docs passed; focused py_compile checks passed.

  2874. Reported: “Full unit discovery passed 282 tests, mdBook built, and syntax checks are clean. I’m updating the tracker’s test count, then I’ll run the local compatibility wrapper and final whitespace/status checks.”

    Action/result: Updated PROGRESS.md to record the 282-test local run.

  2875. Reported: “The tracker now reflects the 282-test run. I’m running the local compatibility manifest next; this covers unit tests, mdBook, and opcode-evidence freshness without launching QEMU again.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_restart_prompt_001.json passed.

  2876. Reported: “The local compatibility wrapper passed too, again with 282 tests and a clean mdBook build. I’m checking its JSON, then I’ll run git diff --check and clean any generated cache directories left behind.”

    Action/result: Inspected build/compatibility-suite/local_restart_prompt_001.json; unit tests, mdBook, and opcode evidence all returned zero. git diff --check passed. tests/__pycache__ and tools/__pycache__ were present.

  2877. Reported: “git diff --check is clean, and the local compatibility report has all three return codes at zero. The only generated clutter is tests/__pycache__ and tools/__pycache__, so I’m removing those before the final status.”

    Action/result: Removed generated Python cache directories.

  2878. Reported: “The cache directories are gone. I’m doing one final status/stat pass, then I’ll add the final validation entries to the progress log.”

    Action/result: Final status showed the intended source/docs/test changes plus the pre-existing untracked run.sh; no __pycache__ directories remained. git diff --check still passed.

  2879. Reported: “The final log entries are in. Since that changed the mdBook source after the earlier build, I’m doing a quick final mdBook build and whitespace check so the last edit is covered too.”

    Action/result: mdbook build docs passed, git diff --check passed, and final status still showed only the intended modified files plus the pre-existing untracked run.sh.

  2880. Reported: “I’ll pick up where the menu-gate probe left off: first I’ll update the docs and progress tracker with the source-backed/QEMU-confirmed result, then I’ll run the local checks so this lands as a coherent evidence step.”

    Action/result: Resumed from a clean committed baseline except for the uncommitted menu-gate probe implementation, generated Python caches, and the pre-existing untracked run.sh.

  2881. Reported: “The code/test side is already in place and the successful QEMU evidence exists; the remaining work is bookkeeping and making the docs describe the actual oracle cleanly, including the false start where the logic never yielded back to the main cycle.”

    Action/result: Read the current tracker, versions chapter, runtime model, opcode chapter, compatibility chapter, AGENTS instructions, symbolic labels, and the tail of the clean-room evidence notes.

  2882. Reported: “I’m going to make a compact docs patch now: tracker status, version/runtime bytecode notes, compatibility commands, symbolic labels, AGENTS rerun instructions, and the chronological evidence/progress logs.”

    Action/result: Began the documentation pass for the GR v3 menu interaction gate probe.

  2883. Reported: “The tracker currently still treats the menu gate as a remaining item even though the new probe confirmed it. I’m tightening that up so the dashboard points next at raw key-release behavior rather than sending future-us back over solved ground.”

    Action/result: Updated PROGRESS.md current snapshot and GR/SQ2 comparison sections to mark the menu gate QEMU-confirmed and leave raw key-release as the remaining source-backed gate behavior.

  2884. Reported: “That removes the stale remaining-work pointer. I’m folding the same result into the menu subsystem summary and compatibility-suite paragraph so the status reads consistently from both angles.”

    Action/result: Updated PROGRESS.md menu evidence, compatibility-suite evidence, and Highest-Value Remaining Work.

  2885. Reported: “Next I’m updating the version ledger and opcode chapter. These are the places someone comparing v2/v3 will probably land first, so the wording needs to distinguish ‘source says this gate exists’ from ‘we confirmed the observable gate effect.’”

    Action/result: Updated docs/src/versions.md and docs/src/logic_bytecode.md with the menu_gate_suite evidence and the zero/nonzero gate behavior.

  2886. Reported: “The runtime model is getting a small implementation-facing note too: for v2, 0xa1 plus flag 14 is enough to request the menu; for GR v3, the request is additionally filtered by the [0x0403] gate before drawing/waiting.”

    Action/result: First runtime-model patch missed the exact table anchor; reread the nearby lines and inserted the GR v3 gate note before the menu interaction lifecycle table.

  2887. Reported: “Compatibility docs already know about the v3 suite layer, but not the promoted menu-gate named command. I’m adding both the short result paragraph and the exact rerun command.”

    Action/result: Updated docs/src/compatibility_testing.md with the v3 menu-gate suite report, direct behavior paragraph, and rerun command.

  2888. Reported: “I’m adding the reusable command to AGENTS as well, since that file is the quick-start memory for future sessions and needs to expose promoted probes without hunting through the mdBook.”

    Action/result: Added the Gold Rush v3 menu interaction gate command and expected comparisons to AGENTS.md.

  2889. Reported: “Now I’m appending the clean-room evidence section. I’m including the useful failure, because it explains a subtle interpreter scheduling point: a generated logic script that self-loops never returns to the main cycle, so the menu request flag is never serviced.”

    Action/result: Appended the clean-room evidence section covering source reads, generated fixture design, the self-loop correction, successful QEMU direct report, and suite report.

  2890. Recorded retrospectively from the current menu-gate implementation pass: Began from PROGRESS.md highest-value remaining work and selected the source-backed GR v3 menu interaction gate 0xb1 as the next target.

    Action/result: Chose a probe that could be driven from ordinary generated logic in a copied GR fixture, using QEMU only as confirmation.

  2891. Recorded retrospectively from the current menu-gate implementation pass: Reused the existing SQ2 menu probes as the behavioral template for one-heading/one-item menu setup, finalization, item enable state, and status-byte selection.

    Action/result: Identified the pieces needed for a GR fixture: 0x9c/0x9d/0x9e menu construction, flag 14, 0xa1 menu request, and GR-only 0xb1.

  2892. Recorded retrospectively from the current menu-gate implementation pass: Re-read the GR disassembly around action 0xb1, action 0xa1, code.menu.interact, and the main-cycle request check.

    Action/result: Confirmed that 0xb1 writes word [0x0403], 0xa1 writes request word [0x1b67], the main cycle calls 0x9724, and 0x9724 returns immediately while [0x0403] == 0.

  2893. Recorded retrospectively from the current menu-gate implementation pass: Added menu-gate constants and payload builders to tools/gr_v3_behavior_probe.py.

    Action/result: Implemented generated marker-draw helpers, direct control payloads, menu-gate request payloads, fixture builders, QEMU comparison logic, and the --probe menu-gate CLI branch.

  2894. Recorded retrospectively from the current menu-gate implementation pass: Added local test coverage for the new fixture builder and suite command.

    Action/result: Updated tests/test_gr_v3_behavior_probe.py, tools/compatibility_suite.py, and tests/test_compatibility_suite.py; focused syntax and unit checks passed.

  2895. Recorded retrospectively from the current menu-gate implementation pass: Ran the dry-run menu-gate fixture build and v3 compatibility-suite dry-run.

    Action/result: Confirmed the fixture cases and the named gr_menu_gate_qemu suite command were wired into the manifest.

  2896. Recorded retrospectively from the current menu-gate implementation pass: Ran the first Enter-driven QEMU oracle.

    Action/result: The enabled and disabled cases both matched the blocked marker control, so the initial visual oracle failed to prove the gate.

  2897. Recorded retrospectively from the current menu-gate implementation pass: Rechecked the disassembly and generated logic bytes after the first QEMU failure.

    Action/result: The source model and 0xb1 byte encoding were still correct; the failure pointed to fixture scheduling rather than a decoded handler mistake.

  2898. Recorded retrospectively from the current menu-gate implementation pass: Ran a longer-wait QEMU diagnostic and inspected why the request was never serviced.

    Action/result: Found that ending the generated logic with self_loop() kept logic 0 inside the interpreter, preventing the main cycle from reaching the [0x1b67] request check.

  2899. Recorded retrospectively from the current menu-gate implementation pass: Replaced the generated self-loop with the structural end action and reran QEMU.

    Action/result: The fixture now yielded back to the main cycle, but the Enter-driven accepted-marker oracle was still not reliable enough to promote.

  2900. Recorded retrospectively from the current menu-gate implementation pass: Refactored the probe into a no-key modal-gate oracle.

    Action/result: The final comparisons became: disabled request must match the blocked control, enabled request must differ from the blocked control, and enabled request must differ from the disabled request.

  2901. Recorded retrospectively from the current menu-gate implementation pass: Ran the direct QEMU confirmation build/gr-v3-behavior/menu_gate_qemu_003.json.

    Action/result: The report passed. Blocked control and disabled request shared hash 160a4ed1bab5ec6eb901ae2c5e3198a081000c0261cf6ad89eec4033e88861b4; enabled request had hash e463cb17d86267bda970277df82d51c6b51dc743327f51c856a25de65399155b.

  2902. Recorded retrospectively from the current menu-gate implementation pass: Ran the named compatibility-suite command gr_menu_gate_qemu.

    Action/result: The first unprivileged run failed before launch with VNC socket permission, then the escalated rerun passed in build/compatibility-suite/qemu_v3_menu_gate_001.json.

  2903. Reported: “The documentation is now aligned with the promoted result. I’m going to run the focused checks first, then the full local suite and mdBook build; after that I’ll update any test-count line that needs changing.”

    Action/result: Focused py_compile checks passed, and AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suite passed 30 tests.

  2904. Reported: “Focused syntax and unit checks are clean. I’m running the full local suite and mdBook now; this will also tell me whether the tracker’s ‘282 tests’ line needs to become ‘285 tests’ after the added menu-gate cases.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 285 tests, and mdbook build docs passed.

  2905. Reported: “Full unit discovery passed 285 tests and mdBook built. I’m updating the tracker count, then I’ll run the local compatibility wrapper and final whitespace/status checks.”

    Action/result: Updated PROGRESS.md from 282 to 285 tests.

  2906. Reported: “The count is current. I’m running the local compatibility wrapper now; it’s the project’s normal ‘unit tests + mdBook + opcode evidence’ belt-and-suspenders check without launching QEMU.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_menu_gate_001.json passed.

  2907. Reported: “The local compatibility wrapper passed with 285 tests and a clean mdBook build. I’m checking the report, whitespace, and generated cache clutter now.”

    Action/result: The compatibility report showed zero return codes for local unit tests, mdBook, and opcode-evidence freshness. git diff --check passed. tests/__pycache__ and tools/__pycache__ were the only generated cache directories.

  2908. Reported: “Whitespace is clean and the compatibility report has all zero return codes. The only generated clutter is Python cache directories, so I’m removing those before the final status.”

    Action/result: Removed tests/__pycache__ and tools/__pycache__.

  2909. Reported: “One stale sentence jumped out in PROGRESS.md: that GR/SQ2 section still says QEMU confirmation is deferred, which is no longer true for the promoted v3 probes. I’m correcting that and doing one tiny final check.”

    Action/result: Updated the GR/SQ2 tracker preface to say QEMU confirmation is added after source mapping justifies a targeted probe.

  2910. Reported: “I’ll continue from the updated tracker and leave the uncommitted menu-gate work intact. The next obvious source-backed gap is the GR v3 raw key-release gate, so I’ll start by rereading the disassembly and existing input/event probes before deciding whether it deserves a QEMU fixture.”

    Action/result: Read current status, PROGRESS.md highest-value work, key-release references, and existing input/key probe helpers.

  2911. Reported: “The existing docs already describe the byte gate, but they still avoid the exact release-event observable behavior because timing is touchy. I’m going source-first here: the question is whether disassembly gives enough detail to model the event shape without needing a brittle keyboard fixture.”

    Action/result: Re-read SQ2 and GR keyboard IRQ disassembly windows plus current opcode/runtime/compatibility notes for the release-gate path.

  2912. Reported: “The GR and SQ2 IRQ paths are nearly identical after relocation: both filter scan codes 0x47..0x51, require an enable byte, set a per-scan pressed latch on keydown, and enqueue (type=2, value=0) on release only when the gate is nonzero. The real v3 difference is the gate writer: SQ2 increments [0x1530]; GR sets/clears [0x0405] with 0xad/0xb5.”

    Action/result: Identified the portable tracked-key latch state machine and the version-specific gate-writer differences.

  2913. Reported: “I’m going to turn this source-only IRQ path into a small local model with tests rather than a QEMU screenshot probe. That captures the exact latch/gate semantics, including SQ2’s byte-increment wraparound, without pretending QEMU key-release timing is a stable compatibility oracle.”

    Action/result: Added tools/agi_input.py with KeyReleaseIrqState, SQ2/GR gate helpers, and process_tracked_key_irq_scan(); added tests/test_input_model.py.

  2914. Reported: “The model is in place. I’m running it now because there’s a small but important edge in there: SQ2’s gate is an 8-bit increment, so repeated 0xad can wrap back to zero, while GR’s v3 gate is explicitly set/clear.”

    Action/result: Focused py_compile and tests.test_input_model passed.

  2915. Reported: “The new model tests pass. I’m updating the evidence strings and docs next so 0xad/0xb5 read as a modeled IRQ latch contract, not just a loose ‘key release gate’ note.”

    Action/result: Read current opcode evidence, bytecode, runtime, versions, and tracker text before patching.

  2916. Reported: “I’ve got enough to promote this as source-modeled behavior. I’m updating the spec/tracker to remove raw key-release from the ‘highest-value remaining’ bucket, while keeping the caveat that a direct QEMU release fixture is still optional and not the source of truth.”

    Action/result: Updated tools/logic_opcode_evidence.py, docs/src/logic_bytecode.md, docs/src/runtime_model.md, docs/src/versions.md, and PROGRESS.md.

  2917. Reported: “The progress tracker now treats the release-gate work as source-modeled rather than a pending QEMU target. I’m updating labels and compatibility notes so future readers can jump straight to the helper and tests.”

    Action/result: First symbolic-label patch missed current wording; reread exact rows, then updated docs/src/symbolic_labels.md and docs/src/compatibility_testing.md.

  2918. Reported: “I’m appending a clean-room evidence note for this pass now. It’s short but important: the useful new fact is not a screenshot, it’s a portable state-machine contract extracted from the IRQ code.”

    Action/result: Appended the tracked key-release IRQ source-model evidence section to docs/src/clean_room_executable_notes.md.

  2919. Reported: “Docs and labels now point to the model. I’m regenerating the opcode evidence chapter so the generated table stays consistent with the updated 0xad evidence text.”

    Action/result: First generator run refused to assume a game directory.

  2920. Reported: “The generator correctly refused to assume a default game directory. I’m rerunning it with the explicit local SQ2 evidence input.”

    Action/result: Regenerated docs/src/logic_opcode_evidence.md with AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py.

  2921. Reported: “Opcode evidence regenerated cleanly with AGI_GAME_DIR=games/SQ2. I’m doing the focused checks and then I’ll update the final tracker count if the new six tests move the suite total as expected.”

    Action/result: Focused py_compile passed; the first doc-coverage test command failed because it lacked AGI_GAME_DIR.

  2922. Reported: “One focused test command failed for the same explicit-input reason: the doc coverage test invokes the opcode evidence checker. I’m rerunning that with AGI_GAME_DIR=games/SQ2.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_input_model tests.test_logic_doc_coverage passed 11 tests.

  2923. Reported: “Focused source-model and doc-coverage tests are clean. I’m adding the input-model test to the subsystem tracker now; the final suite run will set the exact total count.”

    Action/result: Updated the text/input subsystem tracker in PROGRESS.md.

  2924. Reported: “The log is current through the source-model pass. I’m moving into the full validation cycle now: unit discovery, mdBook, opcode evidence check, and whitespace/status cleanup.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 291 tests; mdbook build docs passed; AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check passed.

  2925. Reported: “Full unit discovery passed 291 tests, mdBook built, and opcode evidence is fresh. I’m updating the tracker count, then I’ll run the local compatibility wrapper once more.”

    Action/result: Updated PROGRESS.md from 285 to 291 tests.

  2926. Reported: “The tracker count is current. The compatibility wrapper is next; that redoes unit tests, mdBook, and opcode-evidence freshness from the suite manifest.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_input_model_001.json passed.

  2927. Reported: “The local compatibility wrapper passed with 291 tests. I’m checking its JSON result, whitespace, status, and generated cache directories before the final note.”

    Action/result: The compatibility report showed zero return codes for local unit tests, mdBook, and opcode-evidence freshness. git diff --check passed. tests/__pycache__ and tools/__pycache__ were the only generated cache directories.

  2928. Reported: “Validation is clean; only Python cache directories were generated. I’m removing those, then I’ll do one last mdBook/whitespace/status pass after adding the final log entries.”

    Action/result: Removed tests/__pycache__ and tools/__pycache__.

  2929. Reported: “I’m picking back up from the v3 fixture-packing work: the code path is already sketched in, so I’m going to finish the documentation/tracker trail, then run the broader checks so the new GR-style fixture support is grounded rather than just “looks plausible.””

    Action/result: Rechecked working-tree state and the current PROGRESS.md remaining-work section.

  2930. Reported: “The tree has both the earlier interpreter/input-model changes and the newer v3 fixture changes. I’m leaving the unrelated run.sh alone and treating the current pass as additive: make the v3 fixture capability explicit in the docs, then verify it doesn’t disturb the broader suite.”

    Action/result: Located stale documentation and tracker references that still described v3 fixtures as logic-only.

  2931. Reported: “I found the stale spots: they still say v3 fixtures can only patch direct logic records. I’m going to replace that with the narrower, source-backed statement we can actually defend now: direct v3 logic/view records plus v3 picture-nibble picture records in copied GR-style fixtures.”

    Action/result: Read the resource, version, and current progress-log sections that needed updates.

  2932. Reported: “I’m checking the exact helper shape before writing the evidence note. One thing that matters for the spec is that this still does not invent GR’s dictionary compressor; it deliberately uses the interpreter’s already-observed direct-record path for logic/view and the observed picture-nibble path for pictures.”

    Action/result: Confirmed the new helper/API shape in tools/qemu_fixture.py and tools/agi_resources.py.

  2933. Reported: “I’m making the doc edits now. I’ll keep them factual and scoped: this is a fixture-writing capability, not a claim that we have implemented every v3 compression path or QEMU-validated a new graphics behavior yet.”

    Action/result: Updated PROGRESS.md so v3 picture/view fixture packing is no longer listed as missing work.

  2934. Reported: “The high-level tracker now treats v3 picture/view fixture packing as available tooling, with the remaining work reframed around using it for source-backed probes rather than building it from scratch.”

    Action/result: Updated AGENTS.md with the v3-synthetic-picture and v3-synthetic-picture-view commands and the direct-record caveat.

  2935. Reported: “I’ve also updated the agent instructions so future runs don’t rediscover the old limitation. Next I’m bringing the mdBook resource and version chapters into sync with the new fixture commands.”

    Action/result: Updated docs/src/resource_files.md with picture-nibble encoding and v3 copied-fixture patching evidence.

  2936. Reported: “The resource chapter now records the encoder and fixture-writer boundary. I’m updating the Versions chapter next, because that’s the most likely place future “compare another interpreter” work will look first.”

    Action/result: Updated docs/src/versions.md with generated v3 picture/view fixture commands and compatibility notes.

  2937. Reported: “The Versions chapter now has the new commands and the important caveat: direct records for controlled generated data, dictionary decoding for original resources. I’m adding the clean-room evidence note and progress-log entries before the validation pass.”

    Action/result: Added the v3 generated picture/view fixture-packing section to docs/src/clean_room_executable_notes.md.

  2938. Reported: “The clean-room notes now have the evidence chain for this pass. I’m adding the step-by-step progress log entries, then I’ll run the full checks.”

    Action/result: Added this progress-log block through the documentation update phase.

  2939. Reported: “The docs are now caught up with the implementation. I’m starting validation from the narrow end first, then I’ll widen to full unit discovery, mdBook, opcode-evidence freshness, and the compatibility wrapper.”

    Action/result: python3 -B -m py_compile tools/agi_resources.py tools/qemu_fixture.py tests/test_agi_resources.py tests/test_qemu_fixture.py passed, and AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_agi_resources tests.test_qemu_fixture passed 59 tests.

  2940. Reported: “Focused validation is clean: the new resource and fixture tests pass. I’m running the whole local suite now so we catch any interaction with the existing opcode/resource tooling.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 298 tests.

  2941. Reported: “Full unit discovery passed 298 tests, which matches the expected bump from the new fixture/resource tests. I’m updating the tracker count, then I’ll build the book and run the generated evidence checks.”

    Action/result: Updated PROGRESS.md from 291 to 298 tests.

  2942. Reported: “There’s one current tracker count to bump from 291 to 298; the older progress-log mentions are historical and I’m leaving those as-is.”

    Action/result: Confirmed only the current tracker count was changed.

  2943. Reported: “The tracker count is current. I’m running mdBook and the opcode evidence freshness check in parallel, then the compatibility wrapper will re-run the core local checks as a single manifest.”

    Action/result: mdbook build docs passed and AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check passed.

  2944. Reported: “mdBook and the opcode evidence check passed. I’m running the local compatibility wrapper now; that gives us one report file tying the unit/book/evidence checks together.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_v3_fixture_packing_001.json passed with zero return codes for local unit tests, mdBook, and opcode-evidence freshness.

  2945. Reported: “The compatibility wrapper passed with 298 tests and rebuilt the book. I’m checking the report JSON and whitespace/status now, then I’ll clean any generated Python caches.”

    Action/result: The compatibility report showed zero return codes, git diff --check passed, and tests/__pycache__ plus tools/__pycache__ were the only generated cache directories.

  2946. Reported: “The report is clean and git diff --check passed. The only generated clutter is Python cache directories, so I’m removing those before the final status pass.”

    Action/result: Removed tests/__pycache__ and tools/__pycache__.

  2947. Reported: “Caches are gone. I’m adding the validation results to the progress log now, then I’ll do one last mdBook/whitespace/status sweep because the log itself changed after the previous checks.”

    Action/result: Added this validation progress-log block.

  2948. Reported: “The validation log is recorded. I’m doing the last sweep now: rebuild the book after the log edit, check whitespace, confirm caches stayed gone, and summarize the remaining worktree cleanly.”

    Action/result: mdbook build docs passed, git diff --check passed, no __pycache__ directories remained under tests/ or tools/, and git status --short showed the expected modified/untracked files.

  2949. Reported: “Final sweep is clean: mdBook builds, whitespace check passes, and cache dirs are gone. I’m reviewing the diff shape now so the handoff is accurate and doesn’t blur this v3 fixture work with the earlier uncommitted interpreter/input-model changes.”

    Action/result: Reviewed git diff --stat and the focused v3 resource, fixture, test, and documentation diffs.

  2950. Reported: “The diff review confirms the newest slice is the v3 fixture packing plus docs; the larger tree also contains the earlier uncommitted menu-gate and input-model work from this batch. I’m logging these last two status updates, then I’ll rerun the tiny final checks one more time.”

    Action/result: Added this final progress-log entry before the last mdBook/whitespace/status check.

  2951. Reported: “I’ll continue from the freshly finished v3 fixture packing. I’m going to re-read the current tracker and then pick the next highest-value item, keeping the existing uncommitted work intact and logging the new steps as I go.”

    Action/result: Re-read git status --short and the current PROGRESS.md tracker sections.

  2952. Reported: “The tracker points to one useful bridge before more renderer work: the v3 writer is locally tested, but not yet proven against the original GR engine. I’m going to inspect the existing GR probe/QEMU helpers and, if it fits cleanly, add a small QEMU smoke that draws a synthetic v3 picture/view fixture produced by the new writer.”

    Action/result: Read the GR behavior probe, existing tests, and graphics helpers.

  2953. Reported: “The existing GR probe is a good fit: it already builds copied fixtures, runs QEMU cases, hashes captures, and compares controls. I’m reading the runner/argument layer now so the new smoke can be plugged in as one more probe instead of becoming a one-off script.”

    Action/result: Read the QEMU case runner, GR probe CLI, compatibility manifest, and transient view/action references.

  2954. Reported: “I have the runner shape now. The new probe can be deliberately modest: build one all-synthetic v3 picture/view fixture, run it under the original GR interpreter, and assert the capture is nonblank plus distinct from an end-only blank control.”

    Action/result: Inspected picture_view_logic_payload, existing transient-object tests, and the new v3 synthetic fixture tests.

  2955. Reported: “This will also exercise the freshly added v3 picture-nibble encoder under the real interpreter, which is exactly the confidence gap left by the last pass. Before editing, I’m checking the low-level picture/view payload builders so the synthetic resources stay tiny and valid.”

    Action/result: Confirmed the probe should use a tiny picture fill and a one-loop one-frame direct view payload.

  2956. Reported: “The probe will use three captures: blank control, synthetic picture-only, synthetic picture+view. That gives a compact oracle: the v3 picture-nibble record must draw something, and the direct v3 view record must visibly change the picture capture.”

    Action/result: Reviewed compatibility-suite tests and manifest placement before editing.

  2957. Reported: “I’m editing the GR probe now. The important design choice is to keep this as a compatibility check for the fixture format, not as new picture/view semantic evidence: the source-backed semantics are already the v3 reader paths; QEMU will just prove the original engine accepts the generated records and renders them.”

    Action/result: Added generated picture/view constants to tools/gr_v3_behavior_probe.py.

  2958. Reported: “The synthetic payloads are intentionally tiny: picture fill through the v3 nibble path, and a 4x4 opaque view frame stored as a direct v3 view record. Next I’m adding fixture construction and capture comparison helpers.”

    Action/result: Added build_gr_synthetic_picture_view_fixtures().

  2959. Reported: “Fixture construction is in place. I’m adding the QEMU result reducer now, so the report says exactly what changed instead of just “the command exited.””

    Action/result: Added pixel-difference and unique-color report helpers plus run_gr_synthetic_picture_view_qemu().

  2960. Reported: “The reducer compares pixels between blank, picture-only, and picture+view captures. I’m wiring the new probe into the CLI and then I’ll add unit coverage around the generated records.”

    Action/result: Added CLI probe --probe synthetic-picture-view.

  2961. Reported: “The CLI now has --probe synthetic-picture-view. I’m updating the local tests so fixture packing, directory patching, and the new compatibility manifest entry are pinned before any QEMU run.”

    Action/result: Added local GR fixture read-back test coverage.

  2962. Reported: “The GR unit test now reads the generated v3 records back through the parser, which is a nice little loop: writer output must be accepted by the same resource reader model. I’m adding the new QEMU probe to the opt-in compatibility manifest next.”

    Action/result: Added compatibility-suite command gr_synthetic_picture_view_qemu.

  2963. Reported: “The manifest entry is in the v3 opt-in layer, so it won’t slow the default local suite but can be run by name or with --include-qemu-v3. I’m pinning that in the manifest tests.”

    Action/result: Updated tests/test_compatibility_suite.py to include the new qemu-v3 command.

  2964. Reported: “The code and tests are patched. I’m running focused validation now before touching docs, so any syntax or fixture-model mistake gets caught while the change is still small.”

    Action/result: Focused py_compile passed and AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suite passed 31 tests.

  2965. Reported: “Focused local tests pass. I’m going to run the new probe without QEMU first against games/GR; that will build the copied fixtures and produce a report without launching the emulator.”

    Action/result: Fixture-only report build/gr-v3-behavior/synthetic_picture_view_001.json was generated.

  2966. Reported: “The fixture-only run succeeded, so the actual local GR directory layout accepts the generated entries. I’m checking the report and then I’ll run the QEMU confirmation by compatibility-suite name.”

    Action/result: Inspected the fixture-only report and generated fixture file list.

  2967. Reported: “The first QEMU attempt failed before the guest launched because sandboxed QEMU couldn’t bind the local VNC socket. That’s the same permission failure we’ve seen before, so I’m rerunning the exact named compatibility command with escalation.”

    Action/result: Reran AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_synthetic_picture_view_qemu --report build/compatibility-suite/qemu_v3_synthetic_picture_view_001.json with elevated execution.

  2968. Reported: “QEMU is running now under the compatibility wrapper. I’m waiting for the snapshot cases to finish and will inspect the JSON/captures when it returns.”

    Action/result: The QEMU compatibility command completed successfully.

  2969. Reported: “The QEMU run completed. I’m checking the probe report now to make sure both expected differences are present and to capture the exact evidence values for the docs.”

    Action/result: Confirmed build/gr-v3-behavior/synthetic_picture_view_suite.json passed: picture-only differed from blank by 215,040 pixels, picture-plus-view differed from picture-only by 128 pixels, and unique color counts were 1, 2, and 3.

  2970. Reported: “The QEMU probe passed. The useful numbers: picture-only differs from blank by 215,040 pixels, picture+view differs from picture-only by 128 pixels, and color counts go 1 → 2 → 3. I’m updating the docs/tracker with that evidence now.”

    Action/result: Located the relevant AGENTS, tracker, resource, version, compatibility, and clean-room evidence sections.

  2971. Reported: “I’ve got the relevant documentation locations. I’m adding the new probe command and its result to the operational docs, then I’ll update the high-level tracker and evidence log.”

    Action/result: Added the reusable synthetic picture/view QEMU command to AGENTS.md.

  2972. Reported: “The reusable command is now in AGENTS.md. I’m updating the tracker to show that v3 generated picture/view fixtures are no longer just locally readable; they’ve been exercised by the original GR engine.”

    Action/result: First PROGRESS.md patch missed due paragraph wrapping.

  2973. Reported: “One tracker patch missed because that paragraph has shifted wrapping. I’m reading the exact current lines and applying it in smaller chunks.”

    Action/result: Re-read the current PROGRESS.md paragraphs.

  2974. Reported: “I found the exact current paragraphs. Applying the tracker updates as focused replacements now.”

    Action/result: Updated PROGRESS.md with the QEMU-validated generated v3 fixture path.

  2975. Reported: “The tracker now records the QEMU result but I’ll leave the final unit-test count alone until the full suite rerun confirms it. Next I’m updating the resource/version/compatibility chapters with the exact probe outcome.”

    Action/result: Updated docs/src/resource_files.md with the QEMU result and pixel counts.

  2976. Reported: “The resource chapter now has the direct QEMU evidence numbers. I’m updating the version ledger so future interpreter comparisons know this is validated tooling, not merely a parser round trip.”

    Action/result: Updated docs/src/versions.md with the promoted synthetic picture/view QEMU probe.

  2977. Reported: “The version ledger is updated. I’m adding a short compatibility-testing section beside the other GR v3 probes, including the command and the promoted pass criteria.”

    Action/result: Updated docs/src/compatibility_testing.md.

  2978. Reported: “The compatibility chapter now explains what this probe proves and what it doesn’t. I’m appending the clean-room evidence note with the source/model, commands, and QEMU result.”

    Action/result: Added the v3 synthetic picture/view fixture QEMU probe section to docs/src/clean_room_executable_notes.md.

  2979. Reported: “The clean-room note is in place. I’m logging the work-in-progress updates now, then I’ll run the full validation cycle and update the test count if the suite total changes.”

    Action/result: Added this progress-log block through the documentation update phase.

  2980. Reported: “The progress log is current through the docs pass. I’m running full validation now: unit discovery, mdBook, opcode-evidence freshness, and the local compatibility wrapper.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 299 tests; mdbook build docs passed; and AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check passed.

  2981. Reported: “Full unit discovery passed 299 tests and the book/evidence checks are clean. I’m bumping the tracker count from 298 to 299, then I’ll run the local compatibility wrapper.”

    Action/result: Found only the current PROGRESS.md count needed updating; older progress-log mentions remained historical.

  2982. Reported: “Only the current tracker count needs updating; the progress-log references are historical.”

    Action/result: Updated PROGRESS.md from 298 to 299 tests.

  2983. Reported: “The tracker count is updated. I’m running the local compatibility wrapper now, which redoes the local unit suite, mdBook, and opcode-evidence freshness from the manifest.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_v3_synthetic_picture_view_001.json passed.

  2984. Reported: “The local compatibility wrapper passed with 299 tests and rebuilt the book. I’m checking the reports, whitespace, and generated caches now.”

    Action/result: The local compatibility report showed zero return codes, git diff --check passed, and tests/__pycache__ plus tools/__pycache__ were the only generated cache directories.

  2985. Reported: “The report and whitespace check are clean; only Python cache directories were generated. I’m removing those, then I’ll add the final validation log entries and do one last quick sweep.”

    Action/result: Removed tests/__pycache__ and tools/__pycache__.

  2986. Reported: “Caches are removed. I’m recording the validation results in the progress log now, then I’ll rebuild/check once more after that final log edit.”

    Action/result: Added this validation progress-log block.

  2987. Reported: “The validation log is recorded. I’m doing the final quick sweep now: mdBook after the log edit, whitespace, cache check, and status.”

    Action/result: mdbook build docs passed, git diff --check passed, no __pycache__ directories remained under tests/ or tools/, and git status --short showed the expected modified/untracked files.

  2988. Reported: “The final sweep is clean: mdBook builds, whitespace passes, and cache dirs are gone. I’m adding that last sweep result to the log and doing one final status-only check after it.”

    Action/result: Added this final progress-log entry.

  2989. Reported: “I’ll pick up from the clean commit…”

    Action/result: Rechecked repository state and PROGRESS.md; only the unrelated untracked run.sh was present after the previous commit.

  2990. Reported: “The tracker is clean after the commit…”

    Action/result: Selected the sound/audio tracker row as the next useful implementation-spec target and began inspecting the relevant docs, tests, and helper scripts.

  2991. Reported: “There isn’t a sound-specific chapter…”

    Action/result: Confirmed tools/agi_sound.py and tests/test_sound_resources.py already model the source-backed sound resource format, scheduling, port-output boundary, and envelope behavior, while the mdBook lacked a dedicated sound/audio chapter.

  2992. Reported: “I found the next useful gap: the project has source-backed sound parsing and driver evidence, but it’s scattered across runtime notes, tests, and labels. I’m going to turn that into a dedicated implementation-oriented docs chapter and wire it into the book/tracker.”

    Action/result: Read the summary, runtime model sound section, symbolic sound labels, sound tests, and sound helper to gather the contract points for a new chapter.

  2993. Reported: “The sound helper/tests are nicely concrete: they already encode the channel record format, countdown semantics, PC-speaker divisor, and non-PC port byte behavior. I’m going to keep the new chapter at the ‘observable interpreter contract’ level, with the port writes as the hardware boundary and analog synthesis marked out of scope.”

    Action/result: Chose the chapter scope: resource parsing, load/start/stop semantics, tick scheduling, completion flags, tone/attenuation output, and explicit deferral of analog waveform synthesis.

  2994. Reported: “The current status file already calls out analog sound as deferred, so the new chapter should make that explicit rather than accidentally widening the target. I’m patching docs now: summary entry, sound chapter, progress log, clean-room notes, and tracker wording.”

    Action/result: Added docs/src/sound_and_audio.md, linked it from docs/src/SUMMARY.md, updated PROGRESS.md and docs/src/current_status.md, and added this clean-room evidence note.

  2995. Reported: “The edit is mostly consolidation, not new claims. I’m anchoring each rule to an existing local artifact: tools/agi_sound.py, tests/test_sound_resources.py, the symbolic labels, and the prior SQ2 corpus observations.”

    Action/result: Completed the sound/audio documentation patch and prepared to validate the mdBook and focused sound tests.

  2996. Reported: “The new chapter is in place. I’m validating it now with the focused sound tests, mdBook build, and whitespace checks, then I’ll clean up any generated Python caches.”

    Action/result: mdbook build docs passed and git diff --check passed. The first focused sound test run failed because the explicit AGI_GAME_DIR/--game-dir requirement was not supplied.

  2997. Reported: “The first focused test run reminded me of our newer no-default-game rule: it failed fast because AGI_GAME_DIR was not set. I’m rerunning it with the explicit local SQ2 evidence directory, which is the right shape for these legacy SQ2 corpus tests.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_sound_resources passed 16 tests. No __pycache__ directories were present under tests/ or tools/.

  2998. Reported: “The explicit SQ2 sound tests pass: 16 tests in the focused suite. I’m adding the validation notes, then I’ll rebuild the book once more after that final docs edit.”

    Action/result: Added validation details to docs/src/clean_room_executable_notes.md and this progress-log block.

  2999. Reported: “The validation trail is recorded. I’m doing the final quick sweep after those log edits: rebuild mdBook, whitespace check, cache check, and status.”

    Action/result: mdbook build docs passed, git diff --check passed, no Python __pycache__ directories were present under tests/ or tools/, and git status --short showed the expected documentation changes plus the unrelated untracked run.sh.

  3000. Reported: “The final sweep is clean: mdBook and whitespace pass, no Python caches, and the only untracked non-project file remains run.sh. I’m going to log that and then move to the next PROGRESS.md item instead of stopping at the sound chapter.”

    Action/result: Recorded the sound/audio final sweep and continued toward the next high-value tracker item.

  3001. Reported: “Next I’m re-reading the high-value list and the v3/version notes to choose a source-first task. I’ll prefer something that strengthens the cross-version spec without needing a QEMU run unless the disassembly leaves a behavior ambiguous.”

    Action/result: Re-read PROGRESS.md, docs/src/versions.md, and docs/src/cross_version_workflow.md.

  3002. Reported: “The v3 notes are currently concentrated in the Versions chapter, while Resource Files still begins as an SQ2-only chapter. A portable implementer will need the v2/v3 container contract in the resource chapter too, so I’m checking whether that chapter already covers the v3 combined directory and transformed records before adding anything.”

    Action/result: Confirmed docs/src/resource_files.md already contains a detailed Gold Rush / AGI v3 container comparison, including combined directory sections, 7-byte volume headers, dictionary expansion, and picture-nibble expansion.

  3003. Reported: “Resource Files already has a solid v3 container section, so I won’t duplicate it. Since you’ve added more local games, the next useful cross-version step is a read-only census tool/report that can summarize each game’s layout, version string, resource counts, and transform mix without hard-coding SQ2 or GR.”

    Action/result: Inspected the local games/ directory and found PQ1, KQ1, KQ2, KQ3, KQ4D, LSL1, SQ2, and GR.

  3004. Reported: “The local games/ directory now has several interpreters: PQ1, KQ1-3, KQ4D, LSL1, SQ2, and GR. I’m going to add a small read-only census script that requires explicit paths, so future comparison passes can start from a reproducible inventory instead of ad hoc shell scans.”

    Action/result: Reviewed shared path handling and existing tests, then designed tools/game_census.py around explicit --game-dir or --games-root inputs.

  3005. Reported: “I’m adding this as reusable infrastructure, not just a one-off report: repeated --game-dir for selected inputs, or explicit --games-root for a first-level scan. The tool will emit JSON or Markdown and continue past individual bad records so malformed/out-of- range entries are evidence, not fatal surprises.”

    Action/result: Added tools/game_census.py and synthetic unit tests in tests/test_game_census.py.

  3006. Reported: “The census script and synthetic tests are added. Before using it on the private games/ tree, I’m running the isolated tests and a compile check so the tool’s behavior is pinned down without depending on any copyrighted local input.”

    Action/result: python3 -B -m py_compile tools/game_census.py tests/test_game_census.py passed, and python3 -B -m unittest tests.test_game_census passed 5 tests.

  3007. Reported: “The isolated census tests pass. I noticed two imports that the first draft doesn’t need, so I’m cleaning those before running the tool against the local game directories.”

    Action/result: Removed unused imports from tools/game_census.py.

  3008. Reported: “Now I’m running the census on the explicit local games/ root and writing reports under build/, which keeps the private input-derived report out of source while still giving us concrete observations for the docs.”

    Action/result: Wrote build/cross-version/game_census.json and build/cross-version/game_census.md.

  3009. Reported: “The read-only census completed cleanly. I’m inspecting the generated summary now for anything surprising, especially layout/version spread and record errors that would affect the next comparison pass.”

    Action/result: Inspected the generated Markdown/JSON census and identified six split-directory inputs plus two v3 combined-directory inputs.

  3010. Reported: “The census surfaced useful evidence: six split-directory games plus two v3 combined-directory inputs, and a few record-header errors in KQ1, KQ4D, and SQ2. I’m looking at those errors closely before deciding whether they are malformed data, unsupported container variants, or just directory sentinels our generic parser should treat more carefully.”

    Action/result: Compared error details for KQ1, KQ4D, and SQ2. SQ2’s two errors are the already known out-of-range end entries; KQ1 and KQ4D sound entries need later source inspection before being modeled.

  3011. Reported: “The error rows are themselves useful: SQ2’s two are the already-known out-of-range end entries, while KQ1/KQ4D need later source checks before we treat their suspect sound entries as valid resources. I’m documenting the census tool and the current local findings as a starting point for future version passes, without making those errors part of the portable spec.”

    Action/result: Documented the census workflow and snapshot in docs/src/cross_version_workflow.md, docs/src/versions.md, PROGRESS.md, docs/src/current_status.md, and docs/src/clean_room_executable_notes.md.

  3012. Reported: “The first doc patch missed because PROGRESS.md wording had shifted. I’m applying the same content in smaller pieces against the current text.”

    Action/result: Added the census workflow to docs/src/cross_version_workflow.md and the local census snapshot to docs/src/versions.md.

  3013. Reported: “The workflow and Versions docs now point at the census tool. I’m updating the progress tracker next so this infrastructure shows up in the dashboard rather than being buried in prose.”

    Action/result: Updated PROGRESS.md to mention the multi-game census in the current snapshot, symbolic-map row, and resource-loading row.

  3014. Reported: “The tracker now reflects the census work. I’m adding the clean-room evidence trail and Useful Files pointer, then I’ll run validation over the new tool and docs.”

    Action/result: Added tools/game_census.py to Useful Files and recorded the census work in docs/src/clean_room_executable_notes.md.

  3015. Reported: “One quick correction in the log: I typed ‘seven split-directory’ in one entry, but the census shows six split-directory inputs and two v3 combined inputs. I’m fixing that before validation so the historical log is clean.”

    Action/result: Corrected the progress-log census count.

  3016. Reported: “The census docs are in. I’m validating the new tool and resource parser tests together, plus mdBook and whitespace, because this touched both tooling and the cross-version chapters.”

    Action/result: python3 -B -m py_compile tools/game_census.py tests/test_game_census.py passed; python3 -B -m unittest tests.test_game_census tests.test_agi_resources passed 13 tests; mdbook build docs passed; and git diff --check passed.

  3017. Reported: “Focused validation passed: 13 tests covering the new census tool plus resource parser, and mdBook/whitespace are clean. I’m running the full local unittest discovery once too, since this added a new test file that should fit the normal suite.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 304 tests and generated tests/__pycache__ plus tools/__pycache__.

  3018. Reported: “Full local discovery passed 304 tests, and it generated the usual Python cache directories. I’m removing only those generated caches, then I’ll update the test count and final validation notes.”

    Action/result: Removed tests/__pycache__ and tools/__pycache__.

  3019. Reported: “Full local discovery is up to 304 tests now and caches are removed. I’m finding the current tracker count so I can update only the live dashboard, leaving the older progress-log counts as historical entries.”

    Action/result: Found the live count in the compatibility-suite tracker row.

  3020. Reported: “The live test-count line is in the compatibility-suite row, currently 299. I’m bumping that to 304 and recording the validation commands/results for the census pass.”

    Action/result: Updated PROGRESS.md to 304 tests and added validation details to docs/src/clean_room_executable_notes.md.

  3021. Reported: “The final sweep is clean, with run.sh still the only unrelated untracked file. The census exposed a concrete next source-first question, so I’m going one level deeper on KQ4D’s v3 directory: why the sound section has many present-looking entries that fail the generic v3 record header check.”

    Action/result: Inspected KQ4D DMDIR, DMVOL.*, and executable file types. The sound section starts at 0x02d5 and begins with many ff ff ff absent entries.

  3022. Reported: “I’m checking the raw KQ4D directory and the referenced volume offsets first. If the bad entries are just filler/trailing data, that should show up before I spend time mapping the executable routine.”

    Action/result: Decoded KQ4D sound entries. Clean records appear at sound indices 70..79; later present-looking triples generally point inside non-header volume data.

  3023. Reported: “The sound section starts with a long block of ff ff ff, then real-looking entries. That makes the generic exact-ff ff ff absent rule plausible, but the later bad entries are not obviously just the initial empty range, so I’m decoding the sound section indices and target headers next.”

    Action/result: Printed present-like KQ4D sound entries and confirmed the clean 12 34 headers at indices 70..79.

  3024. Reported: “The target dump is telling: KQ4D has ten clean sound records at indices 70-79, then later present-looking triples that mostly land inside compressed data rather than at 12 34 headers. I’m checking decoded KQ4D logic references now, because if scripts never request those later sound numbers, they are directory tail/data artifacts rather than valid-resource semantics.”

    Action/result: Initial disassemble_logic.py calls used nonexistent flags or the old GR-specific v3 table bases, revealing that KQ4D needed per-build dispatch-table detection.

  3025. Reported: “I guessed nonexistent flags on disassemble_logic.py; it takes explicit logic numbers plus --stats. I’m checking its CLI and then I’ll use the supported path.”

    Action/result: Confirmed the supported CLI and reran with AGI_GAME_DIR=games/KQ4D, still exposing wrong table data from the hard-coded GR v3 bases.

  3026. Reported: “The script consumes --game-dir before its own parser, so help itself needs AGI_GAME_DIR set. I’m using --stats now, then I’ll narrow to sound-related opcodes.”

    Action/result: Verified KQ4D stats were incoherent before the fix, with bogus handler/argc/meta values.

  3027. Reported: “KQ4D also exposed a tooling gap: disassemble_logic.py hard-codes GR’s v3 dispatch-table offsets, so KQ4D --stats is decoding with the wrong table. I’m going to inspect the dispatch-table signatures and see if we can infer v3 table offsets per game instead of treating GR as the only v3 layout.”

    Action/result: Compared SQ2, GR, and KQ4D AGIDATA bytes and found exact action/condition argc/meta signatures at SQ2 0x061d/0x08fd, GR 0x0440/0x0762, and KQ4D 0x0620/0x0942.

  3028. Reported: “Good, KQ4D’s action table scores perfectly at AGIDATA.OVL:0x0620 and its condition table at 0x0942; GR’s are at 0x0440/0x0762. I’m going to replace the v3 hard-code with a signature scan so KQ4D and future local v3 builds can disassemble with their own table bases.”

    Action/result: Patched tools/disassemble_logic.py with signature-based dispatch table detection and added tests/test_disassemble_logic_tables.py.

  3029. Reported: “The table shape is consistent: KQ4D has the same v3 extra action slots 0xb0..0xb5, just at different AGIDATA offsets. I’m adding a small signature-based detector around the known operand metadata pattern so the disassembler can use 0x0620/0x0942 for KQ4D automatically.”

    Action/result: Completed the detector patch and test file.

  3030. Reported: “The detector patch is in, but I don’t need to keep the old hard-coded helper around as live code. I’m trimming that before running KQ4D stats again.”

    Action/result: Removed the unused hard-coded static helper.

  3031. Reported: “Now I’m validating the detector and rerunning KQ4D disassembly stats. If the stats become sane and still show no sound opcodes, that supports treating the suspect KQ4D sound entries as non-script-observed until source inspection says otherwise.”

    Action/result: py_compile passed, dispatch-table tests passed 3 tests, KQ4D stats became coherent, and decoded KQ4D scripts referenced only sound resources 70..79.

  3032. Reported: “With the corrected table bases, KQ4D stats are sane and decoded scripts only reference sound resources 70-79; those are exactly the clean 12 34 sound records. The later bad sound-section triples remain non-observed by current KQ4D scripts, so I’m documenting that and the new table detector.”

    Action/result: Updated resource, bytecode, version, symbolic-label, and progress docs to record build-specific v3 dispatch-table bases and KQ4D sound-reference evidence.

  3033. Reported: “I’m updating the docs to stop presenting GR’s dispatch-table offsets as the v3 offsets. The portable statement is now: v3 keeps the same operand metadata shape and extra action slots, but the table bases are per-build and detected from AGIDATA.”

    Action/result: Added the implementation/evidence section for KQ4D dispatch-table detection and sound references.

  3034. Reported: “The KQ4D evidence is documented. I’m running focused validation again, then the full suite so the tracker count reflects the three new dispatch-table tests.”

    Action/result: py_compile passed; python3 -B -m unittest tests.test_disassemble_logic_tables tests.test_game_census tests.test_agi_resources passed 16 tests; mdbook build docs passed; and git diff --check passed.

  3035. Reported: “Focused validation is clean: 16 tests for table detection, census, and resources, plus mdBook/whitespace. I’m running full local discovery now.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 307 tests and generated tests/__pycache__ plus tools/__pycache__.

  3036. Reported: “Full discovery is now 307 tests. I’m removing the generated cache directories again, then I’ll bump the live tracker count and do the final sweep.”

    Action/result: Removed tests/__pycache__ and tools/__pycache__.

  3037. Reported: “Caches are removed. I’m updating the live test count from 304 to 307 and recording the focused/full validation for the dispatch-table work.”

    Action/result: Updated PROGRESS.md to 307 tests and added validation details to docs/src/clean_room_executable_notes.md.

  3038. Reported: “The tracker and evidence log are current. I’m doing the final sweep now: mdBook, whitespace, cache check, and status.”

    Action/result: mdbook build docs passed, git diff --check passed, no Python __pycache__ directories were present, and status showed the expected working tree changes plus unrelated run.sh.

  3039. Reported: “The final sweep is clean. Because tools/disassemble_logic.py changed, I’m also going to run the opcode-evidence freshness check and the local compatibility wrapper so the generated evidence pipeline still agrees with the updated disassembler.”

    Action/result: AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check passed; AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_game_census_dispatch_001.json passed.

  3040. Reported: “The opcode-evidence check and local compatibility wrapper passed. I’m checking the report and cleaning any caches the wrapper regenerated, then I’ll record those validation results.”

    Action/result: The compatibility report had zero return codes for local unit tests, mdBook, and opcode evidence. No Python cache directories were present.

  3041. Reported: “The compatibility report has all zero return codes and no cache directories were left behind. I’m appending that final validation note now.”

    Action/result: Added final compatibility-suite validation notes to docs/src/clean_room_executable_notes.md.

  3042. Reported: “Final validation notes are recorded. I’m running one last mdBook/whitespace/cache/status sweep after that documentation edit.”

    Action/result: mdbook build docs passed, git diff --check passed, no Python __pycache__ directories were present, and status showed the expected working tree changes plus unrelated run.sh.

  3043. Reported: “The last sweep is clean too. I’m adding that sweep result to the progress log; then I’ll do a status-only check so the final note reflects the exact working tree.”

    Action/result: Added this final sweep entry.

  3044. Reported: “Understood, and that distinction changes how the documentation should be organized. I’ll establish spec/ as the implementation-independent behavioral contract, keep docs/ as the evidence and reverse-engineering record, and make that two-role separation explicit in both the repository README and the contributor instructions.”

    Action/result: Created the separate spec/ mdBook with scope and conformance rules, clarified the clean-room two-role boundary in the repository and evidence-book introductions, and updated project guidance and progress tracking so future findings are promoted into the spec only as portable externally observable behavior.

  3045. Reported: “The initial separation is now in place. One more piece is needed to make it durable: the normal compatibility check currently builds only the evidence book, so I’m updating that manifest to build spec/ as well and adding a test that prevents the specification check from silently disappearing later.”

    Action/result: Added mdbook_spec_build to the default local compatibility manifest, covered it in the manifest tests, and updated validation documentation to build both books.

  3046. Reported: “The existing mdBook version rejects the newer multilingual setting in the scaffold. That is a configuration compatibility issue rather than content; I’m removing the unsupported field and rebuilding the specification before I call this done.”

    Action/result: Removed the unsupported mdBook setting after the first specification build failed; the full 307-test suite and evidence-book build had already passed.

  3047. Reported: “Both books now build cleanly, and the complete default compatibility run passes all 307 tests plus both mdBook builds and the opcode-evidence check. I’m recording that final validation in the evidence trail and doing a last status check; no commit will be made unless you request one.”

    Action/result: Recorded the successful spec/ build and default local compatibility report local_spec_split_001.json; git diff --check also passed.

  3048. Reported: “The highest-value gap is now the deliverable itself: spec/ has its boundary but almost no AGI behavior yet. I’ll start promoting the strongest, least ambiguous material into portable chapters: the resource-container contract, the shared runtime/state model, and sound scheduling, while keeping addresses, registers, DOS calls, and evidentiary commentary in docs/.”

    Action/result: Selected resource containers, core runtime state, and sound scheduling as the first substantive specification promotion set.

  3049. Reported: “The evidence separates cleanly into two kinds of contract. Resource files and sound streams have byte-exact external formats; runtime state is better specified as abstract types and ordered effects, not as copies of the DOS memory layout. I’m extracting those separately so the spec stays precise without becoming a disguised decompilation.”

    Action/result: Extracted byte-exact format rules separately from abstract state and lifecycle rules.

  3050. Reported: “The first promotion set is now bounded: exact v2/v3 resource containers, abstract runtime state and cycle ordering, and the sound resource/scheduler contract. I’m deliberately leaving incomplete four-channel envelope initialization and other evidence-only details out of the normative text until the observable rule can be stated without gaps.”

    Action/result: Defined the promotion boundary and recorded the remaining sound-envelope gap rather than inferring missing behavior.

  3051. Reported: “I’m writing the chapters now and adding a small structural test around the clean-room boundary. It will verify that every specification chapter linked by the mdBook exists and that substantive chapters do not accumulate analysis paths, debugger names, symbolic machine-code labels, or disassembly terminology.”

    Action/result: Added version, resource, runtime, and sound specification chapters plus tests/test_spec_book.py.

  3052. Reported: “The new chapters build and pass their boundary checks. Before promoting them in PROGRESS.md, I’m doing a second accuracy pass against the actual local resource bytes and existing models, especially around dictionary stream initialization and the distinction between engine-cycle behavior and game-authored logic conventions.”

    Action/result: Rechecked the promoted contracts and scanned all 468 readable Gold Rush dictionary streams; every stream begins with reset code 0x100.

  3053. Reported: “The accuracy pass confirmed all 468 currently readable Gold Rush dictionary-compressed records begin with reset code 0x100, so I’m making that a valid-stream requirement rather than leaving decoder startup ambiguous. I’m also correcting the handoff dashboard, which still contained stale language saying additional interpreter inputs were unavailable.”

    Action/result: Added the valid-stream reset requirement, created a specification-coverage dashboard, reprioritized remaining work around the behavioral deliverable, and corrected the stale handoff text.

  3054. Reported: “The specification now has its first real behavioral content, and PROGRESS.md tracks specification coverage separately from reverse-engineering coverage. I’ve also reprioritized the remaining work so completing portable chapters comes before gathering more evidence that does not resolve a spec ambiguity.”

    Action/result: Completed the first specification promotion pass and moved validation into progress.

  3055. Reported: “Validation is clean at 309 tests, including the new specification guardrails. I’m continuing with the next dashboard item rather than stopping there: the logic bytecode chapter will begin with the complete stream grammar and all 19 conditions, then promote action families in bounded, reviewable groups.”

    Action/result: Began the logic-bytecode promotion pass after both books, all 309 tests, opcode evidence, and whitespace checks passed.

  3056. Reported: “The bytecode format has a clean portable boundary: resource framing and message decoding, stream control bytes, condition-list semantics, operand interpretation, and operation effects. I’m promoting the complete condition set plus actions 0x00..0x20 first, including room-switch re-entry and picture load/prepare/show distinctions that a compatible engine must preserve.”

    Action/result: Added spec/src/logic_bytecode.md with the complete grammar and condition catalog plus the first 33 action rows.

  3057. Reported: “The first logic section is written, but I’m not treating the existing prose as infallible. I’m checking the nested-call return rule directly against the local disassembly before validation, because a single mistaken ‘callee termination propagates’ sentence would materially alter control flow for every implementation using the spec.”

    Action/result: Re-read the local logic call and interpreter instruction streams and found a stale opcode-0x00 return description.

  3058. Reported: “That check caught a real documentation error before it reached validation. Opcode 0x00 returns the instruction position after the terminator, not zero; ordinary called logic therefore returns to its caller. Only a callee path that explicitly returns zero, such as room switching, propagates termination into the caller. I’m correcting both the evidence book and the new spec now.”

    Action/result: Corrected normal logic termination and nested-call continuation semantics in both documentation roles.

  3059. Reported: “The source-level correction is now reflected on both sides of the clean-room boundary. I’m running the full suite again, including the spec terminology guard, so the final state verifies both behavioral content and separation discipline.”

    Action/result: All 309 tests, both mdBook builds, opcode-evidence freshness, and whitespace checks passed.

  3060. Reported: “Everything passes after the control-flow correction: 309 tests, both mdBooks, opcode evidence, and whitespace checks. I’m recording that validation and leaving the next specification task clearly bounded at action opcodes 0x21..0xaf, rather than mixing more unreviewed material into this pass.”

    Action/result: Recorded final validation and retained the remaining action catalog as the next bounded specification task.

  3061. Reported: “I’m taking the next specification milestone end to end: promote the remaining 2.936 action catalog in coherent families, source-check any vague field-based labels before naming their behavior, then add the v3 extension rows and validate the whole book. I’ll keep the evidence and progress logs current as each family lands.”

    Action/result: Began a source-first audit of action opcodes 0x21..0xaf.

  3062. Reported: “The remaining actions fall into natural behavioral groups rather than one flat machine-code table: object/view selection, animation and movement, inventory, text/input, save/restart, diagnostics, menus, and replay control. I’m using those domains as the spec organization so an implementer can understand state transitions without learning the original field offsets.”

    Action/result: Organized the remaining catalog around portable subsystem behavior.

  3063. Reported: “The object half is now semantically mapped: view/loop/cel selection, priority and update partitions, horizon/control/collision gates, four frame-cycling modes, three autonomous motion modes, rectangle boundaries, and inventory locations. I’m promoting those names and behaviors now, then I’ll do the UI/system half with the same source-first audit.”

    Action/result: Promoted action opcodes 0x21..0x64 into the behavioral specification.

  3064. Reported: “The UI/system half has two compatibility traps worth preserving explicitly: action 0x95 can consume an extra byte when tracing is already active, while runtime action 0xaf consumes no operand despite its scanner metadata. I’m keeping those observable stream effects in the spec and treating unresolved configured-window parameter meanings as a named gap instead of inventing semantics.”

    Action/result: Promoted actions 0x65..0xaf, added v3 extension actions, and added a test requiring every accepted profile opcode to appear.

  3065. Reported: “All accepted action bytes now have portable catalog entries, and the completeness test makes that mechanically visible. I’m doing a consistency pass next: operand counts against the dispatch tables, clean-room terminology checks, and spot checks of the unusual runtime-length cases before moving on to graphics.”

    Action/result: The focused specification tests, mdBook build, opcode-evidence check, and whitespace check passed; the action catalog remained internally consistent.

  3066. Reported: “The opcode catalog is complete enough to shift the center of gravity to graphics. I’m first checking the pending edits and the exact picture raster rules, then I’ll promote the picture format into the clean-room spec with completeness checks and keep moving into view/cel behavior if validation stays green.”

    Action/result: Began the picture-command specification pass from the already accumulated source and behavioral evidence.

  3067. Reported: “The picture path is unusually mature: all commands have source-backed semantics, scanner edge cases are pinned, and the rasterizer has exact byte-width arithmetic rather than a vague ‘draw a line’ description. I’m turning that into a portable contract now, including the pattern masks and the linear right-edge write behavior that a conforming implementation would otherwise be very likely to miss.”

    Action/result: Extracted the exact eight pattern-mask families and organized the normative picture contract around lifecycle, command scanning, rasterization, fill, and patterned plotting.

  3068. Reported: “The picture chapter is now in the clean-room book, with every accepted command represented and the malformed-stream boundary stated explicitly. I’m validating it against the existing renderer tests now; after that I’ll use the same promotion pattern for view resources, where mirroring and clipped run decoding are the next places an independent implementation could silently diverge.”

    Action/result: Corrected a stale focused-test module name and passed 68 specification and graphics-rendering tests.

  3069. Reported: “The focused picture checks pass: 68 specification and graphics tests. One attempted test name was stale (test_agi_graphics); I corrected it to the repository’s actual test_graphics_rendering module and am recording that false start rather than erasing it. The view contract is next, including shared-cel mirroring state, transparent runs, baseline placement, and the priority scan below low-control pixels.”

    Action/result: Began the view/cel specification promotion from the accumulated payload and drawing evidence.

  3070. Reported: “I’ve finished the source-to-behavior mapping for views. The important distinction is that mirroring is stateful at the cel level: the control byte records the loop orientation currently represented by a possibly shared cel, so selecting another loop can reverse the stored run stream and update that orientation. I’m specifying that transition, not merely saying ‘flip the image,’ because aliases and repeated loop changes can expose the difference.”

    Action/result: Added the normative view payload, run decoding, mutable mirroring, placement, priority-composition, and preview contract.

  3071. Reported: “The view chapter passes 69 focused checks. Its portable contract now covers the entire path from payload offsets through mutable mirroring and per-pixel priority acceptance. I’m moving into object behavior next, splitting it into lifecycle/update ordering, animation, movement modes, bounds/collision/control acceptance, and placement so the spec describes observable state transitions without inheriting the original record layout.”

    Action/result: Began the object-behavior promotion pass.

  3072. Reported: “The object evidence is broad, but one collision sentence is still too abstract for a normative spec: ‘baseline equality/crossing’ does not tell an implementer which inequalities fire. I’m reopening that helper’s local instruction stream before writing the chapter, and I’m also correcting the rectangle wording so the strict inside/outside test is not mislabeled as inclusive.”

    Action/result: Regenerated the disposable decrypted executable and read the collision helper directly.

  3073. Reported: “The direct source check resolved the collision rule precisely: touching horizontal extents count as overlap, and baselines collide when currently equal or when their strict relative order reverses between saved and current positions. It also exposed three catalog summaries that were too loose: object initialization enables update/cycling state, rectangle membership is strict, and the two control options are final-scan gates rather than simple one-class blockers. I’m correcting those while writing the object chapter.”

    Action/result: Added the object chapter, corrected the action summaries, and passed 75 focused checks after correcting another stale test name.

  3074. Reported: “The object chapter now passes 75 focused checks, and the catalog corrections are consistent with the evidence checks. I’ve left the subsystem honestly partial at one narrow point: exact random-motion timing and approach-mode recovery. Since that is now the highest-value remaining item, I’m staying source-first and isolating those two helper ranges for a direct instruction-level pass rather than papering over them with observed end positions.”

    Action/result: Isolated and decoded the approach, random-motion, and shared target-direction helper ranges.

  3075. Reported: “The isolated helpers close the remaining gap cleanly. Nearness uses strict bands (-threshold < delta < threshold), so equality at the threshold still requests movement; random mode chooses direction modulo 9 and a retry countdown modulo 51 until it is at least 6; approach recovery uses half the center/baseline Manhattan distance plus one and samples a delay no smaller than the step. I’m encoding those transitions and adding deterministic model tests around the random source abstraction.”

    Action/result: Added deterministic target/random/approach transition helpers and tests, promoted the exact rules, and marked object behavior complete for the current profiles.

  3076. Reported: “The motion gap is closed: 83 focused checks now pass, including strict target bands, random countdown rejection, and approach recovery transitions. The object subsystem can move to complete for the current profiles. I’m continuing into input and parsing now, starting from the resource and state model rather than the UI probes, because word normalization and event consumption determine what scripts can observe before any text is drawn.”

    Action/result: Began parser, event, text, inventory, and menu promotion.

  3077. Reported: “The input subsystem separates into four clean contracts: dictionary parsing, event normalization, text-surface state, and modal list interaction. I’m promoting those as one chapter but keeping font glyph shapes and the two still-unresolved configured-window parameters explicitly outside the completed claim; parser and event semantics can be normative without pretending the remaining presentation details are solved.”

    Action/result: Added the input/text/menu/inventory specification chapter and structural coverage checks.

  3078. Reported: “The parser/input/menu chapter passes 90 focused checks. I hit and corrected one more stale module name (test_agi_input versus test_input_model), which will go into the historical log. I’m continuing into session state now: room transitions first, then restart, the save envelope, restore ordering, and resource-event replay, with v2/v3 differences kept explicit instead of blended.”

    Action/result: Began the room/replay/persistence promotion pass.

  3079. Reported: “The session model has a useful portable core even before every save byte is semantically named: room-switch abort/re-entry, the replay-pair language, selector control flow, five-block framing, failure behavior, and the v3 transform are all externally testable. I’m writing those now, while labeling the still-opaque portions of serialized state as the remaining blocker for binary save-file conformance.”

    Action/result: Added the room/replay/persistence chapter and passed 101 focused specification, save, input, graphics, and logic-document tests.

  3080. Reported: “The persistence chapter passes 101 focused checks. It now gives an implementation team the room/replay state machine and all currently known save framing, while drawing a hard line around the unmapped bytes inside the five state blocks. Before attempting that much larger serialization map, I’m consolidating the evidence trail, updating the handoff/dashboard, and running the complete project validation so this long pass ends at a trustworthy checkpoint rather than a pile of individually green fragments.”

    Action/result: Updated the handoff and prepared the full validation run.

  3081. Reported: “The full suite is running now across the whole repository, not just the new chapters. After it finishes I’ll build both mdBooks, run the opcode-evidence freshness check, and inspect the complete diff for clean-room boundary leaks and accidental generated-file changes.”

    Action/result: The full local suite passed all 323 tests.

  3082. Reported: “Everything is green so far: 323 tests passed, both books build, and opcode evidence is current. I’m on the final audit now, checking whitespace, the substantive-spec terminology guard’s coverage, newly generated artifacts, and the exact diff footprint; then I’ll update the validation record and leave the next serialization task clearly bounded.”

    Action/result: Both mdBooks, opcode evidence, and whitespace validation passed; the only unrelated untracked file remained run.sh.

  3083. Reported: “I’m picking up at the highest-value remaining item: profile 2.936 save block 1. I’ll map it in block-relative portable fields, use the disassembly and existing symbolic data map to distinguish real state from padding, and add a coverage check so every one of its 1,505 bytes is either named or explicitly unknown rather than silently skipped.”

    Action/result: Began a byte-complete profile 2.936 block-1 map from the local executable and saves.

  3084. Reported: “The block boundary is now clear: block 1 is a contiguous runtime snapshot beginning with the seven-byte signature, followed immediately by all 256 variables and 32 packed flag bytes. The harder half is its tail, where key mappings, string slots, timers, horizon/rectangle controls, and text configuration are interleaved with currently unlabeled bytes; I’m inventorying every referenced field before assigning unknown ranges.”

    Action/result: Regenerated the static instruction listing and inventoried references throughout the full 1505-byte saved region.

  3085. Reported: “A useful anomaly turned up: the saved region from the first string-slot base to the text globals is 960 bytes, exactly 24 slots of 40 bytes, while the parser explicitly accepts only the first 12. I’m checking direct references around the midpoint before deciding whether this is 24 general slots, two 12-slot banks, or 12 slots plus unrelated zeroed state; the clean spec should not flatten those possibilities.”

    Action/result: Confirmed twelve parser-visible slots and retained the following 480 bytes as an opaque bank rather than inferring extra slots.

  3086. Reported: “The source reference inventory supports a complete first-pass partition: two small unknown words among the engine globals, a 44-byte unreferenced gap after the 39-entry key map, and a 480-byte opaque bank after the twelve validated string slots. I’m preserving those as byte ranges with known initial values instead of guessing names, while mapping the timer, horizon, movement rectangle, current prepared picture, replay counters, and text configuration precisely.”

    Action/result: Added an exhaustive block-1 region map and checked the five opaque ranges across all 11 local saves.

  3087. Reported: “The first save block map is ready to turn into both evidence and a portable contract. I’m going to land that documentation first, then use the same coverage discipline on blocks 2 and 4, which appear structurally complete: 21 fixed-size object records and 100 replay pairs respectively.”

    Action/result: Added complete block-relative maps for profile 2.936 save blocks 1, 2, and 4, plus deterministic splitters and coverage tests.

  3088. Reported: “I have enough source evidence to make two distinctions explicit in the spec: a save block may contain portable values plus opaque compatibility bytes, and pointer-shaped object fields are serialized but are not portable object identity. On restore, resource replay and view rebinding reconstruct those associations, so the contract will name the bytes while requiring implementations to rebuild references from resource selections rather than preserve process addresses.”

    Action/result: Separated serialized reference tokens from portable object identity in the normative block-2 contract.

  3089. Reported: “Before I describe those serialized references as disposable, I’m checking the successful-restore path field by field. The key question is not whether the bytes look like addresses, but whether any of them can affect valid behavior before the restored view, loop, cel, and render state is rebuilt.”

    Action/result: Confirmed that successful restore rebinds view/loop/cel state, rebuilds list participation, restores flags, and normalizes the event identifier to the object index.

  3090. Reported: “I’m editing the save helper, tests, and persistence chapter now. The maps will be mechanically exhaustive, with a validator that rejects gaps or overlaps; the prose will keep block-relative byte positions separate from the portable state model and will mark unresolved bytes instead of assigning speculative semantics.”

    Action/result: Updated the helper, tests, specification, dashboard, symbolic labels, handoff, and chronological evidence record.

  3091. Reported: “The restore pass resolves the reference-token question cleanly. It preserves the saved loop and cel selectors, reloads the selected view, recomputes loop/cel counts and references plus cel dimensions, rebuilds active/list state from saved flags, and restores those flags afterward. It also normalizes each object’s event identifier to its object index, so that byte is present in the file but is not authoritative after a successful restore.”

    Action/result: Added the post-restore normalization rule to the clean specification and a local save-corpus invariant test.

  3092. Reported: “The first tranche passes 28 focused checks. I’m moving straight into block 3 now; its 328 bytes strongly suggest the inventory table’s one-byte header plus 109 three-byte entries, but I’m treating that only as a hypothesis until the loader, inventory actions, and save corpus all agree on the boundary and field meanings.”

    Action/result: Rejected the initial arithmetic-only hypothesis after comparing the loader, inventory actions, source file, and save corpus.

  3093. Reported: “The block-3 hypothesis has sharpened: it is not 109 records. The saved bytes begin with 40 three-byte entries, each holding a name offset and one-byte location; those offsets all point to byte 0x78, where the zero-terminated item-name pool begins. The local source file is 331 bytes while the runtime/save block is 328 bytes, indicating a three-byte file prefix is consumed during loading. I’m tracing that loader now to establish the item count and offset origin exactly.”

    Action/result: Identified the 120-byte entry table and 208-byte name pool from the decoded data and source consumers.

  3094. Reported: “The loader confirms the format and ties three blocks together. After XOR decoding, the metadata file begins with name_table_offset:u16le and maximum_object_index:u8; the remaining bytes are exactly save block 3. For this game those values are 120 and 20, yielding 40 inventory entries, 21 drawable-object records, a 208-byte name pool, and therefore the observed block-2 and block-3 lengths without any padding.”

    Action/result: Added the inventory metadata file contract, exhaustive block-3 parser/tests, symbolic loader labels, and the derived block-length relationship to the evidence and clean specification.

  3095. Reported: “Block 3 is now complete and the focused suite remains green. I’m on the genuinely variable part, block 5: I’ll first derive its sizing formula and record grammar from the logic-cache allocator and save helper, then correlate each 4-byte growth step with loaded logic/resume state across the 11 saves.”

    Action/result: Reopened the logic-cache serializer, restore lookup, and all local variable block-5 payloads.

  3096. Reported: “Block 5’s grammar is now source-complete. It is a sequence of four-byte (logic_number:u16le, resume_offset:u16le) records, preceded by a serialized zero-valued head record and terminated by a four-byte record whose logic number is 0xffff. The offset is measured from that logic’s bytecode entry, so it remains portable across reloads; restore scans this table by logic number and rebuilds each resume position as new_entry + saved_offset.”

    Action/result: Added a variable block-5 parser with terminator, duplicate, relative-offset, and first-match tests.

  3097. Reported: “One subtle but important distinction emerged from the corpus: block 5 does not decide which logics are loaded. The replay-pair sequence does that. Block 5 is only a lookup table consulted as each replayed logic load occurs; the first matching entry supplies a relative resume offset, no match leaves the logic at its entry, and unmatched block-5 records are ignored. This explains why the saved cache list and replayed logic list overlap without being identical.”

    Action/result: Completed the five-block profile 2.936 map and promoted the replay-versus-resume-table distinction into the clean specification.

  3098. Reported: “All five blocks now pass 35 focused checks. I’m moving to the next dashboard item rather than stopping: a conformance matrix that tells an independent implementer what is universal, what is selected by interpreter profile, what comes from each game’s data, what remains unresolved, and what is deliberately outside the current full-EGA target.”

    Action/result: Began the standalone subsystem/version conformance matrix.

  3099. Reported: “The matrix will not just be a progress checklist. I’m making it normative claim guidance: an implementer can see which chapters must be combined for a 2.936 full-EGA claim, which deltas must be added for 3.002.149, which dimensions come from the selected game rather than interpreter code, and which incomplete areas prohibit narrower claims such as exact four-channel amplitude or v3 binary save interchange.”

    Action/result: Added the matrix chapter, claim requirements, structural test, summary link, and updated dashboard/handoff priorities.

  3100. Reported: “The conformance matrix passes its structural checks, bringing the focused total to 36. I’m doing a consistency audit now because this pass corrected two older evidence assumptions: block 3 is game-metadata-sized rather than interpreter-fixed, and block 5 includes a cache-head record before the linked logic records. After that audit I’ll run the complete suite and both books.”

    Action/result: Corrected stale block-5 evidence text and the completed matrix handoff item; source compilation and whitespace checks passed.

  3101. Reported: “The complete suite is green: 341 tests, up from 323 at the last checkpoint. I’m building both books and checking opcode evidence now, then I’ll do one final diff audit for stale claims and accidental changes; run.sh remains unrelated and untouched.”

    Action/result: The full 341-test suite passed.

  3102. Reported: “Validation is fully clean: both mdBooks build, opcode evidence is current, and whitespace checks pass. The final audit found only the expected project files plus the pre-existing untracked run.sh; the Python cache directories created during compilation have been removed.”

    Action/result: Completed the final validation and artifact audit for the save-map and conformance-matrix pass.

  3103. Reported: “The profile 2.936 save map and conformance matrix are complete, so I’m taking the next priority into source discovery rather than editing more spec by analogy. I’ll inventory the available Gold Rush save artifacts and locate the five v3 save source ranges first; any v2-shaped fields will only be promoted after the relocated v3 code confirms them.”

    Action/result: Reopened GR v3 save block evidence and source labels as the next PROGRESS.md priority.

  3104. Reported: “The v3 inventory block exposed a correction rather than a new mystery: the saved bytes decode exactly with the repeating Avis Durgan key and then match the decoded OBJECT runtime block byte-for-byte. The existing 59-byte helper constant produces nonsense on the same original-engine save, so I’m reopening the v3 transform extraction before changing the spec; this is precisely the kind of cross-check the byte-complete pass is meant to catch.”

    Action/result: Compared the original-engine GR save block against the decoded GR OBJECT runtime payload and found the previous helper key was wrong despite round-trip tests passing.

  3105. Reported: “The correction is now conclusive: 0x072c is a data-segment address, and AGIDATA.OVL contains Avis Durgan there. The earlier 59-byte constant came from reading main-code bytes at the same numeric offset, a segment-confusion error that round-trip-only tests failed to detect. I’m correcting the helper, spec, labels, and tests, and adding a known input/output assertion so this class of mistake cannot pass again.”

    Action/result: Corrected the GR save transform model from the accidental code-image bytes to the DS:0x072c data key.

  3106. Reported: “I’m applying the correction now. The dynamic save remains valid evidence for the transform, but the previous check only proved “XOR twice returns the input,” which is true for any repeating key. The replacement test pins the actual bytes: decoded runtime prefix 89 01 00 8b 01 1c must encode as c8 77 69 f8 21 58, matching the original-engine save.”

    Action/result: Updated the helper/test/spec files with exact key, wrap-point, and known-vector assertions.

  3107. Reported: “Continuing. The immediate thread is to finish validating the GR v3 save-key correction, then I’ll keep moving into the next PROGRESS.md item rather than stopping at the cleanup point.”

    Action/result: Resumed from the corrected GR save-key state and checked the current tree before continuing.

  3108. Reported: “The code already has the corrected key, but the helper is still SQ2-shaped in places. I’m going to validate the correction first, then generalize the save parser enough to map GR’s block 2/3/4/5 from source-derived metadata instead of hardcoded SQ2 constants.”

    Action/result: Ran the focused save/spec tests successfully and identified generic parser work for GR save blocks.

  3109. Reported: “The GR saves all agree: block 2 is 23 * 43, block 3 decodes byte-for-byte to OBJECT[3:], block 4 is 50 replay pairs, and block 5 currently contains head, cached logic 0, terminator. Block 1 still needs a real source map; I’ll avoid pretending it is complete and mark only the source-backed prefix/portable fields.”

    Action/result: Compared generated GR saves against the decoded GR OBJECT metadata and derived blocks 2 through 5.

  3110. Reported: “The focused correction tests are green. I’m now deriving the GR save dimensions from the GR OBJECT file and the generated original-engine saves, so the next spec step can say “23 object records and 131 inventory entries” because the data proves it, not because it resembles SQ2.”

    Action/result: Promoted decoded-object metadata as the source for GR object/inventory save dimensions.

  3111. Reported: “I’m going to add generic parsers rather than a second pile of GR-only code. The durable contract is: object record stride is still 43 bytes, inventory table size comes from the decoded OBJECT header, and replay capacity comes from the saved block length/profile, so the helper should express those dimensions directly.”

    Action/result: Added generic object-record, replay-pair, inventory-block, and object-metadata decode helpers in tools/agi_save.py.

  3112. Reported: “The generic parser helpers are in place. I’m adding tests that use games/GR only when the private directory is present, and QEMU-generated save artifacts only when they exist under build/, so the public repo can still run without bundled game data.”

    Action/result: Added optional GR metadata/save structure tests with private-input and generated-artifact guards.

  3113. Reported: “The tests now pin the GR dimensions against the decoded metadata and the generated signed save. I’m running that focused suite; if it passes, I’ll promote the block 2-5 mapping into the clean spec and evidence docs.”

    Action/result: Focused save/spec tests passed with 39 checks.

  3114. Reported: “Focused tests are green at 39 checks. I’m updating the spec now with the portable parts of the GR save layout: block 1 length/signature remains partial, while blocks 2-5 have concrete structure for the observed initial save state.”

    Action/result: Added the profile 3.002.149 observed Gold Rush block section to spec/src/session_and_persistence.md.

  3115. Reported: “The clean spec now has a separate GR v3 block section, with block 1 deliberately partial and blocks 2-5 expressed in portable terms. I’m reflecting that narrower completion state in the conformance matrix and project tracker.”

    Action/result: Updated spec/src/conformance_matrix.md, PROGRESS.md, and docs/src/current_status.md.

  3116. Reported: “The evidence-facing runtime notes are getting the same detail, including the object metadata derivation. This keeps the source trail clear: GR’s block dimensions come from decoded local metadata plus original-engine saves, not from a hand-transposed SQ2 table.”

    Action/result: Updated runtime/evidence documentation with GR blocks 2-5 derivation.

  3117. Reported: “Validation is clean and there are no generated __pycache__ directories. I’m not stopping there: the next PROGRESS.md item is GR block 1, so I’m moving back to disassembly around the v3 save writer to identify the source ranges for that 1028-byte block.”

    Action/result: Reopened GR save/restore disassembly around image 0x29e5 and 0x2aba.

  3118. Reported: “The save writer gives the crucial source boundary: GR block 1 is a contiguous 0x0404 bytes from DS:0x0002, exactly like the SQ2 block-1 method but shorter. The expanded 49-slot key map consumes almost all of SQ2’s old gap, the string-slot bank remains 12 slots, and the tail includes the GR menu/key gates.”

    Action/result: Derived the GR block-1 address/length from the writer helper arguments and mapped the main shifted regions.

  3119. Reported: “I’ve added the GR block-1 region table with explicit opaque gaps rather than smoothing them over. I’m adding tests for byte coverage, the expanded key-map/string/tail layout, and the observed signed save values.”

    Action/result: Added GR_V3_BLOCK1_REGIONS, split_gr_v3_block1(...), and focused block-1 tests.

  3120. Reported: “The GR block-1 parser tests pass. That closes the current highest-value item at the structural level, so I’m updating the clean spec from “block 1 partial” to a byte-complete observed Gold Rush block-1 map with explicit opaque ranges.”

    Action/result: Promoted the GR block-1 map into the clean persistence specification.

  3121. Reported: “The spec now has the full observed GR block-1 partition, including the expanded key-map and GR-specific tail gates. I’m moving the dashboard from “finish GR block 1” to the next real gaps: text-window parameters and sound envelope behavior.”

    Action/result: Updated the conformance matrix, PROGRESS.md, and current status handoff.

  3122. Reported: “The tracker now reflects that the GR save-map task is structurally complete for the observed Gold Rush profile, with opaque ranges still honestly marked. I’m adding the evidence-note section for the block-1 source boundary and region map.”

    Action/result: Added a clean-room evidence section for GR block-1 writer source ranges and map coverage.

  3123. Reported: “The runtime chapter now includes the block-1 source boundary. I’m recording the exact disassembly evidence in the clean-room notes, because that writer call is the thing that makes the 1028-byte block boundary solid.”

    Action/result: Updated runtime model prose and evidence notes with the block-1 writer call details.

  3124. Reported: “The evidence note now contains the block-1 writer call and full region map. I spotted one stale sentence in the version profile that still says the v3 save maps are incomplete, so I’m correcting that before validation.”

    Action/result: Corrected the profile 3.002.149 save-map status in the clean version profile.

  3125. Reported: “Final audit is clean: no whitespace errors, no cache dirs, and run.sh is still the unrelated untracked file. I’ll keep going into the new top item: resolving actions 0x97/0x98 from disassembly before changing the spec.”

    Action/result: Started the source-first configured modal-message pass.

  3126. Reported: “The existing evidence already has the handler addresses: SQ2 0x97 at 0x1c54 and 0x98 at 0x1c71, storing three bytes into [0x0d0b], [0x0d0d], and [0x0d09]. I’m reading the callee chain now to map what those globals actually do in the modal text-window geometry.”

    Action/result: Reopened the shared helper at 0x1c96 and modal window-builder path at 0x1d96.

  3127. Reported: “The disassembly resolves the parameters cleanly: parameter 3 is the maximum formatted text width, parameter 1 overrides the window row, and parameter 2 overrides the window column. I’m checking the derived rectangle math so the spec can describe observable behavior rather than just naming globals.”

    Action/result: Mapped [0x0d09] to width, [0x0d0b] to row, and [0x0d0d] to column, including zero-width default handling.

  3128. Reported: “The formulas show the display-helper role: parameter 3 feeds the formatter width, parameter 1 becomes the message-window row (relative to display base), and parameter 2 becomes the message-window column; omitted/default behavior centers the computed text window. I’m promoting that and leaving only pixel-rectangle exactness to the existing text-window section.”

    Action/result: Promoted the row/column/width behavior into the clean logic-bytecode and input/text chapters.

  3129. Reported: “The clean spec no longer treats the 0x97/0x98 parameters as unknown. I’m updating the evidence docs and dashboard next, then the only high-value partial domains left are mostly sound and font-target details.”

    Action/result: Updated evidence logic-bytecode, runtime model, current status, and PROGRESS.md.

  3130. Reported: “These labels make the cross-reference much clearer: [0x0d0b] is the one-shot row override, [0x0d0d] the one-shot column override, and [0x0d09] the one-shot width override. I’m patching those labels and the evidence log now.”

    Action/result: Added symbolic labels for the configured modal-message row, column, and width globals.

  3131. Reported: “One stale summary line still described the configured-message fields as incomplete. I’m correcting that historical summary so the evidence book doesn’t contradict the new source pass.”

    Action/result: Corrected the stale evidence summary and added the configured modal-message source note.

  3132. Reported: “I’ll re-anchor on PROGRESS.md and the current status notes, then pick the highest-value unfinished item that keeps the clean-room spec moving. After that I’ll update the evidence/spec/docs alongside any tests so the trail stays coherent.”

    Action/result: Re-read the tracker, handoff note, conformance matrix, and current git status. Selected the four-channel sound attenuation envelope as the top stale specification gap.

  3133. Reported: “The tracker says the biggest remaining spec gap is the four-channel sound attenuation envelope, with glyph exactness as a policy decision rather than something to chase blindly. I’m going source-first on sound now: labels, disassembly notes, then portable behavior only if the evidence is strong enough.”

    Action/result: Reopened sound/audio evidence, the clean sound chapter, symbolic labels, and existing sound helper/tests.

  3134. Reported: “There’s already a surprisingly good source model in tools/agi_sound.py; the spec is lagging behind it. I’m reading the helper/tests and the exact disassembly notes so I can promote the envelope contract without accidentally smuggling implementation addresses into spec/.”

    Action/result: Confirmed local tests already model the envelope table, delta clamps, channel masks, selector-2 adjustment, tone output, and stop-silence output.

  3135. Reported: “The evidence book already records the key envelope mechanics: reset timing, channel-3 persistence, signed deltas from base attenuation, 0x80 sentinel, global adjustment, selector-2 adjustment, and channel masks. I’m doing one fresh disassembly spot-check before editing the spec, just to keep the promotion grounded in the actual bytes.”

    Action/result: Re-ran ndisasm windows for the driver start, playback tick, and attenuation helper, plus an xxd table dump for the envelope bytes.

  3136. Reported: “The spot-check agrees with the existing helper: start seeds envelope pointers/indexes, ticks reset indices for channels 0-2 but not channel 3, and the attenuation helper applies one table byte per call until 0x80. I’m going to update the clean spec, conformance matrix, and progress tracker to mark the four-channel amplitude contract as specified at the command-output boundary.”

    Action/result: Promoted the four-channel attenuation envelope transition contract into spec/src/sound.md.

  3137. Reported: “The clean sound chapter now has the missing attenuation state machine. I’m tightening the matrix and tracker so they no longer call sound partial for envelope reasons, while still keeping analog waveform synthesis outside the target.”

    Action/result: Updated the conformance matrix and began revising the progress dashboard.

  3138. Reported: “One of the tracker hunks drifted since the last commit, so I’m splitting the update into smaller patches against the current text. Same intent, just less brittle.”

    Action/result: Re-read the exact PROGRESS.md sections and split the tracker update.

  3139. Reported: “The dashboard now treats sound as covered at the command-output boundary. I’m checking the renumbered high-value list and then I’ll patch the handoff note plus evidence log.”

    Action/result: Found and corrected stale sound-next-work wording and high-value list numbering.

  3140. Reported: “The handoff note now points at cross-version and remaining renderer/text questions instead of already-promoted sound work. I’m adding a structural test for the sound chapter’s envelope terms.”

    Action/result: Updated docs/src/current_status.md and added a spec-book structural test for sound timing/envelope coverage.

  3141. Reported: “That closes the stale sound item. I’m continuing into the next PROGRESS.md item rather than stopping: source-first cross-version work, looking for a mapped v3/SQ2 delta that still has a real observable spec ambiguity.”

    Action/result: Re-read the GR/SQ2 static comparison, version profiles, and remaining cross-version rows.

  3142. Reported: “The remaining v3 deltas in PROGRESS.md are mostly already source-mapped, and the tracker says additional versions should only become spec profiles after observable differences are checked. I’m going to refresh the local multi-game census and pick one concrete interpreter/version difference to document without assuming SQ2 or GR behavior applies.”

    Action/result: Re-ran the explicit local game census over KQ1/KQ2/KQ3/KQ4D/LSL1/PQ1/SQ2/GR and reviewed the versions chapter.

  3143. Reported: “The census refresh still points to one useful source-first question: KQ1 Version 2.917 has four sound-directory entries that look present to the generic v2 parser but do not point at valid volume headers. Before treating that as a container rule, I’m checking whether KQ1 scripts can actually reference those sound numbers.”

    Action/result: Disassembled KQ1 logic stats and sound actions; inspected KQ1 SNDDIR entries 34..37.

  3144. Reported: “KQ1 scripts heavily use sound, but the decoded references I saw are all in the low range. I’m extracting the unique referenced sound numbers directly so we can distinguish directory-looking garbage/end padding from anything scripts can observe.”

    Action/result: Extracted KQ1 immediate sound references 0..21 with gaps and confirmed unreadable sounds 34..37 are not immediately referenced. Rechecked KQ4D and found immediate sound references only 70..79.

  3145. Reported: “The one-off audit is clear enough to document, but repeatability would be better. I’m checking whether disassemble_logic.py exposes enough parser pieces to add a lightweight ‘script-referenced resources’ audit without reimplementing the whole bytecode decoder.”

    Action/result: Determined that importing disassemble_logic.py would force an environment-selected game path, so a standalone audit helper was more appropriate.

  3146. Reported: “I’m adding a small repeatable audit tool now. It scans decoded logic bytecode for immediate resource references and compares those against readable/unreadable directory entries, which is exactly the question raised by KQ1/KQ4D’s suspect sound records.”

    Action/result: Added tools/resource_reference_audit.py.

  3147. Reported: “The audit tool is added. I’m writing a synthetic test now so its bytecode scan catches immediate sound references and distinguishes unreadable directory entries that are actually script-referenced.”

    Action/result: Added tests/test_resource_reference_audit.py.

  3148. Reported: “The test fixture makes one valid sound and one bad sound that the script references, so it exercises the failure mode we care about. I’m running it now before applying the tool to KQ1/KQ4D.”

    Action/result: Py-compile and focused unit test for the audit tool passed.

  3149. Reported: “The audit test passes. I’m applying the tool to KQ1 and KQ4D now, since those are the two local inputs with suspect sound-directory records in the census.”

    Action/result: Generated build/cross-version/resource_reference_audit_kq1_kq4d.json.

  3150. Reported: “The raw JSON is large, so I’m summarizing the fields that matter: referenced unreadable records and unreferenced unreadable records per game/resource kind.”

    Action/result: Confirmed no referenced unreadable resources for KQ1 or KQ4D; KQ1 unreadable sounds 34..37 and KQ4D unreadable high sound entries are unreferenced by immediate decoded script operands.

  3151. Reported: “The audit confirms both suspicious sets are unreferenced by decoded scripts: KQ1 bad sounds 34-37, and KQ4D’s suspect high sound entries. I’m documenting that as planning evidence, not a new runtime rule.”

    Action/result: Updated docs/src/versions.md with the focused script-visible resource reference audit.

  3152. Reported: “The versions chapter now has the focused audit result and the clean-room boundary: unreferenced bad entries stay out of the behavioral model. I’m updating the tracker and handoff files so this tool is discoverable later.”

    Action/result: Updated PROGRESS.md and docs/src/current_status.md with the new audit tool and KQ1/KQ4D conclusion.

  3153. Reported: “The tracker has one policy-shaped gap that is worth resolving cleanly before chasing more probes: exact glyph bitmaps. Since the project target is portable full-EGA behavior, I’m checking the current text chapter and spec boundary to decide whether glyph pixels belong in the core profile or stay as an optional font input.”

    Action/result: Reviewed the text/input chapter, scope/conformance boundary, conformance matrix, and existing glyph-related evidence notes.

  3154. Reported: “The spec already leans the right way: glyph pixels are described as font inputs, not interpreter behavior. I’m going to make that explicit as a conformance decision: core AGI compatibility requires text cell geometry and text bytes/attributes, while bitmap-identical screenshot claims must supply a font profile.”

    Action/result: Added the text cell/font-profile boundary to the clean scope chapter.

  3155. Reported: “The scope chapter now makes the font boundary explicit. I’m updating the text chapter and conformance matrix so glyph exactness is no longer presented as unresolved work.”

    Action/result: Replaced the text chapter’s remaining glyph gap with a Font boundary section and moved exact glyph bitmaps outside the core target unless a font profile is supplied.

  3156. Reported: “The conformance matrix now excludes exact glyph bitmaps unless a font profile is declared. I’m updating the tracker/current-status wording to remove the glyph decision from the remaining work.”

    Action/result: Updated PROGRESS.md, docs/src/current_status.md, and the spec structural test for the renamed text chapter section.

  3157. Reported: “I’m picking up from the completed sound, resource-reference, and font-boundary work. I’ll use PROGRESS.md to choose the next concrete, source-first gaps, keep both books and the evidence trail synchronized, and carry each item through tests before moving on.”

    Action/result: Reviewed the dashboard, handoff, worktree, compatibility manifest, and remaining partial/opaque-state references.

  3158. Reported: “The dashboard is now mostly complete; the two genuinely open portable contracts are persistence’s opaque global-state bytes and broader version coverage. I’m going after the opaque save ranges first because they directly limit binary save interchange, and I’ll begin with static cross-references in the SQ2 and GR executables before deciding whether any narrow runtime probe is warranted.”

    Action/result: Reopened both save maps, existing save tests, symbolic labels, and the complete SQ2/GR disassembly listings.

  3159. Reported: “A useful cross-version pattern has emerged already: three SQ2 ‘opaque’ areas line up exactly with capacity or alignment changes in GR. I’m now checking every direct code reference to the underlying data addresses, including range-based accesses that a simple exact-address search can miss; that should let us distinguish reserved serialization bytes from state that merely lacked a label.”

    Action/result: Confirmed the SQ2 39-slot and GR 49-slot key-map bounds, common string root, removed 480-byte SQ2 tail, and absent direct references to the standalone reserved words.

  3160. Reported: “The static evidence supports a stronger result than ‘unknown’: the SQ2 tail after slot 39 is ten inactive key-map records plus four reserved bytes, the 480-byte bank is a removed legacy/reserved string bank, and the single-byte holes are word-alignment storage. GR confirms those boundaries by expanding the key map and deleting the extra bank. I’m going to encode these as reserved, preserved serialization state with canonical initialization, which closes the valid-execution ambiguity without inventing game-visible fields.”

    Action/result: Defined the portable reserved-state classification and canonical/preservation rule.

  3161. Reported: “I’m adding the save reserved-state contract now. The changes will rename the unresolved save regions into explicit reserved categories, define their canonical bytes and preservation rule, update the clean spec’s interchange claims, and add regression tests that prevent them from drifting back into vague ‘unknown’ state.”

    Action/result: Updated save helpers, save tests, the persistence chapter, version profile, and conformance matrix.

  3162. Reported: “The focused save/spec tests pass with the new partition. This also upgrades the GR claim: its five blocks were already byte-complete, and the only stated blocker was the supposedly opaque block-1 bytes; with those now classified as reserved serialization state, the spec can define Gold Rush save interchange for the mapped game/profile dimensions rather than leaving it categorically partial.”

    Action/result: tests.test_save_resources and tests.test_spec_book passed 43 focused tests.

  3163. Reported: “I’ve got enough evidence to edit. The changes will rename the unresolved save regions into explicit reserved categories, define their canonical bytes and preservation rule, update the clean spec’s interchange claims, and add regression tests that prevent them from drifting back into vague ‘unknown’ state. I’ll keep the disassembly/address rationale confined to the evidence book and symbolic map.”

    Action/result: Updated the evidence-facing versions ledger, symbolic labels, progress dashboard, and current handoff.

  3164. Reported: “The save-state gap is now closed cleanly and both mdBooks build. I’m moving to the next dashboard item: KQ4D’s 3.002.102 interpreter is the best next profile candidate because it sits close to GR’s 3.002.149 but has a distinct dispatch-table layout, so a source comparison can tell us which GR variants are family-wide and which are build-specific.”

    Action/result: Selected KQ4D as the next profile and inspected its MZ executable, combined resources, table offsets, and existing census.

  3165. Reported: “KQ4D is suitable: its AGI is directly disassemblable and its v3 action table has the same 182-entry shape as GR. I’m adding a generic table-comparison helper rather than hard-coding another pair, so future interpreter versions can be compared by detected contracts and normalized handler bodies with explicit game directories.”

    Action/result: Added tools/compare_interpreter_tables.py and tests.

  3166. Reported: “The comparison helper will stay deliberately conservative: matching operand contracts are meaningful, while normalized entry-snippet differences are only triage leads until the disassembled routines are read. I’m wiring that distinction into both the report and tests so a future agent cannot accidentally promote ‘different bytes’ into ‘different behavior.’”

    Action/result: Generated the first KQ4D/GR table report with explicit triage language.

  3167. Reported: “The generic comparator is working and the first KQ4D/GR report is generated. Both builds have identical parser contracts for every shared action and condition; I’m now reading the smaller set of handler-entry differences to identify actual observable deltas, starting with the v3-only slots and any shared actions already known to vary between SQ2 and GR.”

    Action/result: Found zero contract differences, zero condition snippet differences, and 12 shared action snippet differences.

  3168. Reported: “The first real result is crisp: 3.002.102 is not just ‘older GR.’ It shares GR’s v3 opcode-table shape and all 19 condition handlers, but for the 12 differing shared actions it mostly retains the 2.936 behavior: no room alias remap, 39 key slots, and the display-mode/input-width branches that GR later removes. I’m verifying each cluster in focused disassembly before promoting a 3.002.102 profile.”

    Action/result: Read focused KQ4D/GR disassembly for room, key map, input width, close-window, v3 gates, and save handlers.

  3169. Reported: “Focused disassembly confirms the table triage: KQ4D’s room action passes the byte directly, key mapping stops at 39, 0xa3/0xa4 really set and clear the input-width word, and 0xa9 clears it. Separately, KQ4D shares GR’s set/clear release gate, menu gate, v3 no-op slots, and pre-save XOR call. I’m comparing it directly with SQ2 now to pin the mixed profile precisely rather than describing it by age.”

    Action/result: Generated the KQ4D/SQ2 table comparison.

  3170. Reported: “The direct SQ2 comparison narrows KQ4D’s shared-action deltas to just five: inventory temporary state, v3 save transform, restart prompt handling, motion-mode-4 preservation, and the set-style release gate. Everything else among 0x00..0xaf, including room switching and input-width behavior, matches 2.936 at normalized source level. I’m extending this into KQ4D’s save layout now; its writer exposes a particularly clean hybrid block: the full 2.936 block-1 shape plus the v3 menu/release gates.”

    Action/result: Source-mapped the 0x05e4 first block and writer roles.

  3171. Reported: “One KQ4D-specific wrinkle needs resolving before I write the save dimensions into the spec: the eight-byte OBJECT file is structurally valid as plain metadata, while the interpreter’s startup path appears to apply the same Avis Durgan XOR helper used by GR. I’m tracing the demo launcher/install path and file handling around that call, because treating those eight bytes with the wrong transform would produce impossible counts and contaminate the save map.”

    Action/result: Confirmed the launcher does not transform OBJECT and the interpreter XOR helper is instruction-identical to GR’s relocated helper.

  3172. Reported: “The source path is unambiguous enough to justify one confirmation probe. I’m adding a reusable v3 inventory-metadata encoding probe that makes two writable copies under build/: one with the supplied OBJECT bytes unchanged and one XOR-encoded with the interpreter’s local key, then boots both in one QEMU snapshot run. This will tell us whether the local demo input is pre-decoded packaging or whether another startup step compensates for it.”

    Action/result: Added the copied-fixture encoding probe and local test.

  3173. Reported: “The QEMU confirmation is running as a two-case snapshot batch now. It uses copied fixtures only; the private KQ4D directory remains untouched. Once both captures are in, I’ll distinguish ‘engine failed before graphics’ from ‘same playable output’ by inspecting the actual framebuffer rather than relying on process completion.”

    Action/result: Sandbox VNC binding failed; requested the required local QEMU elevation.

  3174. Reported: “The first elevated run exposed a fixture hygiene bug before QEMU started: copytree preserved the read-only directory modes from games/, so the generated copy could not be replaced on the second attempt. I’m fixing the probe to make directories as well as files writable under build/, matching the project’s other fixture builders, then I’ll rerun the same two cases.”

    Action/result: Corrected copied directory modes and reran the probe.

  3175. Reported: “Both first captures are the Sierra loader splash, so they prove only that the launcher starts; the palette difference is timing during that animated screen. I’m advancing past the loader in the same probe and recapturing after the interpreter has actually consumed OBJECT, which is the point where the two encodings can meaningfully diverge.”

    Action/result: Added --advance-loader and reran both cases past Enter.

  3176. Reported: “After advancing into the actual demo, both copies produce the same game frame. That rules out a simple ‘plain file fails, encoded file works’ story. I’m returning to the disassembly around the second XOR call; the likely explanation is that this build treats the metadata buffer as a reversible encoded store and toggles it at two different lifecycle points, so the eight-byte file alone is not enough to infer which representation is canonical.”

    Action/result: Determined the second XOR call belongs to logic-message decoding, not an inventory re-encode lifecycle.

  3177. Reported: “The second XOR call was unrelated logic-message decoding, so the dynamic result does not make the unchanged OBJECT file valid; it only shows this demo reaches its intro despite malformed inventory metadata it does not yet exercise. The source-backed valid path is still clear: decode with the repeating key, yielding the eight-byte intended metadata. I’m using that bounded valid representation to map KQ4D’s save blocks and keeping the packaging anomaly in the evidence book, outside the clean behavioral contract.”

    Action/result: Added KQ4D save constants, parsers, and source-derived dimension tests.

  3178. Reported: “The save helper tests now pin KQ4D’s source-derived dimensions: block 1 is 0x05e4, object state is 16 records, decoded inventory state is one three-byte entry plus \"?\\0\", and the demo configures one replay-pair slot. I’m promoting this as a bounded 3.002.102 resource/logic/persistence profile, while explicitly withholding a full gameplay claim until its renderer and object-update subsystems receive the same source comparison.”

    Action/result: Added the bounded profile, persistence map, and conformance limitation to the clean specification.

  3179. Reported: “The bounded 3.002.102 profile is now in the clean spec and its focused tests pass. It deliberately stops short of a gameplay claim: we now know exactly how KQ4D mixes 2.936-style room/input behavior with v3 gates, save transform, restart, and motion state, but pictures, views, and object-update internals still need role-based comparison before inheriting either complete profile.”

    Action/result: Updated the versions ledger, symbolic labels, progress dashboard, and handoff toward KQ4D renderer/object comparison.

  3180. Reported: “The source mapping is settling into stable relocation bands, so I’m extending the generic comparator with explicit symbolic role pairs. That keeps the comparison address-independent at the reporting level and lets later builds reuse the same role vocabulary.”

    Action/result: Added repeatable --role-pair support and a parser/report regression test to tools/compare_interpreter_tables.py.

  3181. Reported: “With logic and persistence mapped, I’m now comparing KQ4D’s view loader, picture scanner/raster helpers, and object update pipeline against the already-labeled Gold Rush build. I’ll treat normalized snippets as triage and inspect every reported difference in disassembly.”

    Action/result: Generated the first 46-role KQ4D/GR subsystem report.

  3182. Reported: “Rizin can analyze both MZ files, but its raw 16-bit function matcher is not reliable enough here: it includes the DOS header in raw offsets and the function-diff command rejects the required 16-bit architecture setting. I’m using the local ndisasm-based role comparator and direct call relationships instead, with every candidate checked at the instruction level.”

    Action/result: Kept Rizin as exploratory triage only; no behavioral conclusion depends on its function matcher.

  3183. Reported: “The source mapping is already strong: all six view roles, the full picture command scanner, every line/fill/pattern helper, and the object update-list pipeline match after relocation. The only picture differences so far are KQ4D retaining the older alternate-display refresh branches; its primary full-EGA path is the same.”

    Action/result: Classified the decode/fill/full-refresh differences as alternate display-mode wrappers outside the current target.

  3184. Reported: “The role-pair report now covers 46 renderer/object helpers. One animation result is a false mismatch caused by an embedded jump table; I’m manually checking its branch bodies and filling the remaining dirty-rectangle, placement, and motion roles before upgrading the profile.”

    Action/result: Confirmed the animation branch bodies and identified the remaining KQ4D helper entries from their callers.

  3185. Reported: “I’m resuming at the KQ4D/Gold Rush source comparison. The immediate goal is to finish mapping motion, placement, and dirty-rectangle behavior from disassembly, then decide whether the 3.002.102 profile has enough evidence to graduate beyond its current bounded status.”

    Action/result: Confirmed the complete KQ4D object-motion loop and dirty-rectangle helper as relocation matches.

  3186. Reported: “The main object-motion loop is a direct relocation match between KQ4D and Gold Rush, including collision, control-line acceptance, placement adjustment, edge reporting, and motion completion. I also confirmed the dirty-rectangle routine instruction-for-instruction; the remaining work is locating two helper entry points whose addresses shifted outside the dominant relocation band.”

    Action/result: Located placement at image 0x5d64, pre-mode update at 0x065b, mode dispatch at 0x0691, and boundary checking at 0x06f2.

  3187. Reported: “All six remaining role pairs are now mapped. Four normalize exactly, while the motion dispatcher and boundary checker differ only where ndisasm interprets their embedded jump tables as instructions; their executable branch bodies and state transitions match after relocation. That completes the source comparison named as the blocker in PROGRESS.md, so I’m recording the evidence and promoting the KQ4D profile for the primary full-EGA valid-data target, while keeping its alternate display path explicitly version-specific.”

    Action/result: Promoted 3.002.102 to a full-EGA profile and recorded all 52 KQ4D renderer/object role associations.

  3188. Reported: “The clean spec now carries an explicit 3.002.102 full-EGA claim, with KQ4D’s mixed variant choices spelled out rather than inherited by version number. I’ve also added every KQ4D renderer/object address to the cross-version symbol map and preserved the failed Rizin matcher attempt in the evidence trail, so this promotion remains auditable.”

    Action/result: Focused 49-test set and both mdBooks passed.

  3189. Reported: “The profile promotion passes its focused 49-test set, and both mdBooks build cleanly. I’m moving straight to the next dashboard item: selecting another local interpreter version by census, then comparing its dispatch contracts and subsystem roles source-first before deciding whether it adds a genuinely new behavioral profile.”

    Action/result: Selected KQ1/2.917 as the next earlier v2 source target.

  3190. Reported: “The first 2.917 table pass exposed a tooling issue rather than an engine delta: KQ1’s table addresses line up strikingly with 2.936, but the executable body is still transformed, so every disassembly snippet becomes nonsense and the last two table records are misread. I’m tracing the project’s existing SQ2 decryption path and will make the comparator reject or decode this state instead of producing a misleading ‘176 differences’ report.”

    Action/result: Discarded the transformed-file report and decoded KQ1 with its own SIERRA.COM key.

  3191. Reported: “After decrypting KQ1 with its own local loader key, the comparison collapses from nonsense to one clean result: all 19 conditions and actions 0x00..0xad match 2.936 exactly, including handler bodies and operand contracts. The apparent 0xae/0xaf rows are not valid 2.917 table records; I’m verifying the dispatch bound and adjacent data in disassembly before defining the earlier profile’s opcode range.”

    Action/result: Confirmed the action dispatcher maximum is 0xad.

  3192. Reported: “The table geometry gives a reusable answer across every local v2 build: a fixed 32-byte trailer separates the action and condition tables, so the valid action count is derivable instead of assumed. It yields 170 actions for 2.411/2.440, 174 for 2.917, and 176 for 2.936. I’m correcting the disassembler/comparator now so future earlier-version reports cannot accidentally parse trailer bytes as opcodes.”

    Action/result: Implemented geometry-derived v2 action counts and added KQ1/KQ2/LSL1 regression tests.

  3193. Reported: “The subsystem pass found one real 2.917 runtime delta amid otherwise matching code: direction-driven loop selection uses the four-loop table only when a view has exactly four loops. In 2.936 the same branch accepts four or more loops. Everything else in the 52-role renderer/object set matches after relocation, so I’m now mapping 2.917’s save dimensions and absent opcode boundary before deciding how complete the new profile can be.”

    Action/result: Promoted the exact-four-loop rule as a 2.917 variant.

  3194. Reported: “The 2.917 save writer confirms the same five-block grammar and identical 0x05e1 first-state block. KQ1’s game data changes only the object count: 18 records instead of 21; its decoded inventory block remains 0x0148, and logic 0 configures the same 100 replay pairs. I’m encoding those dimensions as tests before promoting the profile, so the gameplay claim and the KQ1-specific binary-save claim stay properly separated.”

    Action/result: Added KQ1 save constants, split/decode helpers, and tests.

  3195. Reported: “The 2.917 model is now internally consistent: 174 actions, 19 conditions, the full-EGA renderer/object pipeline, the exact-four-loop variant, and all five KQ1 save-block dimensions are covered by source-derived tests. I’m writing the evidence/profile ledger next, including the loader-specific executable transform so later comparisons do not repeat the encrypted-image false start.”

    Action/result: Promoted the 2.917 full-EGA and KQ1 binary-save claims.

  3196. Reported: “I’ve reached a coherent commit point: the KQ4D profile promotion is complete, and the new KQ1/2.917 profile has source-backed opcode, renderer/object, loop-selection, and save-dimension coverage. I’m doing the final evidence-log and validation pass now, then I’ll commit only the intended project files and leave run.sh untouched.”

    Action/result: Began final full-suite, mdBook, and diff validation.

  3197. Reported: “I’ll treat KQ4 as an independent evidence input and verify its version, container, executable, opcode tables, and resource census before assuming it matches KQ4D.”

    Action/result: Identified full KQ4 as AGI 3.002.086 with combined v3 resources, distinct from the KQ4D 3.002.102 demo.

  3198. Reported: “It is not the same interpreter build: full KQ4 reports 3.002.086, while the demo is 3.002.102. I’m comparing the dispatch tables and source roles now so the version profile follows observed behavior.”

    Action/result: Generated full-KQ4/KQ4D and full-KQ4/SQ2 table reports.

  3199. Reported: “The first table report exposed a useful tooling bug: it assumed every v3 build had 182 actions and was reading KQ4 trailer data as opcodes. I’m deriving the v3 count from table geometry and adding a regression test before trusting the comparison.”

    Action/result: Replaced the fixed v3 action count with the observed 0x4a-trailer geometry rule; KQ4 correctly parses 178 actions.

  3200. Reported: “Understood: KQ4D is the demo, while KQ4 is the full game. I’m keeping those labels explicit throughout the evidence and spec so the selected-game save dimensions are not mistaken for version constants.”

    Action/result: Renamed documentation sections to distinguish full KQ4 from the KQ4D demo and scoped save dimensions to each selected game.

  3201. Reported: “With the corrected bounds, all 178 shared KQ4/KQ4D action contracts and all 19 conditions match. The actual opcode deltas are narrow: KQ4 increments the release gate, its 0xb0 consumes one ignored byte, and its valid action range ends at 0xb1.”

    Action/result: Mapped KQ4’s action boundary and shared handler variants.

  3202. Reported: “The source comparison is revealing an evolutionary midpoint: KQ4 already has the v3 inventory, save, restart, and motion-mode changes, but retains the earlier room, input-width, key-map, and release-gate behavior. I’m finishing the renderer/object role pass before promotion.”

    Action/result: Compared 52 renderer, view, object, motion, and animation roles against KQ4D after relocation.

  3203. Reported: “The save layout is distinct from the demo: full KQ4 keeps the 0x05e1 first block, has 26 object records, 45 inventory entries, and 250 replay pairs. Its inventory block uses the v3 XOR transform.”

    Action/result: Added full KQ4 save constants, object decoding, block splitters, and source-derived regression tests.

  3204. Reported: “The resource audit finds no immediate reference to KQ4’s missing-volume picture/view entries. Picture selection is variable-based, so I’m recording that as a valid-data exclusion rather than claiming the entries are unreachable.”

    Action/result: Scoped absent-volume records outside the promoted profile and preserved the variable-reference caveat.

  3205. Reported: “There is now enough source evidence to promote 3.002.086: the full-EGA picture/view pipeline matches after relocation, with two object-edge variants in loop selection and exact-zero left-boundary reporting. I’m translating those into portable contracts.”

    Action/result: Added the 3.002.086 profile to the clean specification and conformance matrix.

  3206. Reported: “The portable profile now captures KQ4 as an evolutionary midpoint without exposing DOS implementation details. I’m completing the evidence addresses, progress trackers, book builds, and full test pass.”

    Action/result: Updated the versions chapter, symbolic map, handoff state, progress dashboard, and evidence ledger for full KQ4.

  3207. Reported: “The focused verification is clean: 58 KQ4/save/table/spec tests pass, and both mdBooks build. I’m doing the full repository suite and a final wording/data audit now.”

    Action/result: Confirmed the focused tests and both documentation builds.

  3208. Reported: “The full suite passes: 362 tests, including the resource parsers, save maps, dispatch-table comparisons, renderer models, and book-content checks. No QEMU run was needed because every promoted difference was directly resolved in the disassembly.”

    Action/result: Completed the full local regression suite and retained disassembly as the primary evidence for this profile.

  3209. Reported: “The aggregate runner’s first invocation failed because I omitted the required AGI_GAME_DIR; resource-dependent modules correctly refused to import. This is the intentional no-default-game safeguard, so I’m rerunning with games/SQ2 explicitly selected.”

    Action/result: Classified the failed aggregate invocation as a command configuration error rather than a code regression.

  3210. Reported: “The explicitly configured aggregate manifest is clean: all 362 tests pass, both mdBooks build, and the generated opcode-evidence chapter is current.”

    Action/result: Wrote the successful local compatibility report to build/compatibility-suite/kq4_3002086_local.json.

  3211. Reported: “I’ll move directly into the highest-value remaining work: source-first analysis of the 2.411 and 2.440 interpreters, beginning with executable decoding and dispatch boundaries, then shared opcode handlers, renderer/object roles, and save dimensions.”

    Action/result: Began independent KQ2/2.411 and LSL1/2.440 analysis.

  3212. Reported: “The two targets have clean direct-resource sets, but their executable packaging differs: KQ2 has transformed AGI plus SIERRA.COM, while LSL1 has neither filename. I’m identifying LSL1’s executable before decoding anything.”

    Action/result: Identified LSL1 LL.COM as a complete 38 KiB MZ interpreter; decoded KQ2 AGI with its local loader key.

  3213. Reported: “The table comparison is unusually clean. Versions 2.411 and 2.440 have identical 170-action and 19-condition contracts; only restart handler 0x80 differs between them. Against 2.917, the investigation set is limited to a handful of handlers and the 0x97/0x98 table records.”

    Action/result: Generated KQ2/LSL1, KQ2/KQ1, and LSL1/KQ1 static reports.

  3214. Reported: “A deeper source read shows 0x97/0x98 physically consume four bytes in every build, although 2.411/2.440 record three in AGIDATA. The raw early count makes linear disassembly drift, so I’m correcting the analysis tool using handler-observed width.”

    Action/result: Added an effective operand-count rule and regression test for early configured-message actions.

  3215. Reported: “The local save files exposed and corrected an important detail: early block 1 is 0x05df, not 0x05e1. Relative to 2.917, the omitted tail is exactly the two-byte saved replay-checkpoint count.”

    Action/result: Corrected the early save map and confirmed the first four block lengths from two KQ2 and three LSL1 saves.

  3216. Reported: “The picture command dispatch tables reveal the first major 2.411/2.440 split. LSL1 already has the later pattern organization, while KQ2 routes pattern setup and plotting to shorter routines.”

    Action/result: Proved KQ2 0xf9 ignores its byte and 0xfa plots one pixel per coordinate pair.

  3217. Reported: “The complete role-pair report confirms the boundary cleanly: view loading, line/fill rasterization, object composition, collision, placement, animation, and motion match; only pattern behavior reaches different full-EGA output.”

    Action/result: Generated the 55-role KQ2/LSL1 subsystem report and classified nonobservable draw-state and embedded-table differences.

  3218. Reported: “Real-resource census adds a useful nuance: KQ2’s pictures never invoke 0xf9 or 0xfa; five LSL1 pictures use 0xfa but retain the default zero-radius mode.”

    Action/result: Recorded selected-game command usage separately from the interpreter’s valid-data capability.

  3219. Reported: “The high-level sound cache/load/start/stop paths match, but the low-level driver does not normalize to 2.936. I’m following the driver calls rather than assuming the later envelope model.”

    Action/result: Mapped early driver start, tick, stop, event output, and timer-hook entries in both builds.

  3220. Reported: “The early sound boundary is substantive: neither profile has attenuation envelopes; selector zero alone is single-channel; and 2.411 always writes both non-PC tone bytes while 2.440 conditionally suppresses the low byte.”

    Action/result: Added early channel, tone-byte, and attenuation-output switches to the local sound oracle.

  3221. Reported: “The focused model tests pass, including early single-pixel patterns, both real save families, and early sound outputs. Both profiles now have coverage across resources, opcodes, graphics, objects, sound, restart, and persistence.”

    Action/result: Passed 142 focused tests before spec promotion.

  3222. Reported: “I’m writing the clean profiles in portable terms now, keeping implementation evidence out of spec/; addresses and table anomalies remain in the evidence book.”

    Action/result: Added separate 2.411 and 2.440 behavioral profiles, conformance claims, and subsystem variants.

  3223. Reported: “The clean-spec promotion passes 152 focused tests and builds successfully. I’m completing the evidentiary half, then I’ll cross-check another game in the 2.917/2.936 families.”

    Action/result: Verified the profile specification and began final evidence/tracker updates.

  3224. Reported: “The evidence now separates executable packaging, table geometry, command targets, sound-driver variants, and save dimensions from the portable contracts.”

    Action/result: Updated the versions chapter, symbolic map, executable notes, and historical progress log for both early profiles.

  3225. Reported: “The early profiles are fully reflected in the evidence book, clean spec, symbolic map, and progress dashboard. I’m continuing with PQ1/2.917 and KQ3/2.936 same-version cross-checks.”

    Action/result: Advanced to additional-game profile validation.

  3226. Reported: “PQ1’s AGI is already MZ, while KQ3 uses the transformed loader-managed form. I’m decoding only KQ3 before same-version table comparison.”

    Action/result: Decoded KQ3 and preserved PQ1’s direct executable image.

  3227. Reported: “PQ1/KQ1 match all 174 actions and 19 conditions; KQ3/SQ2 match all 176 actions and 19 conditions, with zero normalized handler differences.”

    Action/result: Generated both same-version table reports.

  3228. Reported: “After removing MZ headers, PQ1/KQ1 differ by only three bytes and KQ3/SQ2 by two; those bytes are embedded game signatures. All 55 subsystem roles match at the same addresses.”

    Action/result: Established strong same-version executable equivalence apart from selected-game identity.

  3229. Reported: “PQ1 has 20 object records, 25 inventory entries, and 250 replay pairs; KQ3 has 17 records, 55 entries, and 127 pairs. I’m adding these as game-data dimensions, not behavioral variants.”

    Action/result: Added save helpers and tests for PQ1 and KQ3 dimensions.

  3230. Reported: “The same-version cross-check is complete. With all locally observed version families profiled, I’m building a reusable normalized role matcher for future relocated interpreters.”

    Action/result: Began generalizing call-target and handler role matching.

  3231. Reported: “The role matcher finds every KQ3/SQ2 subsystem role and returns KQ2’s known variants as review targets rather than false matches.”

    Action/result: Added tools/match_interpreter_roles.py, four tests, and generated KQ3/KQ2 role reports.

  3232. Reported: “I’m documenting the matcher and updating the tracker, then I’ll run the entire suite and both books as the natural stopping point for this extended pass.”

    Action/result: Updated AGENTS, cross-version workflow, current status, and remaining-work guidance for scalable future comparisons.

  3233. Reported: “The aggregate verification is clean: 374 tests pass, both books build, and opcode evidence is current. I’m removing one stale handoff sentence and completing the final repository audit.”

    Action/result: Wrote build/compatibility-suite/cross_version_profiles_local.json; confirmed no tracked or untracked change under games/ was produced by this work.

  3234. Reported: “I’ll pick up from the newly committed cross-version profiles, read the current completion dashboard and evidence trail, then take the highest-value unresolved items through disassembly, portable specification, and tests.”

    Action/result: Confirmed all current local interpreter inputs already belong to seven promoted profiles and narrowed the remaining work to source-visible behavioral edges and suite breadth.

  3235. Reported: “The dashboard is now mature: every known opcode and major subsystem has a portable contract, so the useful next move is not another broad sweep.”

    Action/result: Audited partial/remaining markers and selected source-first renderer/resource lifecycle work rather than speculative QEMU probing.

  3236. Reported: “There are no unanalyzed interpreter directories in the current local set; all nine inputs map to seven promoted profiles.”

    Action/result: Rechecked the local game census and graphics/symbolic-map coverage.

  3237. Reported: “One genuinely open source-backed area remains in the executable evidence: resource cache descriptors still contain under-described fields.”

    Action/result: Determined that the overlay descriptor field is unused by the runtime loader and does not justify a portable behavior rule.

  3238. Reported: “The descriptor gap is linker/startup bookkeeping rather than a game-visible cache rule.”

    Action/result: Reconfirmed that v3 combined-directory fallback and exact absent-entry behavior were already present in both evidence and spec.

  3239. Reported: “I’m running the normalized role audit across every promoted profile, not just the two examples used to develop the matcher.”

    Action/result: Generated SQ2-reference reports for KQ1, PQ1, KQ2, LSL1, KQ4, KQ4D, and GR. Every unmatched role maps to an existing documented profile delta or matcher-candidate limitation; no new variant emerged.

  3240. Reported: “I’m moving to a valid-script lifecycle edge that the current spec states too loosely: what happens to already-bound objects or active playback when a resource is discarded from its cache.”

    Action/result: Disassembled picture/view lookup, discard, and heap rewind routines and established ordered cache truncation.

  3241. Reported: “The discard routines reveal a substantive rule missing from the portable lifecycle: discard is stack-shaped, not an arbitrary map deletion.”

    Action/result: Confirmed selected-and-later same-family resources become unreachable; identified stale selected-payload use as outside the stable valid lifecycle contract.

  3242. Reported: “I’ve confirmed the exact list operation in both picture and view paths and across the promoted builds.”

    Action/result: Added the ordered-retention source model, focused tests, evidence notes, and clean behavioral contract.

  3243. Reported: “The focused model and spec checks pass. One documentation asymmetry showed up: the symbolic map had cross-version associations for view discard but no primary SQ2 role row.”

    Action/result: Added the missing primary symbolic role and structural spec checks for ordered picture/view discard.

  3244. Reported: “The implementation/model tests pass; the only failure is a structural assertion that treated a Markdown line break as semantic text.”

    Action/result: Made the clean-spec assertion whitespace-tolerant before broader verification; no behavioral model change was needed.

  3245. Reported: “Replay executes the same discard handlers, so kind 6 and 7 inherit ordered truncation; this matters for save/restore reproducibility and is now explicit rather than implicit.”

    Action/result: Connected ordered resource retention to restore replay, updated the dashboard/handoff, and prepared full local verification.

  3246. Reported: “The lifecycle work itself is clean, but I invoked the aggregate runner’s --name as a report label; in this tool it selects a registered command.”

    Action/result: Corrected the invocation and reran with --report only.

  3247. Reported: “The rerun exposed a real harness usability gap rather than a behavior failure: the default local suite launches tests that require an explicit game directory, but the runner has no --game-dir option.”

    Action/result: Added explicit --game-dir propagation and report metadata without introducing a default game; updated tests and commands.

  3248. Reported: “The runner fix passes its focused tests and the aggregate suite is now executing with games/SQ2 explicitly selected.”

    Action/result: The local layer passed 381 tests, both mdBooks, and opcode evidence checks; the JSON report records the selected game directory.

  3249. Reported: “The disassembler does accept --game-dir; the earlier failure was specifically bare --help, because the shared option is consumed before the script’s parser.”

    Action/result: Left the established shared-option architecture intact and ran all corpus commands with explicit game paths.

  3250. Reported: “Discard is not merely synthetic: every profile family uses it, with full KQ4 alone containing 201 picture/view discard actions.”

    Action/result: Generated all-game logic disassemblies and inspected discard contexts; no selected game uses variable-view action 0x99.

  3251. Reported: “The corpus clarifies why Sierra’s allocator discipline works: KQ2 logic 67 loads views 53,59,51,52,57,60 and later discards 60,57,52,51,59,53, exactly in reverse order.”

    Action/result: Recorded per-profile usage and the concrete LIFO sequence as game-data support for the source-derived ordered-retention rule.

  3252. Reported: “The repository audit is clean apart from the intended edits and pre-existing untracked run.sh/cache directories.”

    Action/result: Updated suite selection/report documentation and the current 381-test dashboard count before final aggregate verification.

  3253. Reported: “The final aggregate run is clean: 381 tests pass, both books build, and opcode evidence is current.”

    Action/result: Preserved the final report at build/compatibility-suite/local_ordered_resource_discard_final.json and moved to the immutable-input/diff audit without an unnecessary QEMU run.

  3254. Reported: “I’m picking up at the genuinely old interpreter pair now: SQ1 2.089 and XMAS 2.272.”

    Action/result: Resumed the expanded-game comparison after permissions were corrected; inventoried the seven new game directories and their executable/container forms.

  3255. Reported: “The XMAS directory is internally coherent, but it preserves three installation methods.”

    Action/result: Matched active XMAS files to the original variants and classified apparent missing-volume census errors as multi-disk packaging rather than malformed resources.

  3256. Reported: “The oldest action-table difference is now source-confirmed.”

    Action/result: Disassembled SQ1/XMAS action 0x86; established the zero-operand unconditional 2.089 exit and the one-operand 2.272 immediate/confirmed selector.

  3257. Reported: “The apparent XMAS menu support is another important version boundary.”

    Action/result: Mapped 0x9c..0xa0 to operand-advance stubs with no menu state and confirmed 0x9b as a two-byte no-op.

  3258. Reported: “The new save files add useful game-data coverage, but they also caught an assumption in our helper.”

    Action/result: Parsed BC, MG, and SQ1.22 original saves; identified MG’s metadata/save object-count discrepancy instead of forcing later metadata semantics onto it.

  3259. Reported: “The old OBJECT question is resolved for SQ1 and XMAS: those files are plain, not XOR-transformed.”

    Action/result: Parsed the raw headers and inventory tables, added a transform-neutral parser, and added regression tests for both old files.

  3260. Reported: “I have enough evidence for a first durable pass.”

    Action/result: Updated the evidence/version ledgers and clean spec, qualified old metadata transforms, added partial 2.089/2.272 profiles, and corrected the 3.002.149 room-alias generalization using MH2.

  3261. Reported: “The documentation pass now distinguishes three kinds of result cleanly: exact same-build behavior, mapped subsystem equivalence, and partial profiles.”

    Action/result: Updated symbolic labels and the high-level dashboard with the new build associations, confirmed equivalences, and specific next source-first investigations.

  3262. Reported: “Verification is clean now: all 389 local tests pass and both mdBooks build.”

    Action/result: Corrected the single new XMAS expected-name assertion, reran the complete suite successfully, built both books, and confirmed that private game files and generated artifacts are absent from the diff.

  3263. Reported: “One remaining question is cheap enough to answer now: whether MH1/MH2 scripts visibly reference any of the unreadable directory entries.”

    Action/result: Ran the existing reference audit; it exposed an abort-on- unreadable-logic limitation before producing a result.

  3264. Reported: “The audit itself exposed a harness assumption.”

    Action/result: Made the audit record and skip unreadable source logics, added a regression test, and retained the explicit limitation on conclusions drawn from skipped scripts.

  3265. Reported: “The revised audit gives a useful answer.”

    Action/result: Classified MH1 as incomplete due to six directly referenced unreadable views; classified MH2 as inconclusive because 31 unreadable logics remain despite no direct bad references in readable scripts.

  3266. Reported: “The final expanded-game pass is verified.”

    Action/result: All 390 local tests pass, both mdBooks build, and git diff --check reports no whitespace errors; no QEMU confirmation was needed because the promoted deltas are explicit disassembly observations.

  3267. Reported: “The audit found several dashboard omissions, not new behavioral discoveries.”

    Action/result: Expanded PROGRESS.md with the early plain/XOR metadata boundary, XMAS distribution packaging, BC/MG/SQ1.22 save dimensions, partial-profile opcode ranges, MH1/MH2 completeness results, additional symbolic-map coverage, and the now-repeated cross-version workflow.

  3268. Reported: “I’m starting with the two highest-value source questions.”

    Action/result: Began source-first SQ1 2.089/XMAS 2.272 picture, object, input, sound, and save comparisons, with the MG save discrepancy queued behind those profile maps.

  3269. Reported: “The role reports show XMAS is close enough.”

    Action/result: Generated full local 16-bit disassemblies and used the existing relocation reports to anchor corresponding early routines.

  3270. Reported: “The picture scanner reveals a larger early boundary.”

    Action/result: Proved that both early scanners dispatch only 0xf0..0xf8; 0xf9 and 0xfa do not exist as picture commands in profiles 2.089 and 2.272.

  3271. Reported: “Both old builds use the same automatic-loop rule.”

    Action/result: Proved that both select direction loops on every eligible post-logic pass, without the cadence-countdown gate introduced by 2.411.

  3272. Reported: “I’ve now got two concrete early-version differences to carry forward.”

    Action/result: Extended the source comparison into parser slot bounds, word matching, sound event output, and save construction before editing the specification.

  3273. Reported: “The input path is also separating cleanly by age.”

    Action/result: Mapped six string slots in both builds; SQ1 uses exact word-sequence counts without 0x270f, while XMAS implements the later tail terminator.

  3274. Reported: “The sound comparison has exposed another real version step.”

    Action/result: Distinguished SQ1 unchanged control-byte output, XMAS whole-byte adjustment with signed clamp, and the later low-nibble-only adjustment rule.

  3275. Reported: “The old save constructors are substantially different.”

    Action/result: Mapped SQ1’s four-block envelope and XMAS’s five-block envelope, including source-derived selected-data object and metadata lengths.

  3276. Reported: “I have enough source coverage to start promoting the findings.”

    Action/result: Added portable executable models and focused regression tests for picture command bounds, automatic loops, old word matching, sound-control output, and early save dimensions.

  3277. Reported: “The executable models are in place.”

    Action/result: Ran 153 focused tests successfully and resolved MG as an artifact-version mismatch: the historical save predates and contradicts the selected interpreter’s source-derived block lengths.

  3278. Reported: “The specification now has portable profile rules.”

    Action/result: Promoted the new behavior to the clean-room spec while retaining addresses, source traces, and file-date evidence only in the reverse-engineering book.

  3279. Reported: “Verification is clean.”

    Action/result: All 396 local tests passed, both mdBooks built, and git diff --check found no whitespace errors.

  3280. Reported: “The old block-1 tail is now partly recoverable.”

    Action/result: Partitioned replay capacity/count, 39 key mappings, six live strings, six reserved string records, and terminal text state. Corrected block 4 to use configured replay capacity rather than active occupancy; left the earlier scalar prefix explicitly partial.

  3281. Reported: “One correction surfaced during the final source-to-spec cross-check.”

    Action/result: Added the oldest drivers’ device-selector-2 attenuation adjustment to the model, tests, evidence, and specification before their profile-specific direct/whole-byte output steps.

  3282. Reported: “I’ll treat the deleted MG save as retired historical evidence.”

    Action/result: Split the MG regression into a current metadata-derived 91-record test and an optional historical-save comparison, removing any requirement that the private save remain present.

  3283. Reported: “The opcode-to-handler map gives us a better route through the unknown prefix.”

    Action/result: Traced saved scalar words through display, horizon, navigation, motion rectangle, direction coupling, picture preparation, replay, and key-map handlers.

  3284. Reported: “The prefix has resolved into a complete field partition.”

    Action/result: Added a dedicated 2.089/2.272 block-1 layout and local partition test instead of reusing later profile offsets and names.

  3285. Reported: “I’m promoting the completed save partition now.”

    Action/result: Promoted semantic offsets and round-trip preservation while leaving canonical initial bytes outside current binary-synthesis claims.

  3286. Reported: “I’m picking up at the remaining early-interpreter subsystem edges.”

    Action/result: Selected SQ1 2.089 versus XMAS 2.272 object redraw and update ordering as the next source-first comparison.

  3287. Reported: “The portable question is narrower than the implementation difference.”

    Action/result: Traced both temporary object-list representations through membership, key generation, tie handling, composition, and refresh.

  3288. Reported: “This comparison found a real visible version delta.”

    Action/result: Established that 2.089 draws the earlier partition in object-number order while 2.272 drawing-key sorts both partitions.

  3289. Reported: “I’ll encode that delta in the reusable ordering model and tests.”

    Action/result: Added the profile-selectable ordering model, regression test, and portable specification rule.

  3290. Reported: “I’ve now confirmed that frame advancement, collision, control-footprint acceptance, placement, dirty rectangles, and the movement loop itself match after relocation.”

    Action/result: Recorded that the old movement wrappers implement the 2.089 redraw architecture but do not alter movement-state transitions.

  3291. Reported: “The opcode-table triage leaves six shared-action differences that still need semantic classification before promotion.”

    Action/result: Read each remaining SQ1/XMAS shared handler difference and separated portable state/output effects from cleanup, diagnostics, and temporary storage changes.

  3292. Reported: “Two more early action deltas are now definite.”

    Action/result: Established that SQ1 actions 0x4d and 0x4e leave autonomous-motion mode active while XMAS clears it.

  3293. Reported: “The target-motion handlers expose another important early rule.”

    Action/result: Established that 2.089/2.272 defer their first target direction and completion calculation, unlike 2.411 and later.

  3294. Reported: “The early profile boundary is sharpening nicely.”

    Action/result: Traced early show-picture and object-distance semantics and identified retained f15/text-window state plus byte wrapping.

  3295. Reported: “The new regression checks pass.”

    Action/result: Confirmed the early save, object-ordering, distance, and specification regressions together after correcting source addresses.

  3296. Reported: “Inventory was the last major full-EGA surprise.”

    Action/result: Established that 2.089/2.272 inventory is always acknowledgement-only and that interactive v25 selection begins later.

  3297. Reported: “I’ll commit the completed early-profile work now.”

    Action/result: Folded the established inventory result into the evidence record and portable specification before verification and commit.

  3298. Reported: “Verification is running across the full compatibility tests and both books.”

    Action/result: All 400 local tests passed with one optional historical MG-save test skipped, both mdBooks built, and git diff --check passed.

  3299. Reported: “I’m resuming the early-profile completion pass from the committed state.”

    Action/result: Selected the remaining ordinary-EGA input, presentation, restart, and view-preview handler differences for source classification.

  3300. Reported: “The tracker still contains stale byte-map saves language.”

    Action/result: Confirmed the early semantic save map is complete and separated optional pristine synthesis from gameplay conformance.

  3301. Reported: “The normal EGA input paths line up.”

    Action/result: Reduced input configuration, number prompts, line state, refresh, and erase differences to alternate-mode or timing branches.

  3302. Reported: “The source coverage now supports promotion.”

    Action/result: Promoted 2.089 and 2.272 to full-EGA valid-data gameplay profiles while retaining explicit save-synthesis limitations.

  3303. Reported: “A cross-check found one stale persistence paragraph.”

    Action/result: Corrected the early save status to complete field/reserved assignment with caller-supplied pristine reserved-byte initialization, and added structural promotion regressions.

  3304. Reported: “The focused suite caught a useful structural detail.”

    Action/result: Kept the 2.272 binary-interchange claim name contiguous in Markdown so the conformance regression can identify it reliably.

  3305. Reported: “The early-profile promotion now passes its focused tests.”

    Action/result: Moved past incomplete MH inputs and audited current partial tracker entries for the next source-backed actionable task.

  3306. Reported: “The next concrete gap is in the suite manifest.”

    Action/result: Identified all-picture, movement, and overlay evidence that had passed separately but was absent from an aggregate suite layer.

  3307. Reported: “I’m adding a separate qemu-exhaustive layer.”

    Action/result: Added opt-in inherited exhaustive scheduling for all current picture, movement, and overlay original-engine cases.

  3308. Reported: “The exhaustive dry run is correct.”

    Action/result: Launched the first aggregate local, smoke, broad, and exhaustive original-engine compatibility run.

  3309. Reported: “The aggregate run has passed all 401 local tests.”

    Action/result: Confirmed both books and the initial parser snapshot cases before continuing into the remaining QEMU layers.

  3310. Reported: “The first aggregate run exposed a manifest dependency bug.”

    Action/result: Added deterministic fuzz-corpus generation as an explicit smoke prerequisite after the clean-build run lacked named fuzz cases.

  3311. Reported: “The corrected exhaustive run has reached the all-picture stage.”

    Action/result: Passed 403 local tests, both mdBooks, parser/fuzz smoke, all 8 broad pictures, all 19 view cases, and all 74 present pictures.

  3312. Reported: “The movement stage exposed a fixture-capacity limit.”

    Action/result: The unchunked 37-case movement disk filled while copying fixture 14; no interpreter mismatch caused the stop.

  3313. Reported: “I’m splitting object probes into snapshot chunks.”

    Action/result: Added reusable chunk-specific snapshot paths and ten-case movement/overlay batching without changing case semantics.

  3314. Reported: “The deterministic movement set reached 34 matches.”

    Action/result: The next autonomous-random case produced a stationary capture, which is valid because the selected random direction can be zero.

  3315. Reported: “I’m marking nondeterministic cases explicitly.”

    Action/result: Classified random_motion_visible_somewhere as exploratory and excluded it from the required exhaustive gate while retaining both deterministic random-motion clearing cases.

  3316. Reported: “The filter and manifest tests pass.”

    Action/result: Passed 25 focused tests and confirmed the new --deterministic-only command-line selection.

  3317. Reported: “The named object sweep is clean.”

    Action/result: Matched all 36 deterministic movement cases and all 24 overlay cases in chunked original-engine runs with no errors.

  3318. Reported: “I’m launching the complete inherited aggregate.”

    Action/result: Started one report covering local checks, both books, smoke/broad probes, all pictures, movement, and overlays.

  3319. Reported: “The aggregate’s local phase is clean.”

    Action/result: Passed 403 tests with one optional historical-save skip, built both mdBooks, and passed opcode-evidence checks.

  3320. Reported: “All smoke and broad semantics match.”

    Action/result: Regenerated the 1,062-case fuzz corpus and matched parser, picture-fuzz, 8-picture, and 19-view original-engine probes.

  3321. Reported: “All 74 picture resources matched inside the aggregate.”

    Action/result: Completed five chunked picture carousels without a disk prompt, mismatch, or capture error.

  3322. Reported: “The complete aggregate report passed all 15 commands.”

    Action/result: Recorded 36/36 deterministic movement and 24/24 overlay matches in build/compatibility-suite/qemu_exhaustive_001.json.

  3323. Reported: “The next actionable gap is the result adapter.”

    Action/result: Defined a language-neutral versioned bundle with canonical 160 by 168 EGA-index frame observations and SHA-256 digests.

  3324. Reported: “The spec guard caught an evidence-side tool path.”

    Action/result: Kept normative interchange semantics in spec/ and moved repository adapter commands exclusively to the evidence book.

  3325. Reported: “The first export exposed a private-input dependency.”

    Action/result: Removed game-resource imports from the adapter by making EGA capture normalization self-contained.

  3326. Reported: “The first portable reference bundle is complete.”

    Action/result: Exported 165 successful deterministic visual cases with 165 frame artifacts and self-compared all 165 with zero failures.

  3327. Reported: “The result adapter is hardened for independent producers.”

    Action/result: Added duplicate-ID, canonical-frame, artifact-digest, and producer-error validation; all focused adapter tests pass.

  3328. Reported: “Final verification is clean.”

    Action/result: Passed 408 local tests with one optional historical-save skip, built both mdBooks, checked opcode evidence, and passed the diff whitespace check.

  3329. Reported: “I’m auditing the next concrete resource gap.”

    Action/result: Selected KQ1’s four suspect sound-directory entries for source-first classification against the 2.917 volume reader.

  3330. Reported: “The KQ1 entries point beyond the selected volume.”

    Action/result: Decoded offsets 0x20e2f..0x2126b, all at least 43,812 bytes beyond VOL.2; ruled out an alternate sound-record format.

  3331. Reported: “The census now identifies out-of-volume offsets directly.”

    Action/result: Added a precise parser diagnostic and focused regression; 18 resource/census tests passed.

  3332. Reported: “I’m applying the same classification to KQ4D.”

    Action/result: Distinguished missing KQ4 volumes from KQ4D’s sound-table tail and inspected the reachable target bytes.

  3333. Reported: “KQ4D exposed an addressability bug in the local parser.”

    Action/result: Established that byte resource numbers make entries above 255 unreachable; prior pseudo-entries 387 through 661 were tool artifacts.

  3334. Reported: “Sound 198 is an accidental picture alias.”

    Action/result: Confirmed picture-nibble metadata, picture command bytes, and failure under the sound channel parser.

  3335. Reported: “The corrected resource audit passes.”

    Action/result: Passed 34 focused tests and reduced KQ4D to 15 unreferenced addressable bad-header entries plus the unreferenced picture alias.

  3336. Reported: “I’m scaling the portable bundle beyond SQ2.”

    Action/result: Selected eight completed Gold Rush source-mapped visual probes and mapped their cases/qemu report envelope.

  3337. Reported: “The v3 adapter needs path-safe artifact names.”

    Action/result: Separated stable probe/label IDs from sanitized, digest-suffixed artifact filenames after focused tests caught / paths.

  3338. Reported: “The first v3 reference bundle is clean.”

    Action/result: Exported 32/32 Gold Rush 3.002.149 cases with 32 artifacts and self-compared all cases with zero failures.

  3339. Reported: “Raw save digests would over-constrain independent engines.”

    Action/result: Kept nonvisual persistence output pending a semantic canonical form instead of requiring DOS-era serialized references.

  3340. Reported: “The complete verification pass is clean.”

    Action/result: Passed 414 tests with one optional historical-save skip, built both mdBooks, checked opcode evidence, and passed git diff --check.

  3341. Reported: “I’m auditing the partial core runtime contract.”

    Action/result: Compared spec/src/runtime_state.md against promoted input, object, sound, room, and persistence subsystem contracts.

  3342. Reported: “The first cycle stage is too vague.”

    Action/result: Identified that timer/sound IRQ work was incorrectly grouped with synchronous pacing and input processing.

  3343. Reported: “I’m reading the main-cycle instructions directly.”

    Action/result: Regenerated the decrypted SQ2 executable and disassembled image 0x0150, plus the six helpers called around the logic boundary.

  3344. Reported: “The cycle ordering and state lifetimes are now exact.”

    Action/result: Separated asynchronous ticks; mapped transient status, input, direction, pre-motion, logic re-entry, cleanup, and post-update phases; promoted core runtime coverage to complete.

  3345. Reported: “The first guard exposed a Markdown-wrapping assumption.”

    Action/result: Scoped the structural check to the cycle section and normalized whitespace so it tests the behavioral contract rather than source line breaks.

  3346. Reported: “The instruction-level cycle trail is now preserved.”

    Action/result: Recorded the reproducible disassembly command, direct call order, transient-state clears, re-entry branch, text-mode gate, and asynchronous-tick inference in the evidence book.

  3347. Reported: “The next locally actionable gap is portable nonvisual results.”

    Action/result: Chose the compatibility bundle’s frame-only implementation gap after classifying incomplete MH inputs and alternate display/IRQ work as blocked or conditional under the current target.

  3348. Reported: “The bundle now carries semantic values without engine layout.”

    Action/result: Added canonical JSON observations for deterministic state, input, ordered sound commands, and persistence outcomes, with recursive typed comparison and JSON Pointer difference paths.

  3349. Reported: “The complete local verification gate passes.”

    Action/result: Passed 418 tests before the final artifact-validation guard, with three optional generated-GR-save skips and the intentionally absent historical-MG-save skip; checked opcode evidence, built both mdBooks, and passed git diff --check.

  3350. Reported: “The result-format review tightened canonical validation.”

    Action/result: Rejected malformed known observations and statuses, required lowercase hexadecimal frame digests, and verified artifact length and EGA-index range in addition to content hashes.

  3351. Reported: “The tightened format passes the complete gate.”

    Action/result: Passed 419 tests with the same four expected optional skips, checked opcode evidence, built both mdBooks, and passed git diff --check after the final validation guard.

  3352. Reported: “The handoff summary had fallen behind the dashboard.”

    Action/result: Recorded the completed cycle and nonvisual-result work, marked deleted build/ paths as historical generated locations, and restored the incomplete MH1/MH2 resource-set blocker to the next-work summary.

  3353. Reported: “The trace has narrowed this down nicely.”

    Action/result: Confirmed that SQ2 prepared and inverted the correct glyph and redirected vector 43h, while the VGA BIOS call never read the redirected scratch bytes.

  3354. Reported: “I’m moving to the targeted workaround now.”

    Action/result: Selected an alternate VGA option ROM as the narrow test; the system BIOS and immutable game input remained unchanged.

  3355. Reported: “The legacy ROMs are downloaded and checksum-pinned.”

    Action/result: Downloaded official LGPL VGABIOS 0.7a standard and Cirrus binaries into disposable build/ output and recorded their SHA-256 values.

  3356. Reported: “I’m testing the standard VGA variant first.”

    Action/result: Booted FreeDOS with the stock 0.7a standard VGA option ROM, advanced SQ2 to its first inverse dialog, and captured the same repeated glyph corruption.

  3357. Reported: “Merely swapping SeaVGABIOS for an older binary is not enough.”

    Action/result: Rejected VGA BIOS age alone as the cause and focused the requirement on honoring the current INT 43h vector at glyph-draw time.

  3358. Reported: “I’m inspecting the firmware’s AH=09h path now.”

    Action/result: Downloaded the matching VGABIOS source and found that its planar, CGA, and linear graphics character paths use private font arrays.

  3359. Reported: “There is enough unused space inside the ROM.”

    Action/result: Located verified option-ROM padding and designed a small planar EGA fetch routine that reads font offset/segment from vector 43h while retaining the existing pixel renderer.

  3360. Reported: “I’m adding a reproducible patch builder.”

    Action/result: Added a pinned validator/patcher and NASM source with source checksum, exact call-site, empty-area, size, and final ROM checksum guards.

  3361. Reported: “The patched VGA BIOS works.”

    Action/result: Booted QEMU with the generated ROM and advanced SQ2 to the first playable-room dialog; all inverse text rendered as distinct, readable black-on-white glyphs without FIXAGI.COM.

  3362. Reported: “The root cause is confirmed as VGA BIOS behavior.”

    Action/result: Classified QEMU’s bypass of the live font vector as the compatibility fault; no game executable, resource, DOS image, or system BIOS patch is required.

  3363. Reported: “I’m turning this into a durable project setup now.”

    Action/result: Added patcher tests, README/manual QEMU instructions, compatibility guidance, and the complete static/dynamic evidence trail.

  3364. Reported: “I’ll put the experimental FIXAGI helper back into a non-dangerous state.”

    Action/result: Restored PAM0 to readable/write-protected mode after the corrected font-copy verification and documented that this utility cannot repair a VGA BIOS that ignores vector 43h.

  3365. Reported: “The durable change is scoped to the analysis environment.”

    Action/result: Kept the downloaded and patched ROM under disposable build/, with only its deterministic generator, patch source, tests, and documentation entering the repository.

  3366. Reported: “The focused tests and both mdBooks pass.”

    Action/result: Passed the three patcher guards, rebuilt a byte-identical corrected ROM with SHA-256 cfbbc5e3...f01247, built both books, and passed the whitespace check.

  3367. Reported: “The automated QEMU harnesses currently launch the bundled VGA BIOS.”

    Action/result: Audited all direct QEMU command construction sites and identified a common launch-argument helper as the smallest durable integration point.

  3368. Reported: “I’m adding one launch-argument helper.”

    Action/result: Made harnesses automatically select the generated corrected ROM when present, with explicit environment controls for an alternate ROM or the QEMU default.

  3369. Reported: “The harness integration passes 45 focused tests.”

    Action/result: Passed snapshot, picture/view carousel, fuzz, save/restore, patcher, and environment-selection tests after the shared launch change.

  3370. Reported: “I’m running the full local compatibility gate now.”

    Action/result: Passed all 425 local tests with four expected optional fixture skips, the full compatibility runner, both mdBooks, opcode evidence checks, deterministic ROM comparison, and whitespace checks.

  3371. Reported: “I’ll make the unmodified VGABIOS a tracked third-party input.”

    Action/result: Established the artifact policy: track the pristine ROM, license, provenance, patch source, and builder while keeping the patched derivative exclusively under ignored build/.

  3372. Reported: “I’ve confirmed the redistributable input is the official LGPL VGABIOS 0.7a standard ROM.”

    Action/result: Copied the exact 41,472-byte upstream binary and LGPL 2.1 text under third_party/vgabios/ and reverified SHA-256 cd9fdd6a...246c6.

  3373. Reported: “The build flow will no longer depend on a network download.”

    Action/result: Changed the builder to consume and validate the tracked pristine ROM, require a 44-byte assembled patch, verify source and output digests, and atomically emit only the generated ROM under build/.

  3374. Reported: “The normal FreeDOS setup will invoke that step.”

    Action/result: Integrated VGA BIOS build/verification into setup_freedos_image.py, with --skip-vgabios reserved for intentional bundled-firmware control environments.

  3375. Reported: “The artifact boundary is now explicit.”

    Action/result: Added upstream binary/source URLs and checksums, the LGPL text, generated-output policy, and deterministic patched digest to the repository and operational documentation.

  3376. Reported: “I’m adding a dedicated evidence-book chapter next.”

    Action/result: Added a self-contained chapter covering symptoms, rejected hypotheses, decisive trace, patch map, artifacts, reproduction, validation, troubleshooting, and license/source availability.

  3377. Reported: “I’m doing the final end-to-end verification now.”

    Action/result: Built a separate temporary FreeDOS image through the integrated setup path without touching the populated manual image, then passed all 427 tests with four expected skips, the complete compatibility runner, both mdBooks, opcode evidence, and whitespace validation.

  3378. Reported: “I’m staging the complete reproducible solution now.”

    Action/result: Staged the pristine upstream BIOS, license/provenance, patch source and builder, FreeDOS/harness integration, tests, dashboard, and operational/evidence documentation while excluding generated output and unrelated local files.

  3379. Reported: “The upstream license text contains formatting artifacts.”

    Action/result: Removed one whitespace-only line and the extra final blank line from the license copy so the staged repository passes its whitespace gate without changing the license wording.

  3380. Reported: “I’ll rebuild the FreeDOS image from scratch.”

    Action/result: Confirmed the previous generated image was 32 MiB and enumerated 16 top-level private game directories for the requested copy.

  3381. Reported: “I’m checking the available disk-formatting tools.”

    Action/result: Confirmed mtools supplies partition-aware FAT formatting and boot-sector templating, allowing a host-side reproducible large image without an interactive DOS installation.

  3382. Reported: “I’m going to make the builder create a fresh 1 GiB disk.”

    Action/result: Added active FAT16-LBA MBR construction, enlarged FAT16 formatting, source boot-code preservation, full FreeDOS tree transfer, atomic output replacement, and configurable 64-2048 MiB sizing.

  3383. Reported: “The geometry tests pass.”

    Action/result: Added deterministic tests for the 1 GiB partition entry, CHS encoding/saturation, active/type/LBA fields, and invalid source MBRs.

  3384. Reported: “The setup process is still downloading in the background.”

    Action/result: Observed the slow transfer through its temporary filename; the atomic downloader prevented the partial archive from being accepted as the persistent FreeDOS cache.

  3385. Reported: “The 1 GiB FAT16 volume is populated.”

    Action/result: Copied all 16 game directories, matched every recursive host/DOS file count, and measured 1,009,057,792 bytes free afterward.

  3386. Reported: “I’m doing the final boot test.”

    Action/result: Booted the enlarged type-0x0e image with the patched VGA BIOS and reached the FreeDOS C:\> prompt; recorded the benign legacy CHS consistency warning for this LBA volume.

  3387. Reported: “I’m running the full local test suite and both mdBooks now.”

    Action/result: Passed 431 tests with four expected optional-fixture skips, built both mdBooks, checked opcode evidence and whitespace, and retained the populated boot-tested 1 GiB image as disposable build output.

  3388. Reported: “I’ll regenerate the 1 GiB FreeDOS image.”

    Action/result: Rebuilt the deleted FreeDOS image and patched VGA BIOS from tracked inputs using the committed large-image setup path.

  3389. Reported: “The FreeDOS download is still in progress.”

    Action/result: Allowed the atomic temporary-cache transfer to complete; no partial ZIP or disk image was exposed as a completed artifact.

  3390. Reported: “The image is rebuilt and populated.”

    Action/result: Copied all 16 top-level private game directories, matched every recursive host/DOS file count, confirmed the exact 1 GiB raw size, and measured 1,009,057,792 bytes free on the resulting FAT volume.

  3391. Reported: “I’m resuming from the resource census and the opening-route reconstruction.”

    Action/result: Resumed the SQ1.22 game-specific playthrough detour from canonical resources and divided the remaining analysis into Kerona, Ulence Flats, and Deltaur phases.

  3392. Reported: “The indexer has already exposed a useful fact: the game declares 202 as the maximum, while all positive award sites sum to 221.”

    Action/result: Enumerated all score mutations, confirmed logic 104’s maximum-score assignment, and identified mutually exclusive awards, reversible awards, and penalties that prevent a naive positive-site sum from describing a playable route.

  3393. Reported: “The Kerona route is resolving into two distinct puzzle chains.”

    Action/result: Source-traced the survival-kit expansion, Orat water solution, spider-droid boulder sequence, rock/geyser point reversal, acid traversal, reflective-glass barrier, Orat proof handoff, cartridge reader, and skimmer departure.

  3394. Reported: “The city phase shows the intended economy rather than a direct corridor.”

    Action/result: Reconstructed the skimmer sale, three-beer sector-HH clue, random slot-machine funding dependency, correct flight-droid and cruiser purchases, mutually exclusive ship-price branches, droid load, and sector entry.

  3395. Reported: “I have enough structure to start preserving the result.”

    Action/result: Added a dedicated SQ1.22 playthrough-analysis chapter, indexed it in the evidence mdBook, promoted the workstream to PROGRESS.md, and changed the analysis index to extract maximum-score assignments directly from logic actions.

  3396. Reported: “The score discrepancy is now narrower and more informative.”

    Action/result: Proved that the two ship awards and two vent-entry awards are mutually exclusive, retained the required rock-taking deduction, and isolated the remaining over-202 ambiguity to reachable Deltaur and optional-interaction branches rather than forcing an unsupported ledger.

  3397. Reported: “I’m rendering the remaining room pictures as a navigation cross-check.”

    Action/result: Rendered the present pictures from 34 through 65 into a disposable contact sheet, visually confirming Ulence exteriors, the Deltaur approach and corridor families, the Star Generator chamber, launch bay, ending scene, and numeric self-destruct keypad. Missing picture numbers 40, 44, and 63 were absent canonical entries rather than renderer failures.

  3398. Reported: “A shared one-time flag resolves the largest ambiguity.”

    Action/result: Proved the five-point boulder and ten-point cave outcomes are alternative Orat/spider resolutions, identified penalty-free rock acquisition in room 25, corrected the required first Keronian audience, and derived the designed score equation 221 - 4 - 2 - 10 - 3 = 202. Isolated a possible 205/202 overscore if both guard-defeat awards can coexist.

  3399. Reported: “I’m doing a full local verification pass now.”

    Action/result: The focused SQ1.22 message-decoder/index checks passed. A first whole-suite run selected SQ1.22 globally and demonstrated that legacy corpus/hash tests are intentionally SQ2-specific; rerunning with the intended SQ2 fixture passed all 432 tests with four optional-fixture skips, both mdBooks, opcode-evidence validation, and whitespace checks.

  3400. Recorded: Final static route refinements.

    Action/result: Added phase score checkpoints 61, 110, 153, and 202, and included the three separately scored disguised-guard interactions: talk, kiss, and answering yes to the King’s Quest II ownership question.

  3401. Reported: “I’ll apply the same resource-only method to SQ2.”

    Action/result: Selected games/SQ2 explicitly and began reconstructing its route from local logic, message, vocabulary, object, picture, and view resources without consulting external game material.

  3402. Reported: “SQ2 immediately exposed a corpus edge.”

    Action/result: Found that logic-directory entry 141 names VOL.0:0x1ffff, where no valid resource header exists. Changed the playthrough indexer to preserve the unreadable-entry evidence and continue indexing all readable resources.

  3403. Reported: “The SQ2 profile is now indexed.”

    Action/result: Indexed 118 readable logics, 2,472 parser conditions, 182 room transitions, 70 score mutations, and 40 inventory slots. Confirmed logic 104 assigns the maximum score of 250.

  3404. Reported: “The score structure is already beginning to close.”

    Action/result: Grouped the score sites by story phase and traced their parser, inventory, room, and flag guards to distinguish cumulative awards from duplicate parser branches and alternative puzzle solutions.

  3405. Reported: “The score ledger now closes exactly.”

    Action/result: Proved that the 283 positive score sites reduce to the declared 250 after excluding 33 points from alternate airlock, spore, rope, gem, stone, and platform-guard branches. No negative score action is required by the designed route.

  3406. Reported: “The full static story path is now coherent.”

    Action/result: Reconstructed the route through XOS 4, Labion, Vohaul’s asteroid, clone-launch cancellation with code SHSR, escape-pod launch, and terminal suspended animation in logic 93, with phase checkpoints 10, 166, and 250.

  3407. Reported: “I’ve completed the first static SQ2 route reconstruction and am tightening the score ledger now.”

    Action/result: Reviewed the preserved chapter against the extracted event groups and found two transcription errors confined to its compact numeric ledger, not the route or phase totals.

  3408. Reported: “The chapter’s narrative route is sound, but the compact numeric ledger has two transcription mistakes.”

    Action/result: Corrected the Labion and asteroid award lists. An apparent duplicated inventory loop was then checked against the source directly.

  3409. Reported: “One correction to my last update.”

    Action/result: Confirmed the apparent duplicated inventory loop came from overlapping terminal excerpts, not the source file. The indexer already emits each of SQ2’s 40 inventory slots exactly once.

  3410. Reported: “I’m updating three things together.”

    Action/result: Promoted the SQ2 250-point candidate and remaining replay work into PROGRESS.md, and aligned the highest-value work queue with both SQ1.22 and SQ2 playthrough validation.

  3411. Reported: “The corrected ledger now sums mechanically.”

    Action/result: Verified 10 + 156 + 84 = 250 independently, then enumerated the 75 present picture-directory entries for a visual route cross-check.

  3412. Reported: “The visual sweep rendered all 74 valid SQ2 pictures.”

    Action/result: Rendered 74 pictures without error and found that picture 147 points to an invalid resource header at VOL.0:0x2ffff. Preserved it as unreadable directory evidence parallel to logic 141 rather than treating bytes outside a valid record as picture semantics.

  3413. Reported: “The contact sheet corroborates the route’s geography.”

    Action/result: Visually checked the Labion forest, swamp, cave, shuttle, asteroid, Vohaul machinery, ruptured tube, pod-bank, and escape-pod families. Recorded that pictures 140 through 146 are title/interface imagery and marked the picture-rendering validation step complete.

  3414. Reported: “The regenerated index reproduces every claimed count exactly.”

    Action/result: Rebuilt the SQ2 JSON index and independently confirmed 118 readable logics, 2,472 parser conditions, 182 transitions, 70 score events, 40 inventory slots, one unreadable logic entry, and logic 104’s 250-point maximum assignment.

  3415. Reported: “I’m at the verification pass now.”

    Action/result: Ran the focused message/table tests, all 432 local tests with four expected optional-fixture skips, both mdBook builds, the SQ2 opcode-evidence check, and the whitespace check successfully.

  3416. Reported: “Everything passes.”

    Action/result: Completed the final review while preserving the boundary between the resource-proven static route and the exact movement, hazard-timing, and parser-input stream still requiring original-engine replay.

  3417. Reported: “I’ll apply the same clean-room, resource-only reconstruction to games/KQ1.”

    Action/result: Began a KQ1 maximum-score route reconstruction from local logic, message, vocabulary, object, picture, and view resources, with generated artifacts confined to build/ and no changes to private game inputs.

  3418. Reported: “KQ1 indexes cleanly.”

    Action/result: Indexed 90 readable logic resources, 1,478 parser conditions, 277 room transitions, 180 score mutations, and 27 inventory slots. Confirmed logic 0 declares a 158-point maximum; all positive score sites sum to 269 before duplicate, reversible, and alternative branches are resolved.

  3419. Reported: “The 158-point structure is resolving into three treasure chains.”

    Action/result: Reconstructed the mirror chain through the well and dragon, the shield chain through the witch, rat, and leprechauns, and the chest chain through the gnome, beanstalk, and giant. Static score branches consistently assign more points to nonviolent water, food, music, and sleep solutions than to destructive or treasure-surrender alternatives.

  3420. Reported: “The score ledger now closes at 158.”

    Action/result: Distinguished show carrot from give/feed carrot in the parser branches. Showing it awards five points while retaining the two-point carrot; giving it first subtracts those two points. Combined with net water, bowl, cheese, and mushroom exchanges, the selected static route now totals exactly the declared maximum.

  3421. Reported: “The route topology is now coherent.”

    Action/result: Traced the condor’s jump/contact animation through room 80 to room 48, the fall into rooms 73 through 78, and the rat/leprechaun complex. Separated that shield route from the independently planted beanstalk route to the giant’s cloud region, and identified the high-score mushroom exit from room 78 to room 36.

  3422. Reported: “All 82 present KQ1 pictures render successfully.”

    Action/result: Rendered the complete present picture set and visually corroborated the surface map, well and dragon caves, condor landing, underground complex, beanstalk/cloud region, giant territory, castle return, and ending imagery. Pictures 83 and 84 are title/transition assets rather than traversable rooms.

  3423. Reported: “I now have enough corroborated structure to preserve the complete static candidate.”

    Action/result: Added a dedicated KQ1 playthrough-analysis chapter, indexed it in the evidence mdBook, promoted the 158-point candidate and remaining replay work into PROGRESS.md, and expanded the highest-value queue to cover all three game-level candidate playthroughs.

  3424. Reported: “The preserved ledger checks mechanically.”

    Action/result: Independently summed phase totals 4 + 79 + 23 + 23 + 25 + 4 = 158 and applied alphabet mirroring to the local parser token ifnkovhgroghprm, yielding rumplestiltskin without relying on an external solution source.

  3425. Reported: “I’m running the same verification gate used for SQ1.22 and SQ2 now.”

    Action/result: Ran 15 focused KQ1 decoder/table tests, all 432 SQ2-based regression tests with four expected optional-fixture skips, both mdBook builds, the opcode-evidence check, and the whitespace check successfully.

  3426. Reported: “Verification is clean.”

    Action/result: Completed the final diff review while retaining a clear distinction between the resource-proven 158-point route and the exact movement, command, random-event, and timing stream still requiring an original-interpreter QEMU replay.

  3427. Reported: “I’ll apply the same resource-only method to games/KQ2.”

    Action/result: Began a KQ2 maximum-score route reconstruction from local logic, message, vocabulary, object, picture, and view resources, keeping private game inputs immutable and generated analysis under build/.

  3428. Reported: “KQ2 indexes cleanly as a larger game.”

    Action/result: Indexed 133 readable logics, 1,406 parser conditions, 290 room transitions, 82 score mutations, and 35 named inventory objects in slots 50 through 84. Confirmed logic 180 assigns the 185-point maximum; positive score sites sum to 222 before alternate and consumed-item branches are resolved.

  3429. Reported: “The route is organizing around three key quests and a final rescue world.”

    Action/result: Identified high-value branches using flowers for the mermaid, returning Neptune’s trident, transforming the snake with the bridle, recovering the covered nightingale for the lamp, using disguise or the cross at the poisoned lake, helping Grandma with soup, feeding the lion ham, and rescuing the golden fish. Lower-valued violent and treasure- payment branches remain separate negative conformance cases.

  3430. Reported: “I’ve got the three-key structure and the major alternate- solution branches.”

    Action/result: Began closing the exact 185-point ledger by tracing the variable Dracula bonus, Neptune’s trident sequence, duplicate candle sites, and the five final-world fish awards rather than assuming every positive score site can coexist.

  3431. Reported: “The score equation exposed an important branch detail.”

    Action/result: Proved that returning Neptune’s trident for four points and waving it personally for two points are alternatives: either action opens the clam and sets the same one-time state. Continued tracing the shared three-point variable through logic 67 to determine which bonus applications belong to the winning Dracula sequence.

  3432. Reported: “The 185-point equation now closes exactly.”

    Action/result: Proved that the ruby ring and black cloak share one three-point disguise-completion award, and identified the variable bonuses as three points for resolving Dracula plus five on first reaching Valanice. With mutually exclusive branches removed, the declared route totals exactly 185.

  3433. Reported: “The visual sweep rendered all 108 present KQ2 pictures.”

    Action/result: Rendered every present picture without a record error and built a disposable contact sheet. The images corroborate the Kolyma map, underwater kingdom, mountain and castle quests, iridescent final world, and quartz-tower ending geography.

  3434. Reported: “One static edge case is important enough not to bury.”

    Action/result: Found that logic 48 clears its side markers after each one-point bridge traversal and checks for collapse only when the traversal counter is already greater than six. Recorded 191 as an original-engine overscore hypothesis alongside the exact declared-max 185 route.

  3435. Recorded: Preserved the KQ2 static playthrough reconstruction.

    Action/result: Added the dependency-ordered three-key route, exact score ledger, terminal wedding state, lower-scoring alternatives, deaths and dead ends, picture evidence, bridge hypothesis, and replay requirements to a dedicated evidence chapter and the project progress tracker.

  3436. Reported: “The documentation and tracker updates are in place.”

    Action/result: Regenerated the KQ2 index; all 15 focused message/table tests passed. The full SQ2-anchored suite passed all 432 tests with four expected optional-fixture skips, both mdBooks built, opcode evidence remained synchronized, and whitespace validation passed.

  3437. Reported: “I’ll apply the same clean-room, resource-only workflow to games/KQ3.”

    Action/result: Began a KQ3 maximum-score winning-route reconstruction from local logic, message, vocabulary, object, picture, and view resources. Kept the private game input immutable and reserved QEMU for later validation of ambiguities and an exact replay.

  3438. Reported: “KQ3 is larger and much more parser-heavy than the earlier King’s Quest games.”

    Action/result: Indexed 125 readable logics, 2,994 parser conditions, 277 room transitions, 67 score mutations, and 55 inventory slots with no decode failures. Confirmed logic 101 declares a 210-point maximum and began separating raw ingredients, transformed spell products, and the wizard-supervision timing state.

  3439. Reported: “The score ledger has a clean shape.”

    Action/result: Summed 215 positive score-site points and proved that the two four-point logic-14 branches and two one-point dog-fur branches are same-result alternatives. Excluding those duplicate five points yields the declared 210. Confirmed all seven successful spell preparations are independent, one-time ten-point events.

  3440. Reported: “The full route now resolves into four coherent acts.”

    Action/result: Reconstructed the wizard-house stealth/timed-chore phase, Llewdor ingredient and seven-spell phase, poisoned-porridge liberation, and pirate/mountain/Daventry finale. Identified logic 74’s family reunion as the terminal completion sequence after the four-point castle entry.

  3441. Reported: “All 97 present KQ3 pictures render successfully.”

    Action/result: Built a disposable contact sheet and visually confirmed Manannan’s house/laboratory, Llewdor, bandit tree, pirate ship, landing beach, snowy mountain labyrinth, ruined Daventry, dragon cloud land, and restored castle. Found no malformed picture-directory entry.

  3442. Reported: “I have enough evidence to preserve the KQ3 result.”

    Action/result: Added a dedicated evidence chapter covering the timed-safe house strategy, all scored acquisitions, seven exact spell recipes, poisoned-porridge liberation, pirate/mountain/dragon route, 210-point ledger, terminal assertions, deaths, dead ends, and replay requirements. Added KQ3 to the project tracker and evidence-book summary without promoting game-specific material into the portable specification.

  3443. Reported: “The route and ledger are now documented.”

    Action/result: Regenerated the KQ3 index and passed all 15 focused message/table tests. The full SQ2-anchored suite passed all 432 tests with four expected optional-fixture skips; both mdBooks built, opcode evidence remained synchronized, and whitespace validation passed.

  3444. Reported: “I’ll do the full games/KQ4 next with the same evidence standard.”

    Action/result: Began a KQ4 maximum-score winning-route reconstruction from its local interpreter profile and canonical resources. Kept the private game directory immutable and directed generated indexes and renders to build/playthrough/kq4.

  3445. Reported: “KQ4 is the full 3.002.086 v3 build, not the later demo.”

    Action/result: Confirmed the combined KQ4DIR/prefixed-volume profile. The first index pass exposed a message-table decoding failure at message 148; began distinguishing a malformed/missing-volume record from an incorrect v3 decoding assumption before altering the reusable indexer.

  3446. Reported: “I’m narrowing the failure before touching the decoder.”

    Action/result: Scanned every present KQ4 logic record. The failures affected many dictionary-expanded records rather than one damaged resource. Raw expanded message regions contained readable text while unconditional XOR produced the missing terminators.

  3447. Reported: “The raw KQ4 records resolve the ambiguity.”

    Action/result: Compared KQ4 and GR direct/dictionary-expanded records. KQ4 has 174 expanded and three direct logic records; the direct records’ message text requires the repeating key while expanded records are already plain. GR exhibits the same storage-dependent distinction.

  3448. Reported: “The executable confirms the exact rule.”

    Action/result: Read KQ4 image 0x30f7..0x3365 and logic setup 0x13d9..0x1498. A direct read sets word [0x0f5e] to one, dictionary expansion clears it, and logic setup calls the repeating-key XOR helper only when that word is nonzero. Updated the disassembler/indexer to select message decoding from the record transform and added a plain-text decoder test.

  3449. Reported: “The corrected pass is clean: all 177 present KQ4 logics decode.”

    Action/result: Generated the complete KQ4 logic/message index: 5,148 parser predicates, 469 room transitions, 86 score-changing sites, no unreadable logic, and declared maximum score 230. Began clustering score events and ordering story dependencies.

  3450. Reported: “The score ledger now reconciles exactly.”

    Action/result: Distinguished alternate room copies from sequential awards. Lolotte’s three completed errands each award seven points, while the board’s first two successful crevice crossings each award two. The resulting intended ledger totals exactly 230.

  3451. Reported: “The first picture sweep exposed another reusable-tool assumption rather than a KQ4 defect.”

    Action/result: Found agi_graphics.py still opened v2 split directory names directly. Routed picture/view lookup through the established version-aware resource reader so v3 combined directories and expanded records can be rendered without special-case copies.

  3452. Reported: “The v3 payload routing works, but the render attempt found a second, narrower limitation.”

    Action/result: Confirmed the patterned-brush tables still use SQ2 image offsets. The SQ2 graphics baseline passed all 91 focused tests. Rendered a clearly qualified brush-disabled KQ4 contact sheet with all 146 locally readable pictures; pictures 150–151 remain unavailable with missing KQ4VOL.6.

  3453. Reported: “The dependency graph is now coherent enough to preserve.”

    Action/result: Reconstructed the maximum route through trading, ocean, haunted manor, crypt, swamp, three Lolotte errands, castle escape, and ending. Identified the crown-then-recatch frog sequence as necessary to retain both its direct two-point award and separate five-point kiss sequence.

  3454. Reported: “I’m writing the evidence chapter with that non-obvious branch.”

    Action/result: Added the full KQ4 playthrough chapter, 230-point grouped ledger, global timing/resource constraints, death/dead-end inventory, and deterministic replay requirements. Added KQ4 to the evidence-book summary and high-level tracker without promoting game-specific material into the portable specification.

  3455. Reported: “Verification is clean.”

    Action/result: Confirmed expanded logic 0 decodes its King’s Quest IV version message and direct logic 97 decodes Like a heavy blanket, darkness enfolds you. Regenerated the 177-logic index. The full suite passed 433 tests with four expected optional-fixture skips; both mdBooks, opcode-evidence synchronization, and whitespace validation passed.

  3456. Reported: “I’ll apply the same resource-only maximum-score reconstruction to games/GR.”

    Action/result: Began a clean-room Gold Rush analysis from the local game resources only. Selected the v3 combined-resource reader and generated a disposable logic/message index under build/playthrough/gr/ without modifying the immutable game directory.

  3457. Reported: “The first pass is unusually rich: all 182 present logics decode. There are two apparent score ceilings.”

    Action/result: Decoded all 182 readable logic resources with no failures and indexed 6,227 parser predicates, 431 room transitions, and 150 score mutations. Found the normal startup maximum of 250 and a route-specific later assignment of 255.

  3458. Reported: “The 255 ceiling is deliberate. The game explicitly raises it on the Cape Horn branch.”

    Action/result: Traced the cheap-ticket purchase branch in logic 3 to a nine-point award and an explicit maximum-score change to 255. Correlated this with the arrival checkpoint that reports 100 points for Cape Horn and 95 for the other routes, establishing Cape Horn as the required route for the highest terminal score.

  3459. Reported: “The Cape Horn score structure now reconciles cleanly.”

    Action/result: Reconstructed the 40 voyage points as five three-point fishing-tool components, seven points for the Bible interaction, eight for catching the fish, five for completing the rescue promptly, two for the storm repair, and three for safely eating an early piece of ham. Identified duplicate room/state award sites and the later diseased-ham death branch so they are not double-counted.

  3460. Reported: “The direct assignments resolve the numeric mapping but expose a useful correction.”

    Action/result: Traced route variable values through their downstream narration instead of relying on the purchased item’s apparent purpose. Corrected the provisional route label: route value 2 is Panama, requires mosquito netting, invokes the Panamanian-jungle logic, and carries the 255-point maximum; route value 1 is Cape Horn and requires oranges. Consequently the previously grouped 40-point ship ledger belongs to the Cape voyage and cannot be used as the maximum-route Panama ledger.

  3461. Reported: “The Panama phase now closes exactly.”

    Action/result: Reconciled the Panama crossing as four points for cooperating with the armed group, four for the ant/vine escape, seven for the resting traveler’s Bible, five for the highest traversal branch, ten for the ancient gold disk, and ten for the stream crossing. These 40 points carry the route from the Brooklyn 60 checkpoint to the explicit California-arrival 100 checkpoint.

  3462. Reported: “The full score arithmetic now reconciles.”

    Action/result: Identified the apparent four-point Brooklyn excess as the ferry sequence used by the overland route, not the Panama ship departure. Traced logic 110’s gold progression to a shared 70-award cap and separated Psalm 23’s five points from the arrival checkpoint. The resulting route totals 60 Brooklyn, 40 Panama, 5 Psalm, 80 California one-time, and 70 gold-progression points, or 255.

  3463. Reported: “All 186 present GR pictures decode in the brush-disabled qualitative mode.”

    Action/result: Added an explicit --no-pattern-brushes option to the reusable picture CLI and generated a disposable contact sheet. It corroborates Brooklyn, ship and jungle travel, Sacramento, Sutter’s Fort, hotel, mule, cabin, and mine geography without claiming pixel fidelity from still-unmapped v3 patterned-brush tables.

  3464. Reported: “I’m writing the evidence chapter now.”

    Action/result: Added the Gold Rush playthrough chapter with the static Panama route, 255-point grouped ledger, timing and inventory constraints, death/dead-end families, and original-interpreter replay requirements. Linked it into the evidence book and added GR to the high-level progress tracker and replay backlog.

  3465. Reported: “The hotel coin consistency check supports the route.”

    Action/result: Confirmed logic 12 materializes the hotel-room coin from its persistent Brooklyn discovery flag rather than requiring its current inventory location, leaving the one-point recovery available after the Panama robbery sequence. Rebuilt the GR index, exercised the new renderer flag, built both mdBooks, checked opcode evidence and whitespace, and ran 433 tests with four expected optional-fixture skips. An initial test run omitted the deliberately required AGI_GAME_DIR; rerunning against the SQ2 regression baseline passed cleanly.

  3466. Reported: “I’ll apply the same clean-room route reconstruction to games/PQ1.”

    Action/result: Began a resource-only Police Quest analysis from the immutable local evidence directory. Preserved the existing uncommitted playthrough work and unrelated run.sh, and selected a full resource, score, parser, transition, message, and picture census as the first pass.

2026-07-11 16:15

Resumed the PQ1 maximum-score reconstruction at the unresolved score-ledger gap. The resource census found 118 readable logic resources, 71 pictures, 220 views, a declared maximum score of 245, and 114 positive score operations whose raw sum is 270. Shared one-time guards account for 12 points of alternative or duplicate awards, leaving 13 further points to classify by following the disassembled controller and room-state conditions rather than assuming every syntactic score operation is reachable in one playthrough.

Two bundled local save files were inspected read-only as independent consistency evidence. Both represent a score-156 checkpoint, but neither is being used to derive the route; the route remains based on decoded logic, messages, inventory transitions, and room transitions.

Direct inspection of logic 44 and logic 45 confirmed that their briefing, message, newspaper, and informant awards contribute 19 distinct obtainable points after accounting for the shared newspaper-availability guard. The next step is to identify the remaining cross-phase and mutually exclusive score branches in source order, then construct and independently sum the route ledger.

2026-07-11 16:55

Completed the first PQ1 static winning-route reconstruction. Direct logic and message analysis establishes the playable dependency chain from briefing and patrol procedure through the DUI/Hoffman arrests, Narcotics transfer, warrant, park bust, Cotton Cove body identification, Hotel Delphoria cover, poker, Room 404 transmitter, Bains capture, trial, and terminal parade. The route and failure constraints are recorded in games/pq1_playthrough_analysis.md.

Rendered all 71 present PQ1 pictures with patterned brushes disabled and built a qualitative contact sheet under disposable build/playthrough/pq1/ output. The sweep corroborates route geography but is explicitly not pixel-conformance evidence because the PQ1-specific brush table has not been mapped.

Corrected the score analysis after following nested conditions directly. Six proved alternative groups remove 13 points from the raw 270-point syntax sum, leaving a 257-point static upper bound. The remaining 12-point reachability gap against the declared maximum of 245 is preserved as open work; no unproved score closure is claimed. Updated the evidence book summary, executable notes, and PROGRESS.md accordingly.

Verification passed 433 deterministic tests with four expected optional-input skips. Both mdBooks built successfully, the generated opcode-evidence chapter was current, and git diff --check reported no whitespace errors. No file under games/PQ1 was modified.

2026-07-11 17:10

Began a clean-room maximum-score winning-route reconstruction for the immutable local games/LSL1 evidence set. The planned pass starts with complete logic, parser, transition, score, inventory, and terminal-state indexing; follows the decoded control flow and messages to establish puzzle dependencies; and uses brush-disabled picture rendering only as qualitative geographical evidence.

2026-07-11 17:45

Completed the LSL1 static maximum-score reconstruction. All 46 present logic resources decode, and their 49 positive score sites sum exactly to logic 51’s declared maximum of 222; no negative score operation exists. Direct dependency analysis places every award in one route from Lefty’s through the store, telephone, casino, disco, chapel, hotel, Faith, Eve, and logic 45’s terminal penthouse sequence.

Rendered all 43 present pictures with patterned brushes disabled and built a qualitative contact sheet under disposable build/playthrough/lsl1/ output. Added games/lsl1_playthrough_analysis.md with the route, exact score ledger, money and inventory constraints, death/dead-end map, and replay requirements. Updated the evidence-book summary, executable notes, and PROGRESS.md. No file under games/LSL1 was modified.

Verification passed 433 deterministic tests with four expected optional-input skips. Both mdBooks built successfully, the compatibility runner’s generated opcode-evidence check passed, and git diff --check reported no whitespace errors.

2026-07-11 18:00

Began reorganizing the newly added game-specific playthrough-analysis chapters under docs/src/games/. The move will preserve every chapter in the mdBook navigation and update all evidence-book, progress-dashboard, and historical-log path references to the new location.

Moved all nine playthrough-analysis chapters into docs/src/games/. Added a dedicated Game Playthrough Analyses section to the mdBook summary, retained all nine chapters in navigation, and added an Interpreter Evidence heading before the remaining engine-focused chapters. Updated PROGRESS.md and all historical-log filename references to use the nested paths.

Verification found no stale top-level playthrough path, built both mdBooks, generated all nested HTML pages, and passed 433 deterministic tests with four expected optional-input skips. git diff --check reported no whitespace errors.

The final Git audit found that the broad games/ ignore pattern also hid docs/src/games/. Anchored the rule as /games/, preserving the private root-level game-input exclusion while making the nested documentation chapters trackable.

2026-07-11 18:15

Began separating whole-game playthrough progress from interpreter and portable- specification progress in PROGRESS.md. The nine game-specific route entries will move together under a dedicated section without changing their evidence, status, or remaining replay work.

Moved the nine route-analysis entries from Current Snapshot into a dedicated Game-Specific Playthroughs section immediately before behavioral- specification coverage. Interpreter, version, save, fixture, and corpus status now remain together in the general snapshot, while each game’s score closure, evidence chapter, uncertainty, and replay backlog remain grouped separately.

Rebuilt both mdBooks successfully after the dashboard reorganization. git diff --check reported no whitespace errors, and the section audit confirms the intended order: Current Snapshot, Game-Specific Playthroughs, Behavioral Specification Coverage, and the later remaining-work dashboard.

2026-07-11 18:30

Began a clean-room maximum-score winning-route reconstruction for the immutable local games/BC evidence set. The pass will index all logic, parser, score, inventory, transition, and terminal-state evidence; derive the route from local resources; and place the resulting game-specific chapter under docs/src/games/.

2026-07-11 18:52

Completed the static BC resource census and score-branch reconciliation. All 85 present logics decode, logic 107 declares a 230-point maximum, and the 40 award sites reduce from a raw 463 to exactly 230 after grouping mutually exclusive rescue, route, encounter, resolution, and reward branches. Rendered all 69 present pictures qualitatively and drafted the maximum-score route, score ledger, failure map, and replay checklist in the game-specific evidence chapter.

Verified the BC chapter and dashboard changes with git diff --check, both mdBook builds, and the full compatibility-suite runner. The suite passed all 433 deterministic tests with four expected optional-input skips, rebuilt both books, and completed the opcode-evidence checks. The rendered evidence book contains the new games/bc_playthrough_analysis.html chapter.

2026-07-11 19:05

Began a clean-room maximum-score winning-route reconstruction for the immutable local games/MG evidence set. The pass will census and decode all present resources, classify scoring and terminal alternatives from local logic, render pictures for qualitative geography, and add a game-specific evidence chapter without modifying the private game input.

Completed the static MG reconstruction. All 73 present logics decode and show that the apparent 18-point maximum is an eighteen-rhyme completion counter. Mapped the 35-room outdoor grid, nine attached rooms, twenty randomized props, all eighteen recipients, the one-prop carrying protocol, Old King Cole’s three-stage exception, and logic 102’s terminal celebration. Rendered all 49 present pictures qualitatively and added the route strategy, completion ledger, recovery model, and deterministic replay backlog to the game-specific chapter.

Verified the MG chapter and tracker updates with git diff --check, both mdBook builds, and the full compatibility-suite runner. All 433 deterministic tests passed with four expected optional-input skips; both books rebuilt and the rendered evidence book includes games/mg_playthrough_analysis.html.

2026-07-11 19:35

Began a clean-room whole-game reconstruction for the immutable local games/MH1 evidence set. The pass will prioritize readable disassembled logic, catalogue any unavailable resource evidence, identify the winning and maximum- score states, render readable pictures, and add a game-specific evidence chapter without modifying the private input.

The MH1 resource census identified a version 3 combined-directory input with 66 present logics. The first indexing pass exposed a generic error in the intended unreadable-resource path: it treated a structured directory entry as an old tuple. Corrected the indexer to report the entry’s volume and offset through named fields, then regenerated the index successfully.

Sixty-five MH1 logics decode. Logic 136 is unavailable because its directory entry selects MHVOL.3:0x1d323, beyond the local file length of 0xfe00. The readable scripts contain no score mutations or maximum-score assignment; instead they establish four ordered assignments, four late module-installation states, and four distinct ship bomb-drop states. All four bomb states are required before the switch to logic 162 and its To be continued... ending.

Rendered 196 of 237 present picture entries for qualitative story and location evidence. The remaining 41 require unavailable volume bytes, and the readable scripts also directly reference six unreadable views. Added a bounded MH1 chapter documenting the proven phase skeleton, inventory, Alliance computer, terminal condition, failure/retry evidence, candidate route, and explicit complete-input blocker. Updated the evidence notes, mdBook summary, and game-specific dashboard without modifying games/MH1.

Verified the MH1 work with a fresh tolerant index run, git diff --check, both mdBook builds, and the full compatibility-suite runner. The regenerated index reports exactly 65 readable logics, 138 room switches, 93 inventory mutations, zero score events, and one explicit unreadable record for logic 136 at volume 3 offset 0x1d323. All 433 deterministic tests passed with four expected optional-input skips, and the evidence book contains the new games/mh1_playthrough_analysis.html chapter.

2026-07-11 20:10

Began a clean-room whole-game reconstruction for the immutable local games/MH2 evidence set. The pass will identify the observable completion model, index all readable logic and inventory transitions, render available graphics for qualitative context, and preserve the already known missing-logic boundary rather than inferring unsupported route steps.

The MH2 census found a v3 combined-directory game with 96 present logics, 248 pictures, 181 views, and 195 sounds. Indexing initially stopped when a logic selected absent MH2VOL.3. Extended the generic unreadable-resource path to include missing volume files, then regenerated the index with 65 readable and 31 explicitly unavailable logics.

The readable logic contains six parser predicates, 135 room switches, 32 inventory mutations, and no score or maximum assignment. It establishes the New York-to-San Francisco pursuit, acquisition of MAD and the ID Card, two Orb assignments, the travel network, a 32-item inventory, local arcade/retry contracts, and the hybrid-experiment plot. Logic 0 directly dispatches into many of the missing rooms, so the absent volume prevents a complete route and identification of the real terminal state. In particular, readable The End and YOU WON messages belong to in-world presentations and local challenges, not the whole-game ending.

Rendered all 177 pictures available from the present volumes. Seventy-one present pictures, 67 views, and 73 sounds remain unavailable with volume 3. Added a bounded MH2 chapter containing the resource boundary, assignment and story model, inventory table, partial candidate route, failure classes, and the exact work needed after a matching MH2VOL.3 is supplied. Updated the evidence notes, mdBook summary, and game-specific dashboard without modifying games/MH2.

Verified the MH2 work with fresh MH1 and MH2 index runs, git diff --check, both mdBook builds, and the full compatibility-suite runner. MH1’s out-of-range logic and all 31 MH2 missing-volume logics remain explicit unreadable records. All 433 deterministic tests passed with four expected optional-input skips, and the evidence book contains the new games/mh2_playthrough_analysis.html chapter.

2026-07-11 21:05

Began a source-first cross-game audit of picture pattern-brush behavior. The pass will inspect each locally available interpreter build, group identical binaries, map the 0xf9 mode and 0xfa plot handlers plus their referenced shape tables, and document observable differences before changing any renderer assumptions.

2026-07-11 21:53

Completed the source-first brush audit across all 16 selected game directories. The resulting evidence divides the interpreters into four groups: 2.089/2.272 stop at picture command 0xf8; 2.411 consumes 0xf9 but uses 0xfa as point plotting; 2.439 through 2.936 have complete v2 brushes; and all observed 3.002 builds have a distinct full-brush variant.

Added tools/brush_table_audit.py, which explicitly accepts repeated --game-dir inputs, decodes loader-protected local interpreters when needed, finds each command scanner and dispatch table structurally, and reports brush handlers, table offsets, radius-one rows, and horizontal clamps. Corrected an initial tool interaction in which importing the global game-path parser consumed the first repeated argument and omitted SQ1. The corrected report at build/brush-audit/report.md contains all games and confirms that every full brush routine directly references its selected tables.

The v2 and v3 complete brush cores have identical column masks, radius 0 and radii 2 through 7, mode bits, stipple recurrence, iteration order, and pixel write path. V2 uses radius-one rows e000 e000 e000 and clamp 0x0140; v3 uses 4000 e000 4000 and clamp 0x013e. A rendering sanity check corrected the provisional description “cross”: 4000 intersects neither examined logical-column mask, so normal v3 radius-one output is two adjacent center-row pixels, compared with v2’s 2 by 3 block. KQ2, SQ2, KQ4, and GR produced one, six, two, and two logical pixels respectively for the same synthetic radius-one plot.

Changed the local renderer to discover relocated mask and pointer tables from their structure instead of using SQ2 offsets. It now selects the observed v2 or v3 horizontal limit and automatically uses point behavior when no complete brush table exists. Added structural scanner/table tests and clamp-family tests. Updated the evidence chapters, symbolic labels, versions chapter, runtime model, clean-room picture specification, version profiles, and PROGRESS.md. Also corrected stale game chapters while preserving that their older brush-disabled contact sheets remain qualitative rather than pixel conformance evidence.

Validation resolved the expected table/clamp family independently in every game directory. The full deterministic suite passed 436 tests with four expected optional-input skips. The compatibility-suite runner passed, both mdBooks built successfully, and git diff --check reported no whitespace errors. No game input was modified; generated audit files remain under disposable build/.

2026-07-11 22:08

Audited replacement MH1 and MH2 inputs after the user supplied alternate copies. Both now contain volume 3. Fresh census, reference-audit, and playthrough-index runs show zero skipped source logics: MH1 decodes all 66 present logics, 237 pictures, and 138 views; MH2 decodes all 96 logics, 248 pictures, and 181 views.

Investigated the remaining two sound errors per game. MH1 entries 136 and 138 point into non-header bytes in MHVOL.0; MH2 entries 215 and 216 select absent MH2VOL.6. None is referenced. MH1’s other 99 sounds and MH2’s other 193 sounds all decode, and each valid set exactly equals that game’s static sound reference set. Classified the four entries as inert directory-tail anomalies, not missing valid gameplay resources.

Corrected a reference-audit operand-map bug found during closure checking. The tool had counted set.loop’s loop number as a view dependency and read the cel rather than view operand from add.to.pic. It now ignores set.loop, reads add.to.pic operand 0, and reports references absent from the complete readable set. Added synthetic coverage for those cases and reran both games with no unavailable references.

Confirmed that replacement MH1 uses interpreter 3.002.102: its AGIDATA is byte-identical to KQ4D and its AGI differs only at two signature bytes. MH2 retains the known 3.002.149/Gold Rush relationship. Updated the versions, game-analysis, symbolic-label, current-status, and progress documentation to preserve the old incomplete-copy findings as history while removing the current resource blocker.

Verification passed 22 focused resource/census tests and all 436 deterministic tests with four expected optional-input skips. Both mdBooks built successfully, the project-level compatibility runner passed, and git diff --check reported no whitespace errors.

2026-07-11 22:18

Started a from-scratch reconstruction of the MH1 and MH2 playthrough chapters using the replacement complete resource sets. Deleted both prior partial chapters before beginning the new route analysis. The replacement work will derive story phases, room transitions, inventory dependencies, failure/retry paths, and terminal states from fresh logic indexes and disassembly rather than editing the former missing-resource narrative.

2026-07-11 22:36

Completed the from-scratch MH1 and MH2 playthrough reconstruction against the replacement complete resource sets. Fresh indexes covered all 66 MH1 and all 96 MH2 present logics with no unreadable scripts. The analysis traced terminal incoming edges first, then worked backward through assignment phases, report parsers, inventory mutations, room transitions, retry branches, and late-game controllers.

MH1’s unique continuation ending is logic 162. Its incoming ship-flight edge requires the late authorization state, flight stage 12, and four independent bomb-region completion flags. The rebuilt route records the four assignments, the recognized MAD names, Modules A through D and their independent cockpit consumption, Alliance-computer state, and the four-target bombing requirement.

MH2’s unique continuation ending is logic 187. Its incoming edge from the wraparound maze requires maze state 4 and the player crossing the narrow top exit at X 130 through 143. The rebuilt route now includes the formerly missing Rat Mask/Full Flask, ring, hatchet, Orb-on-a-Stick, statue/Orb Card, lava and captive controller, helicopter, final-control, and toroidal-maze chains.

Rendered fresh representative pictures referenced by both ending scripts into build/playthrough/*-replacement/media/ as qualitative scene checks. No numerical score or maximum-score mutations exist in either game, so each chapter defines winning by its unique To be continued... terminal state. Both chapters explicitly retain exact report answers, movement, arcade timing, and late controller inputs as deterministic replay work rather than guessing.

Validation passed all 436 deterministic tests with four expected optional-input skips. Both mdBooks and the project compatibility-suite runner completed successfully, and git diff --check reported no whitespace errors.

2026-07-11 22:44

Removed game-specific playthrough tracking from PROGRESS.md so it remains an engine reverse-engineering dashboard. Deleted the MH1/MH2 route, puzzle-input, and deterministic-playthrough backlog entries. Retained only MH1/MH2 references that provide interpreter-version, dispatch-table, or resource-container evidence relevant to the engine specification.

2026-07-12

Began replacing the high-level SQ1.22 candidate route with a precise static-data playthrough state machine. The work will trace every selected score event back through parser words, room and position tests, inventory, flags, variables, object state, timers, and random branches in the immutable games/SQ1.22 resources. The canonical reusable result will be JSON with a deterministic Graphviz renderer, accompanied by a detailed evidence chapter; no external AGI material or prior walkthrough is being consulted.

The initial audit confirmed that the existing chapter is a prerequisite-level outline rather than a replay script. Its current index records conditions whose byte ranges enclose an action, but it does not reconstruct jumps and therefore cannot by itself express exact branch reachability. Full logic disassembly and control-flow tracing will be used for the new graph; the index remains a useful locator for score, inventory, parser, and room-transition sites.

Movement analysis will combine every selected room logic with both the visual and priority/control channels of its picture. Required transitions will record the safe approach region and direction against control-line barriers, plus any logic action that changes priority, horizon, object control, or the picture’s effective traversability. This is necessary to distinguish a syntactically present room edge from one the player can actually reach.

Completed the first static pass over all SQ1.22 score sites and the principal room transitions. The remaining pass is tightening parser commands, timing/random branches, and priority/control constraints before encoding the canonical success-path state machine.

Resolved three late-game ambiguities from full disassembly. The 6858 shutdown code is entered by physically walking across numbered keypad regions in room 65; the disguise sequence requires entering the open laundry unit from its narrow baseline box; and gambling is a genuine random loss/win loop whose winnings saturate at 250 buckazoids. These will be represented as coordinate- sensitive and retrying graph edges rather than prose shortcuts.

Created the canonical SQ1.22 JSON state graph with 45 selected score nodes totaling 202, explicit precondition nodes, movement/parser/wait edges, and retry cycles for gambling and guard dialogue. Replaced the candidate chapter with coordinate- and state-specific instructions tied to the rendered priority/control channels; verification of the renderer, tests, and both books is now in progress.

The graph-specific tests and both mdBooks passed. A broad test run with AGI_GAME_DIR=games/SQ1.22 exposed the suite’s existing SQ2-bound assertions for resource hashes/counts, save names, and interpreter tables; those failures are unrelated to the new graph. The unchanged baseline suite is being rerun without the incompatible override, and both outcomes will be retained in the handoff.

Verification completed. The focused graph tests pass; the standard SQ2-backed baseline passes all 438 tests with four expected skips; both mdBooks build; the Graphviz renderer regenerates the committed SVG; and git diff --check is clean. The SQ1.22-selected broad run’s expected SQ2-bound failures remain a documented harness limitation, not a graph failure.

Changed the SQ1.22 Graphviz renderer from left-to-right to top-to-bottom layout and added a focused assertion for the vertical rank direction. The committed SVG is being regenerated from the unchanged canonical JSON state graph.

Started the persistent interpreter-controller implementation with QMP for VM, input, screenshots, and checkpoints; GDB for cycle stops and coherent memory; and a localhost JSON API for semantic state and actions.

The title Enter test exposed a press/release sequencing fault. Confirmed from static logic 69 that the following name field is the ordinary opcode-0x73 shared input mode, not a startup-only editor.

Interrupted the blocked editor and inspected its live registers and stack. The return chain contained 0x0df8, exactly matching the local disassembly, while targeting only image 0x0df2 stopped reliably. This isolated a QEMU real-mode single-breakpoint constraint rather than an incorrect interpreter offset.

Changed the controller to keep the cycle hook active normally, classify a blocking string/modal stack after a bounded interrupt, switch the one active hook to that UI, and restore the cycle hook when the UI exits.

Completed live semantic integration: Enter reached shared string input, submitting roger returned to room 2 cycle control, look entered a detected modal, and dismissing it restored the cycle hook and cleared both modal oracles.

Final verification passed: 13 focused controller tests, the 451-test SQ2-backed repository suite with four expected skips, both mdBook builds, and git diff --check. The controller and QEMU processes were stopped afterward.

Audited interpreter_controller.py for SQ1.22-specific assumptions. Added a prominent non-generic warning, moved the runtime signature and blocking-stack return addresses into the version profile, marked the 2.917-only decoders and screen oracle, and made unsupported object-record widths fail explicitly.

Playthrough Development Workflow

User request:

Add a chapter to the docs that gives advice for developing playthroughs including instructions on how to use the interpreter controller and the general problem-solving lessons you described above.

  1. Reported: “I’ll turn the session’s lessons into a reusable workflow chapter rather than an SQ1-specific walkthrough. I’ll anchor the controller examples to its current API, cover static-first hypothesis building, guarded execution and diagnosis, and clearly separate reusable techniques from the current SQ1.22/2.917 limitations.”

    Action/result: Audited the existing playthrough analyses, state graph, Graphviz renderer, static indexing/rendering tools, controller API chapter, and book structure before drafting the workflow.

  2. Reported: “The chapter structure is now clear: a proof standard for each playthrough edge, a static-resource workflow, a controller runbook, and a failure-diagnosis loop. One point I’m making explicit is that the controller’s local priority planner is a verifier for a room-level hypothesis—not a substitute for tracing room logic or planning multi-room/ elevator routes.”

    Action/result: Added developing_playthroughs.md, linked it from the book summary and controller chapter, and documented static analysis, state-graph construction, semantic transactions, input modes, movement, priority planning, waits, checkpoints, failure diagnosis, recording, and completion criteria.

  3. Reported: “The first draft is in place and cross-linked from both the book summary and the controller chapter. It includes concrete API examples for inspection, transactions, movement, guarded waits, dialogs, input recovery, checkpoints, recordings, and shutdown, plus a symptom-driven diagnostic table. I’m validating the rendered book now and will correct any broken examples or navigation before handing it over.”

    Action/result: Both mdBooks and git diff --check passed. The documented static index, logic disassembly, visual/control rendering, and Graphviz commands ran successfully against the selected SQ1.22 input; the chapter’s JSON and shell examples also passed syntax validation.

Playthrough Guidance in AGENTS.md

User request:

Update AGENTS.md to mention the interpreter controller and developing playthroughs chapters as something that should be consulted for playthroughs

  1. Reported: “I’ll add a concise playthrough-workflow rule to AGENTS.md that points agents to both chapters before static reconstruction or controller-driven replay, while preserving the existing clean-room and explicit-game-directory requirements.”

    Action/result: Added a dedicated playthrough-development section requiring consultation of docs/src/developing_playthroughs.md before playthrough analysis or validation and docs/src/interpreter_controller.md before controller-driven QEMU work. The guidance also preserves the current adapter’s explicit SQ1.22/2.917 limitation.

SQ1.22 Original-Interpreter Playthrough

User request:

Run the real SQ1.22 game under QEMU and play the statically reconstructed success path to completion.

  1. Reported: “I’ll begin by checking the existing worktree and the two controller/playthrough guides, then validate the static route and local SQ1.22 inputs before launching anything. I’ll keep one controller/QEMU session alive, establish deterministic entropy plus checkpoint restore first, and record concrete progress as we go.”

    Action/result: Confirmed that games/SQ1.22 is present, inspected the playthrough/controller guidance and static route, and found only the unrelated untracked run.sh in the worktree.

  2. Reported: “The repository is clean except for an unrelated untracked run.sh, which I’ll leave untouched. The SQ1.22 input is present, and the prior controller work confirms the adapter was live-tested. I’m now extracting the exact API/action semantics and graph edge contracts; after that I’ll prepare a disposable disk and run the required entropy/checkpoint smoke test before attempting route stage 1.”

    Action/result: Began a focused audit of controller endpoints, route-edge preconditions, local runtime prerequisites, and existing disposable artifacts before launch.

  3. Reported: “No reusable controller disk or base FreeDOS image remains under build/, so I’m rebuilding the documented disposable runtime from local inputs. I’m deliberately leaving continuous per-cycle capture off for normal play; I’ll take milestone captures and bounded trace slices, which keeps the persistent session responsive and follows the repository’s performance guidance.”

    Action/result: Rebuilt the derived VGA BIOS and 1 GiB FreeDOS image, then prepared a disposable SQ1.22 qcow2 disk whose copied interpreter passed the fixed-seed patch validation.

  4. Reported: “The disposable environment is ready: the base image rebuilt successfully, and preparation validated and patched the copied SQ1.22 interpreter for fixed seed 0x5eed. I’m launching the persistent controller now, then I’ll discover the cycle hook and verify the selected profile, initial input mode, and checkpoint/restore behavior before entering the route.”

    Action/result: Launched one persistent controller/QEMU session, discovered the SQ1.22/2.917 cycle hook in title room 67, and confirmed the SQ122Adapter fixed-seed profile.

  5. Reported: “The first smoke pass reached the title’s documented random call after exactly 100 cycles and produced v126=20. Restore did reinstate the cycle breakpoint itself, but the restored VM is labeled debug_stop, so the guarded runner refused to advance and could not replay the sequence. I’ve stopped input and am inspecting this bounded controller-state mismatch before proceeding; I won’t work around it with blind key/cycle calls.”

    Action/result: Confirmed through /v1/debug that restore reinstalled the cycle breakpoint. A single bounded semantic step realigned the restored VM from the post-hook instruction to the next cycle boundary.

  6. Reported: “The checkpoint smoke now passes: after one semantic re-alignment step, the replay reaches the same title RNG call in the same total 100 interpreter cycles, again yielding v126=20 and the identical randomized object placement. The active hook is restored correctly; I’ve recorded the debug_stop labeling caveat and am moving through the name prompt into the actual game.”

    Action/result: Saved a title milestone capture and used a transaction to enter room 69’s shared name editor, with both room and input-mode postconditions observed.

  7. Reported: “The first navigation contract was correctly interrupted before movement: room 2 opened an automatic modal from logic 94 while the initial walk-in completed, leaving Roger at (97,66) and stationary. I’m handling that dialog explicitly and checking its static owner before retrying the room-1 exit; no movement input will be repeated until the modal is identified and dismissed.”

    Action/result: Identified and dismissed the Arcada boarding alarm, then reached room 1 through its exact room-2 doorway condition.

  8. Reported: “Dynamic replay exposed one omission in the static route: the scientist sequence is not armed on the first room-1 entry. The game requires a brief room 1 -> 2 -> 1 loop, which sets f53; I confirmed that transition and am now back in room 1 with f53=true and the scientist countdown at v32=247. I’ll preserve this correction in the evidence/route after the live stage is stable.”

    Action/result: Proved the arming loop, positioned Roger safely, and waited through the scientist entry and collapse dialogs to v51=2, v34=6.

  9. Reported: “A second static omission is now confirmed: v33 never starts by waiting alone. Room-1 logic requires TALK TO SCIENTIST while v51=2, v34=6, and Roger is beside him. The command reached a modal, but its full postcondition did not match, so I’m identifying the actual dialog and timer state before dismissing anything.”

    Action/result: Corrected the intermediate hypothesis: TALK TO SCIENTIST selects the nonconversational response, while LOOK AT SCIENTIST selects the wound-description branch that initializes v33 after dismissal.

  10. Reported: “Understood. I’m treating the guide as a hypothesis, not authority. The live state already disproved two route assumptions, and I’ll re-analyze each owning logic whenever a contract fails. This latest LOOK AT SCIENTIST did not set the timer either, so I’m decoding the exact parser branch and current dialog before choosing the next command.”

    Action/result: Verified that the blocking wound-description action precedes the v33 assignment; dismissing it started the countdown, and dismissing the two terminal messages awarded the expected 2 points.

  11. Reported: “The scientist branch is now proved end to end: the trigger is LOOK AT SCIENTIST, not TALK; the two final dialogs then award exactly 2 points. The archive console accepted ASTRAL BODY, and after a bounded 205-cycle retrieval sequence the live state reached v50=2 and f35=true. I’m taking the cartridge next, then I’ll checkpoint and update the route evidence with these corrections before navigating onward.”

    Action/result: Took the cartridge for 5 points, independently verified inventory object 1 is carried, dismissed the result dialog, and saved checkpoint score7_cartridge plus milestone capture sequence 3 at room 1, score 7, position (82,108).

  12. Reported: “I’m switching the remaining Arcada movement to the live priority channel: each local route will be planned from the decoded priority screen, while elevator doors and room edges remain separate guarded transitions.”

    Action/result: Used live-priority plans for every local corridor and console approach. Dynamic stages established the two-elevator route to the room-3 keycard, the room-7 reader/elevator transition, the room-9 airlock, and the room-8 platform-to-pod route.

  13. Reported: “The room-7 entry armed a hostile random encounter, so I’ve stopped the crossing and am checking its owning logic before moving again.”

    Action/result: Identified v67=1 as the armed entry state. Three immediate room-7 retreats/re-entries remained armed; the fourth produced v67=0, allowing a safe priority-planned crossing without replaying the fatal input.

  14. Reported: “The pre-launch milestone is ready: room 10, score 19, door shut, belt fastened, power on, and AutoNav engaged. I’m completing the F5 save now, then I’ll launch and handle each automatic transition as a guarded step.”

    Action/result: Overwrote built-in slot 1 with Score 19 prelaunch. Earlier F7 testing had restored the slot’s score-7 state and confirmed that the original interpreter’s save/restore path reinstates room, position, inventory, and score.

  15. Reported: “The first throttle command was not accepted: the save dialog had just returned control and the interpreter discarded the leading keystrokes, leaving only hrottle at the prompt. I’ve stopped instead of repeating blindly; I’m clearing that partial command, waiting for normal input mode, and will retry from the verified pre-launch state.”

    Action/result: Submitted the partial text once to prove it caused only a parser error, dismissed that modal, reconciled input, and retried. The complete PULL THROTTLE then set f80 with all launch prerequisites still true.

  16. Reported: “Launch is behaving as the local logic predicts, with one guide omission: it first switches back to room 8 for the visible pod-exit cutscene, then enters room 12.”

    Action/result: Followed the bounded automatic sequence through room 8 and room 12. Logic 12 awarded 15 points, raising the live score from 19 to 34, and the narrow-escape modal matched the local message resource.

  17. Reported: “This confirms another substantive guide error: room 13’s 25-point award is conditional on returning from room 37 later in the game, not on the first Kerona approach. The live score is still 34, and I’m preserving that observed contract instead of carrying the static guide’s inflated total forward.”

    Action/result: Waited through in-flight room 10, first-arrival room 13, room 30’s landing animation, and room 14. The score remained 34 until GET SURVIVAL KIT, which raised it to 36.

  18. Reported: “Before continuing across the desert, I caught an omitted dependency from the guide: the survival kit must be opened to replace it with dehydrated water and the Xenon Army Knife.”

    Action/result: Opened the kit outside the crashed pod and verified carried inventory objects 12 and 19. A live-priority plan moved the ego into room 30’s full-width glass rectangle; GET GLASS then raised the score to 39.

  19. Reported: “We’ve reached a clean Kerona milestone at score 39 with the kit opened and glass collected. The guide’s claimed score 61 is definitively wrong at this point: the first room-13 arrival gives no 25-point bonus.”

    Action/result: Updated the playthrough prose, graph, and clean-room notes with the dynamically confirmed Arcada topology, alien retry, closet mapping, airlock, launch sequence, inventory expansion, and relocated 25-point award. The focused graph tests still pass.

2026-07-15: XMAS.230 / AGI 2.230 profile

  1. Reported: “I’ll treat games/XMAS.230 as a new evidence profile: first inventory the executable/resources, then compare its dispatch tables and mapped subsystems against the neighboring early interpreters before adding any normative spec claims.”

    Action/result: Confirmed a clean worktree apart from the unrelated untracked run.sh, kept games/ read-only, and began a focused census of XMAS.230, SQ1 2.089, and XMAS 2.272.

  2. Reported: “The local overlay identifies itself as Version 2.230. I’m using 2.089 and 2.272 as the nearest source baselines and checking parser contracts before interpreting relocated addresses as behavior changes.”

    Action/result: Identified a direct MZ interpreter and split direct v2 resources. Generated selected-game census and two dispatch-table comparison reports under build/cross-version/.

  3. Reported: “The first table pass shows 2.230 is a hybrid rather than a simple relocation of either neighbor. I’m manually reducing the small set of changed handlers and then checking subsystem roles.”

    Action/result: Found 155 actions and 19 conditions. Against 2.089, six shared action entries and two condition entries differed; against 2.272, seven shared actions differed, all condition handlers matched, and 2.272 alone supplied actions 0x9b..0xa0.

  4. Reported: “The new game directory became unreadable during the source pass, so I’ve stopped rather than changing private evidence permissions. Please restore access to games/XMAS.230 and I’ll continue from the saved comparison artifacts.”

    Action/result: Stopped all reads after a permissions failure and did not chmod or otherwise mutate the private game input. No QEMU/controller process was running.

  5. Reported: “Access is restored. I’m resuming at the key 2.230 anomaly: several local view records encode flag bits in the loop header’s high nibble, and the 2.230 last.cel handler explicitly masks them. I’m tracing every cel-count consumer before promoting that behavior into the new spec profile.”

    Action/result: Compared paired 2.230/2.272 view payloads and disassembled loop selector 0x350d, row-mirroring helper 0x50af, and action 0x31 at 0x36e2. Confirmed packed low-nibble count plus mutable loop-wide orientation and mirroring.

  6. Reported: “The view difference is now source-backed, not just a data oddity. Version 2.230 stores mirroring state in the loop header (count & 0x0f, orientation in the upper bits) and mirrors every cel in the shared loop when orientation changes; 2.272 stores orientation in each cel control byte. I’m checking the remaining EGA/output and profile subsystems so the new profile separates real contracts from relocated implementation.”

    Action/result: Confirmed six selected resources use 0xc4/0xc6 packed loop headers where corresponding 2.272 resources use ordinary counts and per-cel bit 0x80. Mapped picture, movement, object, sound, save, input, and EGA output roles.

  7. Reported: “The remaining source pass is closed: 2.230’s EGA overlay entries normalize exactly against both neighboring builds, while its core is a deliberate hybrid—2.272-style position/composition/parser/save behavior, 2.089-style exit/motion-clear/sound behavior, plus the unique loop-header view encoding. I’ve added deterministic model tests and am promoting those contracts into the spec now.”

    Action/result: Added packed-loop header transition/count tests, a local source-byte guard for action 0x31, selected metadata/save-dimension tests, and the new normative 2.230 profile and conformance rows.

  8. Reported: “The interpreter profile and clean-room evidence are in place. I’m finishing the last source-level cross-check—XMAS 2.230’s startup interpretation of the OBJECT header—then I’ll rerun the final consistency checks and hand over the profile.”

    Action/result: Reopened the startup metadata/object initializer in 2.230 and its relocated 2.272 counterpart and recorded the matching header-count and allocation path.

  9. Reported: “The OBJECT capacity is now tied to the executable path itself: 2.230 reads header byte 0x11, increments it, and allocates 18 records of 0x2b bytes. That removes the last inference-only piece from the save-envelope profile; I’m running the final book and diff checks now.”

    Action/result: Added the startup role to the stable symbolic map and recorded the exact read, increment, and record-size multiplication evidence in the executable notes.

  10. Reported: “The combined check stopped immediately because the tests correctly reject an implicit game directory. I’m rerunning with the suite’s documented SQ2 reference input explicitly selected; no analysis result or file was affected.”

    Action/result: Repeated the focused verification with AGI_GAME_DIR=games/SQ2; 161 tests passed with four expected skips, both mdBooks built, and git diff --check passed.

  11. Reported: “Focused verification is green (161 tests, 4 expected skips), both mdBooks build, and the diff is whitespace-clean. I’m doing one final full-suite run against the explicit reference corpus so the handoff reflects the exact current tree, including the last evidence note.”

    Action/result: The complete 482-test suite passed with four expected skips. A process-list check found no QEMU or persistent interpreter controller process running.

2026-07-15: Public repository introduction

  1. Reported: “I’ll reshape the README’s opening for a public audience: a short explanation of AGI, what this repository produces, and the clean-room constraint, while keeping enough concrete terminology that AGI practitioners can identify the scope immediately. I’ll preserve the existing technical material below the introduction.”

    Action/result: Reviewed the existing root README and the two mdBook introductions to establish the public-facing explanation and retain their evidence/specification separation.

  2. Reported: “There are two pre-existing changes in docs/book.toml and spec/book.toml, likely related to publication. I’m checking those diffs so the README edit doesn’t overwrite or accidentally absorb them; the README itself is currently clean.”

    Action/result: Confirmed that both changes add publication site-url settings. Preserved them without modification and kept the README work separate.

  3. Reported: “The existing publication-path edits are separate and will remain untouched. I’m replacing only the README introduction, adding a general AGI reference plus the resource and runtime vocabulary—LOGIC, PICTURE, VIEW/cels, priority/control data, WORDS.TOK, OBJECT, and volumes—that makes the project’s scope immediately legible to AGI developers.”

    Action/result: Rewrote the introduction with an AGI overview and external introductory link, a concise inventory of the reverse-engineered subsystems, a clear statement that the deliverable is a specification, and an explanation of the docs//spec/ clean-room boundary.

  4. Reported: “I’ll add a clear ‘read the published book’ link to each bullet while retaining the repository-directory links for readers who want the source.”

    Action/result: Added the public GitHub Pages URLs for the behavioral specification and reverse-engineering evidence book to their corresponding README bullets.

  5. Reported: “I’ll place a prominent ‘Project repository’ link directly below each book title so it appears immediately on both landing pages, then rebuild both books to verify the rendered sources.”

    Action/result: Added the peterkelly/agi-re GitHub link to the first page of both the evidence and specification books.

Clean-room executable notes

This file records local observations made while inspecting the Space Quest 2 files in this repository. No external AGI documentation, source code, or prior AGI-specific knowledge is used here.

2026-07-01: initial executable inventory

Commands run from /Users/peter/ai/agi/reverse:

  • cat AGENTS.md
  • rg --files
  • ls -la
  • git status --short
  • ls -l SQ2
  • file SQ2/SIERRA.COM SQ2/AGI SQ2/AGIDATA.OVL SQ2/EGA_GRAF.OVL SQ2/IBM_OBJS.OVL SQ2/VOL.0 SQ2/LOGDIR
  • xxd -l 256 -g 1 SQ2/SIERRA.COM
  • xxd -l 256 -g 1 SQ2/AGI
  • command -v objdump ndisasm r2 rizin ghidraRun retdec-decompiler llvm-objdump strings dosbox qemu-system-i386
  • strings -a -t x SQ2/SIERRA.COM
  • strings -a -t x SQ2/AGI
  • xxd -s 0x2a0 -l 512 -g 1 SQ2/SIERRA.COM
  • python3 -c "import capstone; print(capstone.__version__)"
  • python3 -c "import distorm3; print(distorm3.__version__)"
  • python3 -c "import iced_x86; print(iced_x86.__version__)"
  • python3 --version
  • which gobjdump nasm ndisasm radare2 rizin

Local tooling result:

  • /usr/bin/objdump and /usr/bin/strings are present.
  • The installed objdump rejects -b, so it cannot directly disassemble a raw DOS COM binary in the usual GNU objdump style.
  • ndisasm, r2, rizin, ghidraRun, retdec-decompiler, qemu-system-i386, and DOSBox were not found on PATH.
  • Python 3.14.6 is present, but capstone, distorm3, and iced_x86 are not installed.

File inventory observations:

  • SQ2/SIERRA.COM is 3,121 bytes and file identifies it as a DOS executable COM file.
  • SQ2/AGI is 39,424 bytes and file identifies it only as data.
  • Several overlay files exist. file identifies at least EGA_GRAF.OVL and IBM_OBJS.OVL as DOS executable COM-like data.
  • SQ2/SIERRA.COM begins with bytes e9 9d 02, an 8086 near jump in a COM program. With the COM load origin at offset 0x100, this jumps from memory 0x0100 to memory 0x03a0, corresponding to file offset 0x02a0.
  • The bytes immediately after that jump contain text: LOADER v3.0 (c) Copyright Sierra On-Line, Inc. 1987, followed by keyOfs8.
  • SQ2/AGI begins with high-entropy-looking bytes and has no obvious header in the first 256 bytes.

Loader string observations from strings -a -t x SQ2/SIERRA.COM:

  • File-related strings include agi at file offset about 0x01c1 and agidata.ovl at file offset 0x01c5.
  • Error/retry strings include:
    • Can't find the file 'agi'.
    • Can't find the file 'agidata.ovl'.
    • Press Enter to try again.
    • Press ESC to quit.
  • Display-adapter messages mention color/graphics and Hercules-compatible adapters.
  • Disk-prompt strings mention an original disk and a play disk.
  • Later strings include Bad program image. and a nearby byte sequence that includes MZ, suggesting a check or message related to an executable image.

Initial loader code observations from bytes at file offset 0x02a0:

  • The loader disables interrupts, sets SS to CS, sets SP to a low internal offset, and re-enables interrupts.
  • It copies the DOS PSP command tail from memory offset 0x80 to an internal buffer near memory offset 0x037a.
  • It calls code that attempts to open agi, and if that fails it displays the local retry/quit prompt strings and reads keyboard input.
  • It similarly has a path for opening agidata.ovl.
  • A visible subroutine beginning at file offset 0x0310 performs DOS interrupt 21h calls with AH values matching open/read/close style operations based on the local byte sequence:
    • b4 3d cd 21
    • b4 3f cd 21
    • b4 3e cd 21

Current working hypotheses to verify:

  • SIERRA.COM is a loader rather than the main interpreter.
  • AGI is likely packed, encrypted, relocated, or otherwise transformed by SIERRA.COM before execution.
  • The keyOfs8 text and adjacent bytes may relate to the transformation of AGI.
  • AGIDATA.OVL may contain additional runtime data loaded by the loader.

2026-07-01: loader transform reproduced

Additional tools installed by the user:

  • nasm / ndisasm
  • rizin

Additional commands run from /Users/peter/ai/agi/reverse:

  • command -v nasm ndisasm rizin r2 radare2
  • ndisasm -v
  • rizin -v
  • python3 tools/decrypt_agi.py
  • ndisasm -b 16 -o 0x3a0 -e 0x2a0 SQ2/SIERRA.COM
  • ndisasm -b 16 -o 0x6777 -e 0x6977 build/cleanroom/AGI.decrypted.exe
  • rizin -q -e scr.color=false -A -c iH -c ie -c iS -c iz -c q build/cleanroom/AGI.decrypted.exe
  • strings -a -t x build/cleanroom/AGI.decrypted.exe
  • rizin -q -e scr.color=false -A -c afl -c q build/cleanroom/AGI.decrypted.exe
  • rizin -q -e scr.color=false -A -c izz -c q build/cleanroom/AGI.decrypted.exe
  • Commands above were also re-run with shell redirection to preserve derived disassembly and Rizin output under build/cleanroom/.

Derived local artifacts:

  • tools/decrypt_agi.py
  • build/cleanroom/AGI.decrypted.exe
  • build/cleanroom/SIERRA.COM.entry.ndisasm
  • build/cleanroom/AGI.decrypted.entry.ndisasm
  • build/cleanroom/AGI.decrypted.rizin-info.txt
  • build/cleanroom/AGI.decrypted.rizin-functions.txt
  • build/cleanroom/AGI.decrypted.rizin-strings.txt

Important ndisasm confirmations from SIERRA.COM:

  • Loader entry at memory 0x03a0, file offset 0x02a0.
  • 0x03c7 loads the string at memory 0x02c1 (agi) into DX; 0x03cb calls 0x0415, the open/read/close loader routine.
  • 0x03e0 loads the string at memory 0x02c5 (agidata.ovl) into DX; 0x03e4 calls 0x045f, which opens and immediately closes the file. This means the loader checks for AGIDATA.OVL but does not load it at this stage.
  • 0x03f9 calls 0x08fc, which prepares arguments for the transform routine.
  • 0x08fc computes an end segment from [0x0371] + [0x0373], passes the loader key table address CS:0x0141, and calls 0x09f4.
  • 0x09f4 performs the observed transform:
    • Source segment begins at [0x0371].
    • End segment is [0x0371] + [0x0373].
    • Key table is ES:DI, called as CS:0x0141.
    • For each byte, it loads the key byte, XORs the source byte in place, rotates the key byte right through carry (rcr al,1), and stores the updated key byte back.
    • It processes 128 bytes per source segment pass, advances the source segment by 8 paragraphs, and preserves carry across passes.
  • 0x0c46 validates that the transformed image begins with MZ, applies relocation entries, copies the executable image down from its MZ header location, prepares PSP state, sets SS:SP, copies the command tail, and far jumps to the MZ entry point.

Reproduced transform result:

  • tools/decrypt_agi.py applies the observed transform to SQ2/AGI.
  • The result starts with 4d 5a, an MZ signature.
  • file build/cleanroom/AGI.decrypted.exe reports MS-DOS executable.
  • Header values printed by the script and confirmed by Rizin:
    • last_page_bytes = 0x0000
    • pages = 0x004d
    • relocations = 0x0021
    • header_paragraphs = 0x0020
    • minalloc = 0x0271
    • maxalloc = 0xffff
    • initial_ss = 0x0be9
    • initial_sp = 0x0080
    • initial_ip = 0x6777
    • initial_cs = 0x0000
    • relocation_table = 0x001c

Strings recovered from the decrypted MZ image:

  • At file offset 0x0200, the image contains: Adventure Game Interpreter, copyright text, and author text.
  • Near file offsets 0x9986 through 0x99eb, the image contains overlay/file names:
    • CGA_GRAF.OVL
    • JR_GRAF.OVL
    • EGA_GRAF.OVL
    • HGC_GRAF.OVL
    • VG_GRAF.OVL
    • IBM_OBJS.OVL
    • HGC_OBJS.OVL
    • AGIDATA.OVL
    • AGI.EXE

2026-07-01: decrypted executable startup and overlays

Additional commands run from /Users/peter/ai/agi/reverse:

  • python3 -c ... one-off local parsers over build/cleanroom/AGI.decrypted.exe to print MZ relocation entries and the overlay descriptor table.
  • xxd -s 0x9900 -l 256 -g 1 build/cleanroom/AGI.decrypted.exe
  • rizin -q -e scr.color=false -A -c 'pD ...' ... build/cleanroom/AGI.decrypted.exe over focused address ranges around 0x00c4, 0x5cce, 0x5cef, 0x5d12, 0x5d52, 0x5d6b, 0x6777, 0x67c3, and 0x821c.
  • xxd and small Python byte dumps over SQ2/AGIDATA.OVL to compare loaded data offsets with strings observed in the executable.

Documented result:

  • Added docs/src/agi_executable.md.
  • The decrypted executable starts at image offset 0x6777, loads overlay 8 (AGIDATA.OVL) through a generic overlay loader at image offset 0x67c3, then switches DS to segment 0x0a01 and jumps to image offset 0x0078.
  • The overlay descriptor table begins at file offset 0x9900, image offset 0x9700. Descriptors are 16 bytes, with the first descriptor at image offset 0x9704.
  • The loader routine uses descriptor offset +0x04 as destination segment, +0x08 as filename offset within the table segment, and +0x0e as maximum read paragraph count.
  • A later routine at image offset 0x821c chooses one graphics overlay and one object overlay based on mode variables at 0x1130 and 0x112e, then calls the same overlay loader for both.

2026-07-01: resource directory and volume observations

Additional commands run from /Users/peter/ai/agi/reverse:

  • One-off local Python searches for immediate values matching AGIDATA string offsets such as 0x0955, 0x0ee8, 0x0eef, 0x116a, 0x1172, 0x117a, and 0x1182 in build/cleanroom/AGI.decrypted.exe.
  • Focused Rizin disassembly around image offsets 0x3030, 0x30f0, 0x3113, 0x42d0, 0x4305, 0x4371, 0x43a5, 0x43d9, 0x440d, and 0x4441.
  • strings -a -t x SQ2/AGIDATA.OVL
  • xxd -s 0x0ee0 -l 160 -g 1 SQ2/AGIDATA.OVL
  • A local Python parser over SQ2/LOGDIR, SQ2/PICDIR, SQ2/VIEWDIR, SQ2/SNDDIR, and SQ2/VOL.* to decode sampled 3-byte entries and inspect target bytes.
  • Focused Rizin disassembly around image offsets 0x2e32, 0x2e56, 0x2f70, 0x39f7, 0x4a3b, and 0x5126.
  • Rizin cross-reference checks for calls to 0x2e32, 0x2e56, 0x4371, 0x43a5, 0x43d9, and 0x440d.
  • Local Python validation over all non-absent directory entries to check volume header magic, volume byte, payload length, and end offsets.

Documented result:

  • Added docs/src/resource_files.md.
  • The executable loads logdir, picdir, viewdir, and snddir as complete files via a whole-file loader at image offset 0x3113.
  • Loaded directory pointers are stored at 0x11b2, 0x11b6, 0x11b4, and 0x11b8.
  • Directory entries are 3 bytes. The high nibble of byte 0 selects VOL.n, and the low nibble plus the next two bytes form a 20-bit offset.
  • Sampled directory entries point to VOL.* offsets beginning with bytes 12 34; the third byte in those sampled resource headers matches the decoded volume number.
  • The generic volume reader at image offset 0x2e56 reads a 5-byte volume record header, validates 12 34 and the volume byte, interprets the next two bytes as a little-endian payload length, then reads exactly that many payload bytes into either a supplied destination pointer or newly allocated memory.
  • The retry wrapper at image offset 0x2e32 calls 0x2e56 repeatedly until it succeeds or error state [0x0f02] becomes 5.
  • Four higher-level loaders call this generic reader through the four directory accessors:
    • Logic loader 0x119a: 0x4371 then 0x2e32.
    • View loader 0x39f7: 0x43a5 then 0x2e32.
    • Picture loader 0x4a3b: 0x43d9 then 0x2e32.
    • Sound loader 0x5126: 0x440d then 0x2e32.

2026-07-01: logic payload structure and message decoding

Additional commands run from /Users/peter/ai/agi/reverse:

  • Python one-off extraction of selected LOGDIR payloads from VOL.*, using the locally derived directory entry and volume record formats.
  • xxd -s 0x8f1 -l 80 -g 1 SQ2/AGIDATA.OVL
  • strings -a -t x SQ2/AGIDATA.OVL
  • Focused Rizin disassembly around image offsets 0x07ab, 0x119a, 0x11e8, and 0x21f0.
  • Corrected a segment assumption: routine 0x07ab uses DS:0x08f1, which at runtime is AGIDATA.OVL, not the executable image bytes at image offset 0x08f1.

Documented result:

  • Added docs/src/logic_resources.md.
  • Logic payload byte 0 and byte 1 form a little-endian bytecode length.
  • The logic bytecode begins at payload offset 0x0002.
  • The byte after the bytecode is the message count.
  • The message table begins one byte after the count and contains message_count + 1 little-endian offsets relative to the table base.
  • Message table entry 0 is used as the end pointer for the encrypted message area. Entries 1 through message_count point to individual messages.
  • The message text region starts after the offset table and is XOR-decoded in place by image offset 0x07ab.
  • The XOR key is the zero-terminated string at SQ2/AGIDATA.OVL offset 0x08f1: Avis Durgan.

2026-07-01: logic bytecode dispatcher

Additional commands run from /Users/peter/ai/agi/reverse:

  • Focused Rizin and ndisasm disassembly around image offsets 0x02c4, 0x07e3, 0x0823, 0x091a, 0x293c, and related handler ranges.
  • Local Python dumps of raw executable bytes around image offsets 0x07d0, 0x08f0, and of SQ2/AGIDATA.OVL around data offsets 0x061d and 0x08fd.
  • Local Python parser over present LOGDIR resources to use the derived action and condition operand-count tables for a conservative bytecode scan.

Documented result:

  • Added docs/src/logic_bytecode.md.
  • The main logic interpreter at image offset 0x293c executes from logic_record[0x06].
  • Main bytecode structural opcodes:
    • 0x00 (end): terminate current logic execution.
    • 0xfe: little-endian relative jump.
    • 0xff: conditional block.
  • The action dispatcher at image offset 0x02c4 uses the table at DS:0x061d, which is in AGIDATA.OVL at runtime.
  • The condition dispatcher at image offset 0x07e3 uses the table at DS:0x08fd, also in AGIDATA.OVL.
  • Both dispatch tables use 4-byte entries: handler image offset, fixed operand count, and one metadata byte.
  • Condition predicates 0x01..0x06 are direct byte-variable comparisons over the array rooted at DS:0x0009.
  • The condition parser uses marker bytes 0xfd, 0xfc, and 0xff; the exact OR-group grammar around 0xfc remains to be refined.

2026-07-01: condition parser and initial action families

Additional commands run from /Users/peter/ai/agi/reverse:

  • Focused Rizin disassembly around image offsets 0x296c, 0x7355, 0x744c, 0x74ee, 0x09ea, 0x7c1a, 0x3a77, and 0x3b47.
  • Local Python dump of selected action table entries from SQ2/AGIDATA.OVL offset 0x061d, covering opcodes 0x01..0x11, 0x23, 0x25..0x27, 0x29..0x2c, and 0xa5..0xa8.

Documented result:

  • Updated docs/src/logic_bytecode.md.
  • Refined the 0xfc condition marker behavior. The parser uses BH to track an active OR group. A true condition inside an OR group skips the remaining OR terms until the closing 0xfc; a second 0xfc while still in an OR group fails the condition list.
  • Identified the flag bitfield at DS:0x0109. Helper 0x7511 maps a flag number to byte = 0x0109 + flag / 8 and mask = 0x80 >> (flag & 7).
  • Identified flag helpers:
    • 0x74ee: set flag bit.
    • 0x74f4: clear flag bit.
    • 0x74fc: toggle flag bit.
    • 0x7502: test flag bit.
    • 0x752a: clear 0x20 bytes of flags starting at 0x0109.
  • Condition opcode 0x07 (flag_set) tests an immediate flag number; condition opcode 0x08 (flag_set_var) tests a flag number read from the byte-variable array rooted at DS:0x0009.
  • Decoded variable actions:
    • 0x01 (inc_var): saturated increment of var[arg0].
    • 0x02 (dec_var): saturated decrement of var[arg0].
    • 0x03 (assignn): assign immediate to variable.
    • 0x04 (assignv): assign variable to variable.
    • 0x05 (addn)/0x06 (addv): add immediate or variable.
    • 0x07 (subn)/0x08 (subv): subtract immediate or variable.
    • 0x09 (indirect_assignv), 0x0a (assign_indirectv), 0x0b (indirect_assignn): indirect variable assignment forms.
    • 0xa5 (muln)/0xa6 (mulv): multiply by immediate or variable, storing the low byte.
    • 0xa7 (divn)/0xa8 (divv): divide by immediate or variable, storing the 8-bit quotient.
  • Decoded flag actions:
    • 0x0c (set_flag)/0x0d (clear_flag)/0x0e (toggle_flag): set, clear, or toggle an immediate flag number.
    • 0x0f (set_flag_var)/0x10 (clear_flag_var)/0x11 (toggle_flag_var): set, clear, or toggle a flag number read from a variable.
  • Added conservative object/view action notes:
    • 0x23 calls helper 0x0a06, which validates a 43-byte object entry, copies several position/resource fields, sets bits in word field [object+0x25], and calls list/graphics helpers.
    • 0x25 and 0x26 set position-like fields [+0x03], [+0x05], [+0x16], and [+0x18] from immediates or variables.
    • 0x27 stores the low bytes of [+0x03] and [+0x05] into variables.
    • 0x29, 0x2a, 0x2b, and 0x2c resolve an object and dispatch to helpers 0x3ae7 or 0x3bb7 with either immediate or variable operands.

2026-07-01: additional common action handlers

Additional commands run from /Users/peter/ai/agi/reverse:

  • Focused Rizin disassembly around image offsets 0x113d, 0x125a, 0x39b1, 0x04d9, 0x7e7c, 0x6ce4, 0x2c7a, 0x5009, 0x510a, 0x5225, 0x7d77, 0x70b1, and 0x71c0.
  • Local Python dump of selected action table entries from SQ2/AGIDATA.OVL offset 0x061d, covering opcodes 0x14..0x17, 0x1e..0x1f, 0x21..0x22, 0x3f..0x40, 0x51..0x52, 0x62..0x64, 0x7a..0x7b, 0x82, and 0x93.

Documented result:

  • Updated docs/src/logic_bytecode.md with another batch of action semantics.
  • Action 0x14 (load_logic) loads a logic resource by immediate number via 0x117d; action 0x15 (load_logic_var) does the same with a variable-sourced number.
  • Action 0x16 (call_logic) invokes helper 0x12ae, which locates or loads a logic resource and calls the main interpreter at 0x293c on that logic, preserving the previous current logic pointer at [0x0981]; action 0x17 (call_logic_var) uses a variable-sourced logic number.
  • Actions 0x1e (load_view) and 0x1f (load_view_var) call the view-like resource loader 0x39f7 with immediate or variable-sourced resource numbers.
  • Action 0x62 (load_sound) calls the sound-like resource loader 0x5126, which uses the sound directory accessor 0x440d, generic volume reader 0x2e32, and builds four internal pointers from the payload. Action 0x64 (stop_sound_or_clear_sound_state) clears an active sound-like state through helper 0x5234.
  • Actions 0x21 (reset_object_state), 0x22 (clear_all_object_bits), 0x3f (set_global_012d), 0x40 (set_object_bit_0100), 0x51 (move_object_to), 0x52 (move_object_to_var), and 0x93 (set_object_pos_dirty) update object/global state fields and object word flags. Field names remain provisional.
  • Actions 0x7a (setup_transient_object) and 0x7b (setup_transient_object_var) fill globals 0x0eae..0x0eb3, combine one operand into the high nibble of 0x0eb3, then call helper 0x2d52, which uses the object/resource helpers 0x3ae7, 0x3bb7, and 0x3ccb.
  • Action 0x82 (random_range_to_var) stores a generated value in a variable within an inclusive range. Helper 0x71c0 seeds a 16-bit state at 0x1711 from BIOS interrupt 1a if needed, advances that state, and returns an 8-bit mixed value.
  • Inferred the dispatch-table metadata bit rule from decoded handlers and table dumps. Bit 7 corresponds to operand 0, bit 6 to operand 1, and so on. Set bits mark variable-slot/reference operands for table-aware decoding, while each handler still decides whether the slot is read or written.

2026-07-01: local logic disassembler and input/message handlers

Additional commands run from /Users/peter/ai/agi/reverse:

  • Added and ran tools/disassemble_logic.py, a local parser/disassembler for SQ2 logic resources using only the derived LOGDIR, VOL.*, logic payload, and dispatch table formats.
  • python3 tools/disassemble_logic.py 0
  • python3 tools/disassemble_logic.py --stats
  • Focused Rizin disassembly around image offsets 0x095c, 0x1c06, 0x1ce8, 0x0a8f, 0x3c55, 0x479f, 0x7a80, and 0x7b9c.

Documented result:

  • Updated docs/src/logic_bytecode.md.
  • Corrected the interpretation of the 0x0e variable-length skip rule. It applies to condition-list scanning paths, not to ordinary action opcode 0x0e (toggle_flag); action 0x0e (toggle_flag) remains the one-byte immediate flag toggle handler at 0x7492.
  • Refined linear bytecode listing behavior: action 0x00 (end) ends the current execution path, but later bytes in the same logic code area can still be branch targets, so the local static disassembler keeps scanning after 0x00.
  • Decoded condition opcode 0x0e (input_word_sequence) as a variable-length parsed-input word sequence test. Its operand stream is a byte count followed by that many little-endian word IDs. Handler 0x095c compares those word IDs with a parsed input-word buffer rooted at DS:0x0c7b, using word [0x0ca3] as the parsed-word count. Operand word 0x270f terminates the test successfully, and operand word 0x0001 behaves as a wildcard for one parsed word. On full match the handler sets flag 4.
  • Decoded action 0x65 (display_message) as immediate message display and action 0x66 (display_message_var) as variable-sourced message display. Both resolve the current logic message through helper 0x21f0 and pass the string pointer to display helper 0x1ce8.
  • Decoded actions 0x97 (display_message_configured) and 0x98 (display_message_configured_var) as configured message display variants. They set temporary globals [0x0d0b], [0x0d0d], and [0x0d09] from three operand bytes before display, then reset those globals to 0xffff.
  • Added more conservative object-action notes:
    • 0x24 (deactivate_object): deactivates/removes an active object by clearing bit 0x0001 in [object+0x25] and calling list/graphics helpers.
    • 0x2f (set_object_derived_resource_2): calls helper 0x3ccb with an immediate operand and clears object bit 0x1000.
    • 0x36 (set_object_field_24), 0x37 (set_object_field_24_var), 0x38 (clear_object_bit_0004), and 0x39 (get_object_field_24): set, set-from-variable, clear, or read object byte [+0x24] with bit 0x0004 in [+0x25].
    • 0x43 (set_object_bit_0200) and 0x44 (clear_object_bit_0200): set or clear object bit 0x0200.
    • 0x58 (set_object_bit_0002) and 0x59 (clear_object_bit_0002): set or clear object bit 0x0002.
  • The local stats pass reports LOGDIR entry 141 as an invalid-looking target: it decodes to VOL.0 offset 0x1ffff, where no valid 12 34 volume header is present.

2026-07-01: additional object and picture action handlers

Additional local tools and artifacts used:

  • The MS-DOS 6.22 hard disk image at build/dos622/dos622.img was created locally with QEMU and mtools.
  • SQ2 was copied into that image under C:\SQ2.
  • QEMU was used to boot DOS, run SIERRA.COM, and capture screenshots showing the title sequence and intro scene. This confirmed that the local DOS/QEMU setup can execute the game, but the handler work below is still based on static disassembly.

Additional commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py --stats
  • python3 -B tools/disassemble_logic.py 0 1 2
  • Focused Rizin disassembly around image offsets 0x1700, 0x4a00, 0x6a80, and 0x6c80.
  • Focused ndisasm disassembly around image offsets 0x47e0, 0x497b, 0x4b80, 0x6b80, 0x6c54, 0x6e02, 0x6f3e, 0x7000, 0x74b0, and 0x7e00.
  • Additional focused ndisasm disassembly around image offsets 0x2250, 0x7dba, 0x911d, 0x91cf, and 0x93b1. These runs used file skips equal to image offset plus 0x200; an earlier shifted dump around the 0x7dba area was rejected after the exact handler offset was rechecked.
  • Local Python dump of selected action table entries from SQ2/AGIDATA.OVL offset 0x061d, covering opcodes 0x18..0x1b, 0x2d..0x2e, and 0x3a..0x57.

Documented result:

  • Updated tools/disassemble_logic.py with conservative names for newly decoded common action opcodes.
  • Updated docs/src/logic_bytecode.md.
  • Added picture-like action notes:
    • 0x18 (load_picture_var): variable-sourced picture-like resource load through helper 0x4a3b.
    • 0x19 (prepare_picture_var): variable-sourced picture-like resource preparation through helper 0x4acf.
    • 0x1a (show_picture_like): picture/display finalization-like action that clears flag 15, calls helpers 0x1f2b and 0x5546, and sets [0x1216] = 1.
    • 0x1b (discard_picture_var): variable-sourced picture-like resource unlink/release helper.
  • Added object bit actions:
    • 0x2d (set_object_bit_2000)/0x2e (clear_object_bit_2000): set/clear object bit 0x2000.
    • 0x3a (clear_object_bit_0010)/0x3b (set_object_bit_0010): clear/set object bit 0x0010 through helpers that wrap the update in redraw/cache calls.
    • 0x3d (set_object_bit_0008)/0x3e (clear_object_bit_0008): set/clear object bit 0x0008.
    • 0x40 (set_object_bit_0100): set object bit 0x0100.
    • 0x41 (set_object_bit_0800): set object bit 0x0800.
    • 0x42 (clear_object_bits_0900): clear object bits 0x0100 and 0x0800.
    • 0x46 (clear_object_bit_0020)/0x47 (set_object_bit_0020): clear/set object bit 0x0020.
  • Added object field/action notes:
    • 0x45 (object_distance_to_var): computes a capped distance-like value between two active objects and stores it in a variable, or stores 0xff if either object is inactive.
    • 0x48..0x4b: set object byte [+0x23] to modes 0, 1, 3, or 2, with the mode 1 and mode 2 forms also setting bits 0x1030, storing an immediate in [+0x27], and clearing the corresponding flag.
    • 0x4c (set_object_field_1f_var), 0x4f (set_object_field_1e_var), 0x50 (set_object_field_01_var), 0x56 (set_object_field_21_var), and 0x57 (get_object_field_21): move values between variables and object bytes [+0x1f], [+0x1e], [+0x01], and [+0x21].
    • 0x4d (clear_object_fields_21_22), 0x4e (clear_object_field_22_and_global), 0x53 (approach_first_object_until_near), 0x54 (start_random_motion), 0x55 (stop_motion_mode), 0x83 (clear_global_0139), and 0x84 (set_global_0139_and_clear_object0_field_22): update object byte [+0x22] and related globals, especially [0x0139].
  • Added interpreter/resource-control notes:
    • 0x12 (switch_room_like)/0x13 (switch_room_like_var): broad room/state switch helpers that stop active sound, reset object entries, update byte variable 0, load the target logic, set flag 5, clear the status table at 0x1218, and call redraw/reinit helpers.
    • 0x63 (start_sound_with_flag): starts a sound-like resource and associates a flag with completion or active-state handling by storing it in [0x126a].
  • Added formatted-message and menu/list-like UI notes:
    • 0x67 (display_formatted_message)/0x68 (display_formatted_message_var): configured formatted-message display helpers. They call setup helper 0x2b28, pass two placement/configuration values to 0x2b0d, resolve a current-logic message through 0x21f0, format/copy it into a large stack buffer via 0x1f54, send it to 0x2390, then call cleanup helper 0x2b4f. The 0x68 form reads all three operands through variables.
    • 0x9c (add_menu_heading_like): allocates and links an 18-byte top-level node that stores a message pointer, active marker, and position-like value.
    • 0x9d (add_menu_item_like): allocates and links a 14-byte item node under the current top-level node, storing message pointer, item id, active marker, and row/column-like values.
    • 0x9e (finalize_menu_like): finalizes the menu/list-like structure and sets [0x1d2a] = 1, after which later additions are ignored by the handlers.
    • 0x9f (enable_menu_item_like)/0xa0 (disable_menu_item_like): walk the menu/list-like structure and set or clear the active marker on item nodes whose stored id matches the operand.
    • 0xa1 (mark_menu_if_flag_0e): tests flag 0x0e; if set, writes [0x1d22] = 1.
  • Corrected action 0x94 (set_object_pos_dirty_var): exact disassembly at image offset 0x7dba shows that it is the variable-coordinate counterpart to 0x93, storing var[arg1] and var[arg2] into object fields [+0x03] and [+0x05], setting bit 0x0400, and calling helper 0x593a.

2026-07-01: text-window and auxiliary table action handlers

Additional commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py --stats
  • python3 -B tools/disassemble_logic.py 0 1 2 3 4 5 6 7 8 9 10
  • Local Python dump of selected action table entries from SQ2/AGIDATA.OVL offset 0x061d, covering opcodes 0x5a..0x61, 0x69..0x71, 0x76..0x79, 0x81, and 0xa2..0xa4.
  • Focused ndisasm disassembly around image offsets 0x2b78, 0x34bd, 0x3547, 0x382e, 0x386f, 0x4c3d, 0x5e9b, 0x5ebf, 0x7538, 0x7663, 0x7714, 0x7803, 0x7a00, and 0x7b4e.

Documented result:

  • Updated tools/disassemble_logic.py with conservative names for another batch of action opcodes, mostly text-window, prompt/status, and auxiliary table handlers.
  • Added an operand metadata override for action 0xa2 (display_view_resource_text_like_var): the table byte is 0x01, but exact handler disassembly at 0x5e9b shows it reads the resource number from var[arg0].
  • Added text-window/action notes:
    • 0x69 (clear_text_rect): clears/fills a text rectangle through BIOS int 10h service AH=0x06 via helper 0x2b78.
    • 0x6a (enable_text_attr_mode_1757)/0x6b (disable_text_attr_mode_1757): enable/disable an alternate text-attribute mode tracked by byte [0x1757], then refresh related text areas.
    • 0x6d (set_text_window_pair): updates globals [0x05d1], [0x05cd], and [0x05cf] through helper 0x77d5.
    • 0x6e (shake_screen_like): display-shake-like action that either calls display-mode-specific helpers or writes CRT controller ports 0x3d4/0x3d5 directly.
    • 0x70 (show_status_line_like)/0x71 (hide_status_line_like): show/hide a status-line-like area controlled by word [0x05d9].
    • 0x77 (disable_input_line_like)/0x78 (enable_input_line_like): disable/enable an input-line-like area controlled by word [0x05d3].
    • 0xa3 (set_global_0d0f)/0xa4 (clear_global_0d0f): set/clear word [0x0d0f], which helper 0x3652 consults while updating input-line display state.
  • Added auxiliary resource/table notes:
    • 0x5a (set_rect_bounds_0131)/0x5b (clear_rect_bounds_0131): set/clear a rectangle/bounds filter stored in globals [0x0131], [0x0133], [0x0135], [0x0137], and [0x013d].
    • 0x5c..0x61: manipulate byte [+0x02] in 3-byte entries from the table rooted at [0x0971], validating against end pointer [0x0973].
    • 0x79 (map_key_event): stores (arg0 | arg1 << 8, arg2) into the first free four-byte slot among 39 slots rooted at 0x0145.
    • 0x81 (display_view_resource_text_like)/0xa2 (display_view_resource_text_like_var): immediate and variable forms of a view-like resource display/preview helper that loads the resource, builds a temporary object-like record, displays a resource-derived string through 0x1ce8, and cleans up temporary allocations.

2026-07-01: resource accessors and prompt/session handlers

Additional commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py --stats
  • Local Python dump of selected action table entries from SQ2/AGIDATA.OVL offset 0x061d, covering opcodes 0x2f..0x35, 0x72..0x76, 0x85..0x86, 0x8e, and 0x91..0x92.
  • Focused ndisasm disassembly around image offsets 0x027f, 0x0d37, 0x1335, 0x3c8c, 0x3e25, 0x4de8, 0x4e8d, 0x5234, 0x716a, and 0x71ed.
  • python3 -B tools/disassemble_logic.py --limit 200 | rg ... was attempted for call-site sampling, but the helper aborted at the known invalid LOGDIR entry 141. The partial output before that abort was used only as supporting call-site evidence; the verification pass continues to use --stats, which records and skips the bad entry.

Documented result:

  • Added resource-derived object accessor notes:
    • 0x30 (set_object_derived_resource_2_var): variable-argument counterpart to 0x2f, calling helper 0x3ccb. The helper selects a derived subresource/loop-like entry, updates object byte [+0x0e], pointer [+0x10], words [+0x1a] and [+0x1c], clamps object coordinates, and sets bit 0x0400 if it adjusts them.
    • 0x31 (get_object_resource_loop_count): stores *([object+0x0c]) - 1 into a variable, apparently a count from the object’s loaded resource table.
    • 0x32 (get_object_field_0e), 0x33 (get_object_field_0a), 0x34 (get_object_field_07), and 0x35 (get_object_field_0b): copy object bytes [+0x0e], [+0x0a], [+0x07], and [+0x0b] into variables. 0x35 was present in the table but not encountered in the current SQ2 scan.
  • Added string/prompt notes:
    • 0x72 (set_string_slot_from_message): copies a current-logic message into fixed string slot 0x020d + arg0 * 0x28 through helper 0x4de8.
    • 0x76 (prompt_number_to_var): displays a current-logic message prompt, accepts up to four characters through helper 0x0da9, parses the result as decimal via 0x4e8d, and stores the low byte in a variable.
    • 0x85 (display_object_diagnostics_var): formats several object fields into a display string using template pointer 0x1713, then displays it through 0x1ce8.
  • Added interpreter/session-control notes:
    • 0x86 (confirm_and_restart_like): stops sound state and conditionally calls helper 0x02ae, which calls 0x8275 and 0x00ae(0). The handler displays string 0x05e3 as a confirmation path when the operand is not 1.
    • 0x8e (set_global_0141_and_refresh): stores a word at [0x0141] and calls refresh helper 0x707c wrapped in 0x6a54/0x6a8e.
    • 0x91 (save_logic_resume_ip): saves the current bytecode pointer into [current_logic+0x06].
    • 0x92 (restore_logic_entry_ip): restores [current_logic+0x06] from [current_logic+0x04].

2026-07-01: graphics and object pipeline synthesis

Additional commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,220p' AGENTS.md
  • rg -n "picture|draw|object|graphics|0x4a3b|0x4acf|0x6445|0x6a54|0x6a8e|0x593a|0x59fa|0x3ae7|0x3ccb|0x09ea|0x0a06" docs/src tools
  • ls docs/src
  • python3 -B tools/disassemble_logic.py --stats
  • Focused ndisasm disassembly around image offsets 0x0307, 0x09ea, 0x3979, 0x3bb7, 0x4a16, 0x5200, 0x5546, 0x593a, 0x6440, 0x6a20, 0x6a54, and 0x9097. These runs used file skips equal to image offset plus 0x200, matching the decrypted MZ header size.
  • Additional reads of existing docs with sed and rg to avoid duplicating opcode tables already captured in logic_bytecode.md.

Documented result:

  • Added docs/src/graphics_object_pipeline.md.
  • Added the new chapter to docs/src/SUMMARY.md.
  • Consolidated the picture load/decode path:
    • Action 0x18 (load_picture_var) calls loader 0x4a3b, which uses cache lookup 0x49e8, directory accessor 0x43d9, and generic reader 0x2e32.
    • Action 0x19 (prepare_picture_var) calls helper 0x4acf, stores the selected payload pointer at [0x1377], wraps the operation in 0x6a54/0x6a8e, and decodes the picture through 0x6445.
    • Action 0x1a (show_picture_like) clears flag 15, calls display helpers, and sets [0x1216] = 1.
  • Documented the picture command scanner at 0x6475, including the 0xf0..0xfa dispatch range, the 0xff terminator, and the observed drawing globals [0x1369], [0x136b], [0x136c], [0x136d], [0x136e], and [0x136f].
  • Expanded the view/object binding model:
    • Helper 0x3ae7 binds a cached view-like payload to object fields +0x07 and +0x08, copies payload byte +0x02 into object byte +0x0b, and delegates to 0x3bb7.
    • Helpers 0x3c1b, 0x3ccb, and 0x3d6a select nested subresources, update object pointer/size fields, and clamp object coordinates.
  • Added a field map for the 43-byte object records rooted at [0x096b].
  • Documented object activation/deactivation helpers 0x0a06 and 0x0aab, including their list flushing/rebuild calls and the observed active bit 0x0001.
  • Documented placement helper 0x593a and bounds helper 0x5a14, including the screen limits 0xa0 and 0xa7 and the horizon-like global [0x012d].
  • Clarified the update-list wrappers:
    • 0x6a26 builds list root 0x16ff through shared builder 0x0358.
    • 0x6a3d builds list root 0x1703 through shared builder 0x0358.
    • 0x6a54 flushes both roots through 0x0307.
    • 0x6a8e rebuilds and processes both roots through 0x045e.
    • 0x6aab compares current and saved object fields through 0x0488.

2026-07-01: view payload and object overlay rendering

Additional commands run from /Users/peter/ai/agi/reverse:

  • rg -n "0x3d6a|0x9097|0x9177|0x9db0|0x9db6|0x5762|view payload|render/update|update node|0x042f|0x0358" docs/src tools
  • Focused ndisasm disassembly around main executable image offsets 0x0307, 0x0358, 0x3d6a, 0x587d, and 0x9097. A few intermediate disassembly commands used mismatched -o/-e values and were rejected; the observations documented here use the corrected convention -o image_offset and -e image_offset + 0x200.
  • wc -c build/cleanroom/AGI.decrypted.exe SQ2/EGA_GRAF.OVL SQ2/IBM_OBJS.OVL SQ2/AGIDATA.OVL
  • file SQ2/EGA_GRAF.OVL SQ2/IBM_OBJS.OVL SQ2/AGIDATA.OVL
  • xxd -l 128 -g 1 SQ2/EGA_GRAF.OVL
  • xxd -l 128 -g 1 SQ2/IBM_OBJS.OVL
  • ndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVL
  • ndisasm -b 16 -o 0x9800 SQ2/EGA_GRAF.OVL
  • python3 -B tools/inspect_view.py 0 1 2 10 --groups 4 --frames 5
  • python3 -B tools/inspect_view.py --limit 12 --groups 2 --frames 3
  • python3 -B tools/inspect_view.py 11 --groups 4 --frames 4
  • python3 -B -m py_compile tools/disassemble_logic.py tools/inspect_view.py

Documented result:

  • Added tools/inspect_view.py, a deterministic local helper for printing the observed view-like payload structure using only the locally derived directory and volume readers.
  • Confirmed that calls to 0x9db0, 0x9db3, and 0x9db6 target IBM_OBJS.OVL, which is loaded at segment 0x09db and therefore appears at near offsets starting at 0x9db0.
  • Documented the three IBM object-overlay entry jumps:
    • 0x9db0 -> 0x9db9: save a screen rectangle into a node backing buffer.
    • 0x9db3 -> 0x9df8: restore a screen rectangle from a node backing buffer.
    • 0x9db6 -> 0x9e35: draw an object’s selected frame into the graphics buffer.
  • Documented render/update node layout from allocator 0x9097: next pointer, previous pointer, object pointer, rectangle coordinates/dimensions, and a backing-buffer pointer.
  • Expanded the view-like payload format:
    • Payload byte +0x02 is the top-level group count.
    • The group offset table begins at payload +0x05, with 16-bit offsets relative to the payload base.
    • Each group starts with a frame count, followed by 16-bit frame offsets relative to the group base.
    • Each frame begins with width, height, and a control byte, followed by row-terminated encoded data.
  • Local tools/inspect_view.py samples matched the helper-derived layout. For example, view 11 has two groups; group 0 starts at offset 0x09, has two frames, and its first frame starts at offset 0x0e with size 20x5 and control byte 0x01.
  • Documented the current frame-data model from object overlay draw routine 0x9e35: zero bytes terminate rows; nonzero bytes use the high nibble as a color-like value and the low nibble as a run length; a run whose high nibble matches the frame control byte’s low nibble advances without writing.

2026-07-01: object update-list selection and movement pass

Additional commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' AGENTS.md
  • sed -n '1,260p' docs/src/graphics_object_pipeline.md
  • tail -n 180 docs/src/clean_room_executable_notes.md
  • rg -n "0x150a|0x4719|0x56b8|0x69e4|0x6a05|0x6b44|0x6b62|0x0400|0x0051" docs/src tools
  • Focused ndisasm disassembly around main executable image offsets 0x13f0, 0x4619, 0x55b8, 0x583a, and 0x69c0. Some intermediate commands used mismatched file skips and were rejected; the observations below use the corrected convention -o image_offset and -e image_offset + 0x200 for build/cleanroom/AGI.decrypted.exe.

Documented result:

  • Refined the update-list predicates:
    • Callback 0x69e4, used by builder wrapper 0x6a26 for root 0x16ff, accepts objects when (object[+0x25] & 0x0051) == 0x0051.
    • Callback 0x6a05, used by builder wrapper 0x6a3d for root 0x1703, accepts objects when (object[+0x25] & 0x0051) == 0x0041.
    • Therefore flag bit 0x0010 partitions otherwise active/eligible objects between the two update-list roots. Helpers 0x6b44 and 0x6b62 clear and set that bit while wrapping the change with 0x6a54/0x6a8e.
  • Documented movement pass 0x150a:
    • It clears event globals [0x000e], [0x000d], and [0x000b].
    • It scans object records from [0x096b] to [0x096d] in 0x2b-byte strides, processing only objects whose flag word satisfies (object[+0x25] & 0x0051) == 0x0051.
    • Object byte +0x01 is a countdown/tick divider reloaded from byte +0x00.
    • Unless bit 0x0400 is set, direction byte +0x21, step byte +0x1e, and signed-delta tables at 0x0a61 and 0x0a73 produce proposed X/Y movement.
    • Proposed movement is clamped to left, right, top, bottom, and horizon-like bounds, producing boundary codes 1 through 4.
    • The move is accepted only when 0x4719(object) returns zero and 0x56b8(object) returns nonzero. Otherwise the previous X/Y coordinates are restored and placement search helper 0x593a(object) is called.
    • Boundary events are written to [0x000b] for objects with byte +0x02 == 0 or to [0x000d]/[0x000e] for nonzero byte +0x02. If byte +0x22 == 3, helper 0x16b9(object) ends that motion/control state.
    • The pass clears object bit 0x0400 before leaving an object.
  • Documented helper 0x4719(object) as an object-object collision/crossing test. It skips objects with bit 0x0200, skips candidates with matching byte +0x02, checks horizontal rectangle overlap from X and width, then checks whether current and previous Y positions cross.
  • Documented helper 0x56b8(object) as a control/priority-buffer acceptance test. It may derive object byte +0x24 from table 0x127a, scans high nibbles in the graphics/control buffer at [0x136f], reacts to nibble classes 0x10, 0x20, and 0x30, and returns nonzero to permit a proposed move.
  • Added targeted-motion notes for helpers 0x1672, 0x16ed, and 0x16b9: direction-like byte +0x21 can be computed from the current object position to target fields +0x27/+0x28, and 0x16b9 restores byte +0x1e from +0x29 while setting completion flag +0x2a and clearing motion/control byte +0x22.

2026-07-01: graphics/control buffer helper pass

Additional commands run from /Users/peter/ai/agi/reverse:

  • Focused ndisasm disassembly around main executable image offsets 0x4c80, 0x5200, 0x5480, 0x5528, 0x5660, 0x5700, and 0x57c0.
  • xxd -s 0x52b5 -l 8 -g 2 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x0 -e 0x200 build/cleanroom/AGI.decrypted.exe | rg "call 0x4d10|call 0x56a2|call 0x4cbb|call 0x5666|call 0x56b8|call 0x5762|call 0x57cf"
  • A raw xxd read at file offset 0x127a and an origin-shifted whole-image call-site scan were rejected. The 0x127a table described below is a runtime table initialized by code, and the accepted whole-image call-site scan uses -o 0x0 -e 0x200.

Documented result:

  • Added a graphics/control buffer helper section to docs/src/graphics_object_pipeline.md.
  • Documented helper 0x5257 as a buffer fill routine for the segment stored in [0x136f]. It writes 0x3480 words, matching a 0x6900-byte grid. Picture decoding calls it with AX = 0x4f4f, while helper 0x5528 calls it with AX = 0x4040.
  • Documented helper 0x5666 as the direct coordinate-to-buffer conversion DI = y * 0xa0 + x, with AL = y and AH = x.
  • Documented helper 0x56a2 as the default initializer for the 168-byte table rooted at 0x127a: rows 0..47 map to 4, and subsequent 12-row bands map to values 5 through 14.
  • Documented helper 0x4cbb(value) as a reverse mapping from a priority/control value toward a Y row. In one mode it scans the 0x127a table downward; when [0x124a] is nonzero it uses (value - 5) * 12 + 0x30.
  • Documented helper 0x57cf(object) as a post-draw buffer marker. It calls the object overlay draw entry 0x9db6, ensures object byte +0x24 has a low nibble derived from the Y table if absent, and writes the high nibble of +0x24 around the object’s buffer footprint while preserving low nibbles.
  • Refined helper 0x56b8(object): it scans the high nibbles of the selected frame width at the object’s X/Y buffer row. High nibble 0x00 rejects immediately; 0x10 requires object flag bit 0x0002; 0x20 records a seen class; 0x30 continues without changing the tracked class. After the scan, bits 0x0100 and 0x0800 can reject the final class state, and objects with byte +0x02 == 0 update global flags 3 and 0 through 0x74ee/0x74f4.

2026-07-01: transient object and view-preview rendering pass

Additional commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,220p' AGENTS.md
  • sed -n '1,460p' docs/src/graphics_object_pipeline.md
  • tail -n 180 docs/src/clean_room_executable_notes.md
  • rg -n "0x57cf|0x5a14|0x5a3a|0x5fd3|0x6006|0x56b8|0x5762|0x980c|0x9812|0x9db6|0x1c54|0x2e1b|0x2e28" docs/src tools
  • Focused ndisasm disassembly around main executable image offsets 0x1c20, 0x2c40, and 0x5e80, using -e image_offset + 0x200.
  • python3 -B tools/disassemble_logic.py --limit 5 | sed -n '1,220p'
  • Follow-up reads of the existing 0x7a, 0x7b, 0x81, and 0xa2 opcode notes in docs/src/logic_bytecode.md.
  • python3 -B tools/inspect_view.py --limit 40 --groups 1 --frames 1
  • sed -n '1,260p' tools/inspect_view.py

Documented result:

  • Added a transient/preview object section to docs/src/graphics_object_pipeline.md.
  • Refined the 0x7a and 0x7b entries in docs/src/logic_bytecode.md.
  • Updated tools/inspect_view.py to print the observed preview/display string offset from u16(payload + 0x03).
  • Documented actions 0x7a (setup_transient_object)/0x7b (setup_transient_object_var) as callers of helper 0x2d52, which uses a fixed 43-byte object-like record at 0x0eb4.
    • Staged byte 0x0eae selects the view-like resource.
    • 0x0eaf selects the top-level group.
    • 0x0eb0 selects the frame/derived entry.
    • 0x0eb1 and 0x0eb2 are X/Y coordinates.
    • The low and high nibbles of 0x0eb3 feed object byte +0x24.
    • The helper binds the view through 0x3ae7, selects group/frame through 0x3bb7 and 0x3ccb, places the object with 0x593a, draws/marks it through 0x57cf, rebuilds update lists with 0x6a54/0x6a8e, and calls 0x5762.
  • Documented the fixed transient record’s initialization:
    • Its selected frame pointer is copied to saved-frame field +0x12.
    • Staged X/Y are copied into both current and saved coordinate fields.
    • Flag word +0x25 starts as 0x020c, combining fixed-priority, horizon-exempt, and collision-skip behavior.
    • If the staged priority/control low nibble is zero, the helper later replaces the flag word with 0x0008 before drawing/marking.
  • Refined the 0x81/0xa2 view-resource preview path:
    • Helper 0x5edb records whether the view resource was already cached, temporarily sets [0x0f18] = 1 while loading it, and initializes a stack-local 43-byte object-like record with group/frame zero.
    • The temporary preview object is centered with x = (0x9f - width) / 2, given y = 0xa7, fixed priority/control byte +0x24 = 0x0f, and grouping byte +0x02 = 0xff.
    • If enough memory is available, the helper allocates a render node through 0x9097, saves the backing rectangle with 0x9db0, draws with 0x9db6, and later restores with 0x9db3 and frees with 0x910a.
    • The displayed string pointer is payload + u16(payload + 0x03), giving the first observed consumer for view payload bytes +0x03..+0x04.
    • The first 40 present SQ2 view resources sampled by tools/inspect_view.py all had u16(payload + 0x03) == 0, so a nonzero local example remains to be found.
    • If the resource was not cached before the preview, the helper releases it through 0x3f0d.

2026-07-01: object rectangle conditions and configured motion bounds

Additional commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,220p' AGENTS.md
  • rg -n "condition|0x0b|object_rect|rect_test|0x08c6|0x7be6|0x013d|0x0131|0x4719|0x47ef" docs/src tools
  • sed -n '260,360p' docs/src/logic_bytecode.md
  • sed -n '1,520p' docs/src/graphics_object_pipeline.md
  • Focused ndisasm disassembly around main executable image offsets 0x0800, 0x0680, 0x06c0, 0x47e0, and 0x7b40, using -e image_offset + 0x200.
  • ndisasm -b 16 -o 0x0 -e 0x200 build/cleanroom/AGI.decrypted.exe | rg "call 0x7be6|call 0x08c6|call 0x08cc|call 0x08db|call 0x08e8|call 0x091a|call 0x47ef"
  • Follow-up reads of the condition-name table in tools/disassemble_logic.py and the condition documentation in docs/src/logic_bytecode.md.

Documented result:

  • Refined the local names for condition opcodes 0x0b (object_left_baseline_in_rect), 0x10 (object_width_baseline_in_rect), 0x11 (object_center_baseline_in_rect), and 0x12 (object_right_baseline_in_rect) in tools/disassemble_logic.py.
  • Expanded the condition documentation for object rectangle tests:
    • Shared helper 0x091a resolves object index arg0, loads object X into DH and CH, and loads object Y into DL.
    • Common comparison helper 0x08f0 checks DH >= arg1, DL >= arg2, CH <= arg3, and DL <= arg4.
    • Condition 0x0b (object_left_baseline_in_rect) tests object left X/baseline Y.
    • Condition 0x10 (object_width_baseline_in_rect) tests the full horizontal span from left X to x + width - 1.
    • Condition 0x11 (object_center_baseline_in_rect) tests horizontal center X.
    • Condition 0x12 (object_right_baseline_in_rect) tests right X.
  • Documented the configured rectangle helper:
    • Action 0x5a (set_rect_bounds_0131) stores bounds in [0x0131], [0x0133], [0x0135], and [0x0137], and sets [0x013d] = 1.
    • Action 0x5b (clear_rect_bounds_0131) clears [0x013d].
    • Helper 0x7be6(x, y) returns true only for points strictly inside the configured rectangle.
    • Helper 0x06d9(object) compares whether the object’s current baseline point and next step point are on the same side of that rectangle. A crossing sets object bit 0x0080, clears direction byte +0x21, and clears global byte [0x000f] when the object is the first object record. No crossing clears bit 0x0080.

2026-07-01: object dirty rectangles and graphics-overlay refresh entries

Additional commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n '1,240p' AGENTS.md
  • sed -n '1,260p' docs/src/graphics_object_pipeline.md
  • tail -n 120 docs/src/clean_room_executable_notes.md
  • rg -n "0x5762|0x970c|0x9812|0x9815|0x5546|0x5528|0x9db0|0x9db3|0x9db6|display" docs/src tools
  • Focused ndisasm disassembly around main executable image offsets 0x5500, 0x5700, and 0x9600, using -e image_offset + 0x200.
  • sed -n '430,530p' docs/src/graphics_object_pipeline.md
  • sed -n '160,210p' docs/src/agi_executable.md
  • rg -n "0x9800|0x980c|0x9812|0x9815|0x9837|JR_GRAF|CGA_GRAF|IBM_OBJS|load overlay|OVL" docs/src tools
  • ls -l build/cleanroom/AGI.decrypted.exe SQ2/*.OVL
  • ndisasm -b 16 -o 0x9800 SQ2/EGA_GRAF.OVL
  • ndisasm -b 16 -o 0x9800 SQ2/CGA_GRAF.OVL
  • ndisasm -b 16 -o 0x9800 SQ2/VG_GRAF.OVL
  • Follow-up reads of the render/update section in docs/src/graphics_object_pipeline.md.

Documented result:

  • Replaced the previous tentative note about helper 0x5762 with a concrete dirty-rectangle interpretation:
    • It returns immediately unless word [0x1216] is nonzero.
    • It compares the current frame pointer object+0x10 and saved frame pointer object+0x12, plus current/saved X/Y fields +0x03/+0x05 and +0x16/+0x18.
    • It copies the current frame pointer to +0x12.
    • It computes the union rectangle covering the old and new object frame footprints.
    • It calls graphics-overlay entry 0x980c with that union rectangle.
  • Documented the common rectangle argument contract used by 0x980c and 0x9812:
    • AH = left X
    • AL = bottom Y
    • BL = width
    • BH = height
  • Documented graphics-overlay entry 0x980c as a rectangle copy from the interpreter’s logical graphics buffer segment [0x136f] to display memory segment [0x1371].
  • Documented graphics-overlay entry 0x9812 as a rectangle fill; in the EGA and VGA overlays, low byte DL supplies the fill value.
  • Refined helpers around the full-screen display path:
    • 0x5528 clears the logical graphics buffer with fill word 0x4040, calls graphics-overlay entry 0x980f, rebuilds the default priority/control table with 0x56a2, then calls entry 0x9800.
    • 0x5546 can swap nibbles across the logical graphics buffer when [0x1755] & 1 is set, calls HGC-specific helper 0x9899 in display mode 2, then calls 0x980c for the full 0xa0 by 0xa8 screen rectangle.
    • 0x5624 converts the common coordinate tuple into display-memory offsets, with display-mode branches controlled by [0x1130] and [0x112e].
  • Added the EGA graphics overlay entry table from local disassembly of SQ2/EGA_GRAF.OVL loaded at near origin 0x9800:
    • 0x9800 -> 0x9815: set graphics mode 0x0d and store video segment 0xa000 in [0x1371].
    • 0x9803 -> 0x9835: return to text mode and clear/configure the text screen.
    • 0x9806 -> 0x986f: reinitialize graphics and call 0x5546.
    • 0x9809 -> 0x9884: no-op in EGA.
    • 0x980c -> 0x9885: copy a logical-buffer rectangle to EGA display memory.
    • 0x980f -> 0x9983: initialize row-offset table 0x137b and clear a display-memory range.
    • 0x9812 -> 0x9907: fill a display rectangle.

2026-07-01: update-list phase order and stationary object flag

Additional commands run from /Users/peter/ai/agi/reverse:

  • Focused ndisasm disassembly around main executable image offsets 0x0300, 0x0400, and 0x69c0, using -e image_offset + 0x200.
  • ndisasm -b 16 -o 0x0 -e 0x200 build/cleanroom/AGI.decrypted.exe | rg "4000|6a54|6a8e|6aab|045e|0488|0307|0358"
  • Focused follow-up disassembly around image offsets 0x0b80, 0x3f30, and 0x67f0, using sed to limit the visible output.

Documented result:

  • Refined the update-list lifecycle:
    • 0x0307(root) walks root nodes, restores each saved rectangle through 0x9db3, then calls 0x032d(root) to free nodes and clear root pointers.
    • 0x032d(root) frees nodes through 0x910a without doing the restore pass.
    • 0x045e(root) walks from the list tail backward, saving each node’s backing rectangle with 0x9db0 and drawing the node’s object through 0x9db6.
    • 0x6a54 restores/frees roots 0x16ff and 0x1703 through 0x0307.
    • 0x6a71 frees both roots through 0x032d without restoration.
    • 0x6a8e rebuilds and draws root 0x1703 first, then root 0x16ff.
    • 0x6aab runs 0x0488 over root 0x1703 first, then root 0x16ff.
  • Refined helper 0x0488(root):
    • For each node it calls 0x5762(object) before comparing saved fields.
    • It only performs the position comparison when object byte +0x01 equals reload byte +0x00.
    • If current X/Y +0x03/+0x05 equal saved X/Y +0x16/+0x18, it sets flag bit 0x4000.
    • Otherwise it copies current X/Y to saved X/Y and clears bit 0x4000.
  • Refined object flag bit 0x4000 from a generic comparison marker to a stationary/stuck marker used by later motion helpers.
  • Observed two consumers of bit 0x4000:
    • Helper 0x3f5a, reached from motion/control mode byte +0x22 == 1, picks a new random direction through 0x3fa3 when its local countdown expires or when bit 0x4000 is set.
    • The helper around 0x0bb3, reached from the +0x22 == 2 path, can also replace direction byte +0x21 with a random nonzero direction when bit 0x4000 reports no movement.

2026-07-01: logic cache lifetime and room-switch scheduler path

Additional commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n '1,220p' AGENTS.md
  • sed -n '1,360p' docs/src/logic_bytecode.md
  • sed -n '1,280p' docs/src/logic_resources.md
  • tail -n 220 docs/src/clean_room_executable_notes.md
  • rg -n "0x117d|0x119a|0x12ae|0x1364|0x13a5|0x1792|heap|cache|logic record|0x0977|0x0985|0x0983" docs/src tools/disassemble_logic.py
  • Initial raw ndisasm reads around the same regions. These produced too much output because -e is the input skip amount, not an end offset; they were used only for coarse orientation.
  • Focused follow-up ndisasm disassembly around main executable image offsets 0x10d0, 0x117d, 0x1364, and 0x1792, using the decrypted executable header skip and sed to limit the visible output.

Documented result:

  • Expanded docs/src/logic_resources.md with the 10-byte logic cache record layout:
    • +0x00 next record pointer.
    • +0x02 logic number byte.
    • +0x03 message count byte.
    • +0x04 bytecode base pointer, equal to payload + 2.
    • +0x06 current interpreter instruction pointer.
    • +0x08 message offset table base pointer.
  • Documented helper 0x110f(logic_number) as the logic-cache scan. It walks the list rooted at [0x0977] and stores the link slot for the matching or insertion position in [0x0983].
  • Refined loader 0x119a(logic_number):
    • On cache miss it calls 0x6a54, allocates a 10-byte record through 0x13d6, links it through [0x0983], loads the resource through 0x4371 and 0x2e32, derives bytecode/message pointers, temporarily sets [0x0981] while decrypting message text, then calls 0x6a8e.
    • On cache hit it returns the existing record.
  • Documented call helper 0x12ae(logic_number):
    • It preserves the previous current logic pointer [0x0981].
    • If the target logic is already cached, it interprets that record in place.
    • If the target logic is missing, it loads it through 0x119a, runs interpreter 0x293c, unlinks it afterward through the saved [0x0983] slot, and rewinds the heap top to the start of that transient record through 0x143c.
    • Actions 0x16 (call_logic) and 0x17 (call_logic_var) propagate a zero interpreter result as a zero next-instruction pointer, stopping the current logic loop.
  • Documented routine 0x1364 as a snapshot writer for loaded logic execution positions. It emits 4-byte entries at 0x0985 containing logic number and current_ip - bytecode_base, followed by a 0xffff terminator.
  • Documented routine 0x13a5(record) as the matching restore path, setting record[0x06] = record[0x04] + saved_offset when it finds the record’s logic number in the 0x0985 table.
  • Added heap-pointer helpers used by this path:
    • 0x13d6(size) allocates from [0x0a55].
    • 0x143c(ptr) sets or rewinds [0x0a55].
    • 0x1485 restores the heap pointer from mark [0x0a59] after freeing update-list nodes.
    • 0x14a0 updates the free-memory status byte [0x0011].
  • Refined room/state switch helper 0x1792, reached by actions 0x12 (switch_room_like) and 0x13 (switch_room_like_var):
    • It stops active sound, restores heap/update-list state through 0x1485, calls cleanup helpers 0x4482, 0x707c, and 0x706d, and resets every object record’s active/resource/frame state.
    • It sets [0x0139] = 1, stores 0x24 in [0x012d], saves old byte variable 0 in byte variable 1, writes the destination logic number to byte variable 0, clears bytes [0x000d] and [0x000e], and stores object 0’s view/resource byte in [0x0019].
    • It loads the destination logic through 0x117d, optionally loads the logic named by [0x1d12], may reposition object 0 from boundary byte [0x000b], sets flag 5, and calls redraw/reinitialization helpers.

2026-07-01: input parsing action and WORDS.TOK dictionary format

Additional commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n '1,220p' AGENTS.md
  • rg -n "action_75|0x75|0x1958|0x18ac|0x0c7b|0x0c8f|0x0ca3|input_word_sequence|parsed input|word" docs/src tools/disassemble_logic.py
  • Focused ndisasm disassembly around main executable image offsets 0x1800, 0x0c00, 0x0e00, 0x1a30, 0x4d80, 0x4f50, and 0x5000, using the decrypted executable header skip and sed to limit output.
  • xxd -s 0x0940 -l 0x50 -g 1 SQ2/AGIDATA.OVL
  • xxd -s 0x0c60 -l 0x70 -g 1 SQ2/AGIDATA.OVL
  • ls -l SQ2
  • xxd -l 160 -g 1 SQ2/WORDS.TOK
  • xxd -s 0x40 -l 160 -g 1 SQ2/WORDS.TOK
  • xxd -s 0x1a50 -l 160 -g 1 SQ2/WORDS.TOK
  • Local Python sanity check of the inferred WORDS.TOK decoder, followed by the deterministic script tools/inspect_words.py.
  • python3 -B tools/inspect_words.py --limit 60
  • python3 -B tools/inspect_words.py --prefix look --limit 20
  • python3 -B tools/inspect_words.py --id 0x0001 --limit 20
  • python3 -B tools/inspect_words.py --prefix get --limit 20

Documented result:

  • Added tools/inspect_words.py, a deterministic local inspector for SQ2/WORDS.TOK based on the parser format inferred from the executable.
  • Named action opcode 0x75 (parse_string_slot) as parse_string_slot in tools/disassemble_logic.py.
  • Expanded docs/src/logic_bytecode.md with the producer side for condition opcode 0x0e.
  • Documented action 0x75 (parse_string_slot) at image offset 0x1958:
    • It clears flags 2 and 4.
    • It reads one immediate string-slot index.
    • If the index is below 12, it parses fixed string slot 0x020d + index * 0x28 through helper 0x18ac.
  • Documented parser helper 0x18ac:
    • It clears parsed-word ID table 0x0c7b and parsed-word pointer table 0x0c8f.
    • It normalizes the input string into buffer 0x0ca7 through helper 0x199d.
    • It fills 0x0c7b, 0x0c8f, word [0x0ca3], and byte variable [0x0012], then sets flag 2 when a parse result exists.
  • Documented normalization helper 0x199d:
    • Bytes at DS:0x0c67 are separators. SQ2 contains 20 2c 2e 3f 21 28 29 3b 3a 5b 5d 7b 7d 00.
    • Bytes at DS:0x0c75 are ignored punctuation. SQ2 contains 27 60 2d 22 00.
    • It collapses separator runs to single spaces, removes ignored punctuation, trims a trailing space, and zero-terminates the normalized buffer.
  • Documented dictionary lookup helper 0x1a6b and the WORDS.TOK format:
    • Startup loads WORDS.TOK into memory and stores the base pointer at [0x0ca5].
    • The file begins with 26 big-endian offsets for lowercase initial letters. The local SQ2 file has a zero offset for x.
    • Entries are prefix-compressed as u8 prefix_len, encoded suffix bytes with the final byte marked by bit 7, and a big-endian 16-bit word ID.
    • Decoding each suffix byte with (byte & 0x7f) ^ 0x7f yields the lowercase character.
    • Local inspection found 1,099 entries. Sample decoded IDs include look -> 0x0002, get -> 0x0005, and anyword -> 0x0001.
  • Refined parsed-input behavior:
    • Recognized nonzero dictionary IDs are appended to 0x0c7b.
    • ID zero words are ignored, including the special single-letter a and i paths in helper 0x1a6b.
    • An unrecognized token stores its pointer in 0x0c8f, records its one-based position in [0x0012] and [0x0ca3], and stops parsing.
    • Condition 0x0e (input_word_sequence) consumes the parsed IDs from 0x0c7b and uses dictionary ID 0x0001 as a wildcard word.

2026-07-01: raw input event queue and condition 0x0d

Additional commands run from /Users/peter/ai/agi/reverse:

  • rg -n "0x0d|input_or_event_check|0x09be|0x459e|0x001c|0x45d7|0x382e|0x37f7|event|input" docs/src tools
  • Focused ndisasm disassembly around main executable image offsets 0x09a0, 0x43f0, 0x4500, 0x4660, 0x5a40, 0x7f70, and 0x3200, using the decrypted executable header skip and sed to limit output.
  • Full-executable ndisasm call-site search for calls to 0x44a9, 0x44f9, 0x459e, 0x4529, 0x4566, 0x45d7, 0x45f0, 0x4618, 0x467f, and 0x466f.
  • A focused ndisasm read around 0x6100 was rejected because it used the wrong file skip. Follow-up reads around image offsets 0x5f80, 0x8e80, and 0x93d0 used the corrected image_offset + 0x200 skip.
  • xxd -s 0x16b0 -l 0x50 -g 2 SQ2/AGIDATA.OVL
  • xxd -s 0x16d0 -l 0x50 -g 2 SQ2/AGIDATA.OVL
  • xxd -s 0x11ba -l 0x70 -g 2 SQ2/AGIDATA.OVL
  • Focused ndisasm disassembly around image offset 0x0c44, showing action handler 0x73. A follow-up read around 0x0e7e used the wrong skip and was rejected; no conclusions from that shifted read were documented.
  • Corrected focused ndisasm disassembly around image offset 0x0e7e.
  • python3 -B tools/disassemble_logic.py --limit 142 | rg -n "\b8f\b|action_8f|logic="
  • python3 -B tools/disassemble_logic.py --limit 142 | sed -n '/action_8f/,+4p'

Documented result:

  • Renamed condition opcode 0x0d (raw_key_event_available) in tools/disassemble_logic.py to raw_key_event_available.
  • Renamed action opcode 0x79 (map_key_event) in tools/disassemble_logic.py to map_key_event.
  • Expanded docs/src/logic_bytecode.md with the raw event queue:
    • Event records are 4 bytes: type word at +0, value word at +2.
    • Queue storage is 0x11ba..0x1209.
    • Word [0x120a] is the write pointer, and word [0x120c] is the read pointer.
    • Helper 0x44a9(type, value) enqueues one record unless the queue is full.
    • Helper 0x44f9() dequeues one record or returns zero when empty.
  • Documented condition handler 0x09be:
    • It first checks byte [0x001c].
    • If empty, it calls helper 0x459e.
    • Helper 0x459e dequeues events, normalizes some key values through 0x4634, returns the event value for type-1 records, returns zero for no event, and returns 0xffff for non-type-1 records.
    • Handler 0x09be loops past 0xffff, stores a nonzero low byte in [0x001c], and returns true.
  • Documented keyboard helper 0x5a89 as the BIOS int 16h polling path:
    • It returns zero when no key is waiting.
    • It returns the low ASCII byte when the key has nonzero ASCII.
    • It preserves the BIOS scan-code word when ASCII is zero.
  • Documented helper 0x467f as the BIOS-key drain into the event queue:
    • Key words found in table DS:0x16b3 are enqueued as type 2 with a mapped direction-like value.
    • Other key words are enqueued as type 1 with the raw value.
    • The local 0x16b3 table maps key words 0x4800, 0x4900, 0x4d00, 0x5100, 0x5000, 0x4f00, 0x4b00, and 0x4700 to values 1..8.
  • Documented helper 0x4566(event_record):
    • For type-1 events, it scans script-populated four-byte slots rooted at 0x0145.
    • On a match between event value and slot word +0, it changes the event type to 3 and replaces the event value with slot word +2.
    • Action 0x79 (map_key_event) appends those mapping slots.
  • Documented display-mode-specific helper 0x46e8(event_record):
    • When [0x112e] == 2, it scans table DS:0x16d7.
    • Matching type-1 values are changed to type 2 with mapped values.
    • The local table maps ASCII digit key words 8,9,6,3,2,1,4,7 to values 1..8.
  • Named action opcode 0x73 (prompt_string_to_slot) as prompt_string_to_slot:
    • It reads fixed string slot arg0, message number arg1, placement-like bytes arg2 and arg3, and max-length byte arg4.
    • It clears the destination slot 0x020d + arg0 * 0x28.
    • It displays the resolved current-logic message, optionally after calling 0x2b0d(arg2, arg3) when arg2 < 0x19.
    • It accepts edited text through helper 0x0da9, using min(arg4 + 1, 0x28) as the accepted length.
  • Recorded a tentative observation for action opcode 0x8f (action_8f) without assigning a name:
    • Handler 0x0e7e reads one message-number operand.
    • It resolves that current-logic message through 0x21f0.
    • It calls 0x4de8(destination=0x0002, source=message, count=7).
    • It then calls helper 0x5b49.
    • The one local occurrence is in logic 140 before action 0x6f (set_input_line_config) and string setup actions; the role remains open.

Follow-up on action 0x6f (set_input_line_config), action 0x8f (action_8f), and DOS path helpers

Additional commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n '1,220p' AGENTS.md
  • rg -n "0x6f|0x8f|action_6f|action_8f|Text-window|input-line|DOS file|file helper" docs/src tools/disassemble_logic.py
  • Corrected focused ndisasm disassemblies around image offsets 0x0e7e, 0x78f0, and 0x5b49, using the decrypted executable header skip and sed to limit the displayed output.
  • xxd -s 0x5d6c -l 0x10 -g 1 build/cleanroom/AGI.decrypted.exe
  • xxd -s 0x1320 -l 0x60 -g 1 SQ2/AGIDATA.OVL
  • python3 -B tools/disassemble_logic.py --limit 141 | sed -n '/action_8f/,+8p'
  • rg -n "0x5dd|0x05dd|0x05d5|0x05db|0x1379|0x5df|0x05df" docs/src

Documented result:

  • Named action opcode 0x6f (set_input_line_config) in tools/disassemble_logic.py as set_input_line_config.
  • Documented handler 0x78f0:
    • It stores arg0 in [0x05dd], arg0 + 0x15 in [0x05df], arg1 in [0x05d5], and arg2 in [0x05db].
    • It computes [0x1379] from arg0, normally as arg0 << 3.
    • In display mode [0x1130] == 2, [0x1379] is arg0 * 6 for arg0 <= 1 and is clamped to 6 for larger values.
    • Nearby redraw helpers use these globals for input-line/status text areas, so the final user-facing meaning remains provisional.
  • Refined the action 0x8f (action_8f) observation:
    • Handler 0x0e7e copies the resolved message into absolute buffer 0x0002 and calls 0x5b49.
    • Helper 0x5b49 compares bytes at 0x0002 against the embedded SQ2\0 string at image offset 0x5b6c.
    • On the first mismatch it calls helper 0x02ae, already observed in restart/exit-like paths.
    • This looks like a game-signature/configuration guard, but the exact runtime role remains open until dynamically traced.
  • Expanded docs/src/agi_executable.md with the DOS file wrapper cluster from image offsets 0x5cad..0x5e73, the shared pre-call helper 0x5e8d, and the savegame/path helpers around 0x5b73 and 0x5bdd.

Follow-up on relative object positioning and state-file actions

Additional commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n '1,220p' AGENTS.md
  • sed -n '1,260p' docs/src/graphics_object_pipeline.md
  • sed -n '500,580p' docs/src/logic_bytecode.md
  • python3 -B tools/disassemble_logic.py --stats | rg " action_|^28 |^7c |^7d |^7e |^80 |^87 |^88 |^89 |^8a |^8b |^8c |^8d |^96 |^a9 |^9a |^6c "
  • Correct focused ndisasm disassemblies around image offsets 0x7ce7, 0x3726, 0x3753, 0x38b4, 0x2472, 0x2512, 0x2753, 0x28c6, 0x26b0, 0x31d8, and 0x1f2b, using the decrypted executable header skip of image_offset + 0x200.
  • Two preliminary ndisasm probes around image offsets 0x3726 and 0x0257 were rejected because the file skip was wrong; no conclusions from those shifted outputs were used.
  • python3 -B tools/disassemble_logic.py --limit 141 | rg -n "action (28|7c|7d|7e|80|87|88|89|8a|8b|8c|8d|96|a9|9a|6c)" -C 5
  • xxd -s 0x0d20 -l 0xe0 -g 1 SQ2/AGIDATA.OVL
  • xxd -s 0x1c60 -l 0x50 -g 1 SQ2/AGIDATA.OVL
  • xxd -s 0x0a90 -l 0x70 -g 1 SQ2/AGIDATA.OVL
  • sed -n '170,230p' tools/disassemble_logic.py
  • sed -n '440,545p' docs/src/logic_bytecode.md
  • sed -n '604,650p' docs/src/logic_bytecode.md
  • sed -n '45,125p' docs/src/agi_executable.md
  • sed -n '200,235p' docs/src/graphics_object_pipeline.md
  • One rg probe for a markdown backtick pattern in docs/src/graphics_object_pipeline.md failed due shell quoting; it produced no evidence and was replaced by the sed read above.

Documented result:

  • Named action opcode 0x28 (add_object_pos_from_vars) as add_object_pos_from_vars.
    • Handler 0x7ce7 reads object index arg0.
    • It reads signed deltas from byte variables named by arg1 and arg2.
    • It adds them to object fields [+0x03] and [+0x05], clamping underflow at zero.
    • It sets object flag bit 0x0400 and calls placement helper 0x593a.
  • Named action opcode 0x6c (set_input_prompt_char) as set_input_prompt_char.
    • Handler 0x38b4 resolves message arg0 and stores its first byte in [0x05d7].
    • Input redraw helpers 0x37f7, 0x382e, and 0x38d7 test [0x05d7] while drawing or erasing the prompt/input marker.
  • Named action opcodes 0x7d (save_game_state) and 0x7e (restore_game_state) as save_game_state and restore_game_state.
    • Save handler 0x2753 creates file 0x1c8c, writes a 31-byte description/header from 0x1c6c, then writes several length-prefixed blocks through helper 0x28c6.
    • Restore handler 0x2512 opens file 0x1c8c, seeks to offset 0x1f, then reads matching length-prefixed blocks through helper 0x26b0.
    • Local strings around 0x0d34, 0x0d73, 0x0d87, 0x0db6, and 0x0e46 identify the restore/save confirmation and error paths.

Runtime model synthesis and string-table action follow-up

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,220p' docs/src/logic_bytecode.md
  • sed -n '1,260p' docs/src/graphics_object_pipeline.md
  • sed -n '1,260p' docs/src/logic_resources.md
  • ndisasm -b 16 -o 0x7350 -e 0x7550 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x0c30 -e 0x0e30 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x1940 -e 0x1b40 build/cleanroom/AGI.decrypted.exe
  • Local Python dump of AGIDATA.OVL action-table entries for opcodes 0x70..0x78.
  • xxd -g 1 -s 0xc8f -l 192 SQ2/AGIDATA.OVL
  • Local Python dump of words at AGIDATA.OVL offset 0x0c8f.

Documented result:

  • Added docs/src/runtime_model.md and linked it from the mdBook summary and overview. This page groups the lower-level handler notes into implementation-facing runtime types:
    • byte variables rooted at DS:0x0009;
    • packed flags rooted at DS:0x0109;
    • fixed string slots rooted at DS:0x020d;
    • parsed-word buffers consumed by condition 0x0e (input_word_sequence);
    • 10-byte logic cache/activation records linked from [0x0977];
    • resource cache handles for logic, view-like, picture-like, and sound-like payloads;
    • 43-byte object records and their operation families;
    • the graphics/update pipeline phases needed by a replacement implementation.
  • Decoded action opcode 0x74 (set_string_slot_from_table) from handler 0x0d70:
    • It computes destination string slot 0x020d + arg0 * 0x28.
    • It reads a word pointer from DS:0x0c8f + arg1 * 2.
    • It copies up to 0x28 bytes from that pointer into the slot through helper 0x4de8.
    • The sampled static SQ2 AGIDATA.OVL table at 0x0c8f is zero-filled, and this opcode was not encountered in the current local SQ2 logic scan, so the label remains provisional.
  • Added the 0x74 label to tools/disassemble_logic.py and documented the handler in docs/src/logic_bytecode.md.

Inventory selector, restart prompt, and text-window cleanup actions

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • python3 -B tools/disassemble_logic.py --stats
  • sed -n '1,220p' docs/src/logic_bytecode.md
  • sed -n '1,220p' docs/src/runtime_model.md
  • rg -n "action (1d|7c|80|87|88|89|8a|8b|8c|8d|96|9a|a9)" -C 4 over python3 -B tools/disassemble_logic.py --limit 141
  • Local Python dump of action-table entries for opcodes 0x1d, 0x7c, 0x80, 0x87..0x8d, 0x96, 0x9a, and 0xa9.
  • Initial orientation ndisasm probes around image offsets 0x1f00, 0x7300, 0x7700, and 0x8d00; these were used only to find nearby functions. Final conclusions below were rechecked with the correct executable header skip.
  • Correct focused ndisasm disassemblies using -e image_offset + 0x200 around image offsets 0x1f2b, 0x2472, 0x2b78, 0x31d8, 0x33bf, 0x5546, 0x731b, 0x7753, and 0x8d3d.
  • Local Python string dump of SQ2/AGIDATA.OVL offsets 0x0aab, 0x0adb, 0x0f1e, 0x0f26, 0x0f38, 0x0f5d, and menu diagnostic strings around 0x1ccc..0x1d04.
  • rg -n "0x7c|0x80|0x8d|0x96|0x9a|0xa9|0x1d|draw_box|window|text_attr|1755|1d12|1d0a" docs/src tools/disassemble_logic.py
  • sed -n '160,280p' tools/disassemble_logic.py
  • sed -n '520,600p' docs/src/logic_bytecode.md
  • sed -n '700,940p' docs/src/logic_bytecode.md
  • sed -n '100,190p' docs/src/runtime_model.md
  • sed -n '1300,1465p' docs/src/clean_room_executable_notes.md

Documented result:

  • Named action opcode 0x7c (show_inventory_selection).
    • Handler 0x31d8 clears the input prompt, saves/restores text attributes, enables the alternate text-attribute mode, and calls helper 0x3203.
    • Helper 0x3203 scans 3-byte entries from [0x0971] to [0x0973], keeping only entries whose byte [entry+0x02] == 0xff.
    • Each kept entry becomes an 8-byte temporary row containing the original entry index, a name pointer computed as [0x0971] + word[entry+0x00], and row/column display coordinates.
    • The strings at 0x0f26, 0x0f1e, 0x0f38, and 0x0f5d identify the UI as the carried-object list, with an interactive selection mode when flag 13 is set.
    • Enter stores the selected entry index in byte variable [0x22]; Escape stores 0xff.
  • Named action opcode 0x80 (confirm_restart_game).
    • Handler 0x2472 stops sound, clears input, and uses flag 16 to decide whether to skip a confirmation dialog.
    • The confirmation string at 0x0adb asks whether to restart the game.
    • On confirmation it resets heap/update state, sets flag 6, preserves flag 9, clears words [0x0129] and [0x012b], optionally reloads logic [0x1d12], calls menu/list refresh helper 0x930e, redraws the prompt, and returns zero to the dispatcher.
  • Named action opcode 0x9a (clear_text_rect_bounds).
    • Handler 0x7753 reads five immediates and calls helper 0x2bc4.
    • Helper 0x2bc4 is the full-bounds form of the text rectangle clear helper: it saves the cursor, passes top/left/bottom/right and attribute to BIOS int 10h scroll/clear-window service AH=0x06, then restores the cursor.
    • Existing action 0x69 is a narrower wrapper that clears full-width rows through helper 0x2b78, which in turn calls 0x2bc4.
  • Named action opcode 0xa9 (close_text_window_state).
    • Handler 0x1f2b tests word [0x0d1d]; if nonzero, it restores a saved display rectangle by calling helper 0x560c([0x0d23], [0x0d25]).
    • It then clears words [0x0d0f] and [0x0d1d].
    • The same routine is used both as an action handler and as an internal cleanup helper in picture/message/save paths.
  • Named action opcode 0x8d (show_interpreter_version).
    • Handler 0x733c displays the static string at 0x0aab, which identifies the interpreter and version in this executable.
  • Decoded action opcode 0x96 without assigning a stable user-level label yet.
    • Handler 0x8d3d reads three immediates, storing them in words [0x1d12], [0x1d08], and [0x1d0a].
    • The third value is clamped upward to at least 2.
    • The first value [0x1d12] is later used by the restart and room-switch paths as an optional logic resource to load. The other two globals feed the menu/list rendering cluster around 0x8e0b, so this remains a configured UI/session state action until that cluster is fully decoded.
  • Reconfirmed action opcode 0x1d as unresolved.
    • Handler 0x731b sets word [0x1755] = 1, calls full refresh helper 0x5546, waits for an event through 0x4618, refreshes again, then clears [0x1755].
    • Helper 0x5546 has a special branch when bit 0 of [0x1755] is set that rotates every byte of the logical graphics buffer before copying it to the display path. The visual/user-level purpose still needs a dynamic trace or screenshot before naming.

Follow-up on diagnostic, pause, input-line, display-toggle, and joystick actions

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py --stats
  • ndisasm -b 16 -o 0x0250 -e 0x0450 build/cleanroom/AGI.decrypted.exe as an over-broad first probe for the low-offset handler cluster. The useful bytes were later narrowed by direct handler inspection; the extra trailing output was ignored.
  • ndisasm -b 16 -o 0x14a0 -e 0x16a0 build/cleanroom/AGI.decrypted.exe as an over-broad first probe around the diagnostic handler; conclusions were taken only from the aligned handler at 0x14bd.
  • ndisasm -b 16 -o 0x3700 -e 0x3900 build/cleanroom/AGI.decrypted.exe as an over-broad first probe around input-line refresh helpers; conclusions were taken from aligned handlers 0x3726 and 0x3753.
  • Local Python string dump of SQ2/AGIDATA.OVL offsets 0x0a19, 0x0c0d, 0x0fce, and 0x1e2e.
  • ndisasm -b 16 -o 0x794c -e 0x7b4c build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'
  • ndisasm -b 16 -o 0x613c -e 0x633c build/cleanroom/AGI.decrypted.exe | sed -n '1,140p'
  • python3 -B tools/disassemble_logic.py --limit 141 | rg -n "action (87|88|89|8a|8b|8c|1d|96)" -C 5
  • Local Python hex/text dump of SQ2/AGIDATA.OVL offsets 0x1549, 0x15c1, 0x15c3, 0x1531, and 0x153d.
  • rg -n "0x87|0x88|0x89|0x8a|0x8b|0x8c|Miscellaneous|Interpreter/session|Text-window" docs/src/logic_bytecode.md
  • wc -l docs/src/logic_bytecode.md docs/src/clean_room_executable_notes.md docs/src/runtime_model.md
  • sed -n '780,900p' docs/src/logic_bytecode.md
  • sed -n '220,260p' tools/disassemble_logic.py

Documented result:

  • Named action opcode 0x87 (show_heap_status).
    • Handler 0x14bd formats a 100-byte stack message with helper 0x2374 and displays it through 0x1ce8.
    • The format string at 0x0a19 reads heapsize: %u, now: %u max: %u, rm.0, etc.: %u, and max script: %d.
    • The numeric values are computed from heap/script globals [0x0a55], [0x0a57], [0x0a59], [0x0a5b], [0x0a5f], and [0x170f].
  • Named action opcode 0x88 (pause_game_message).
    • Handler 0x0257 sets [0x0615] = 1, calls helper 0x4482, stops sound, displays the fixed pause string at 0x0c0d, then clears [0x0615].
  • Named action opcode 0x89 (refresh_input_line).
    • Handler 0x3753 runs only when input-line enabled word [0x05d3] is nonzero.
    • In display mode [0x1130] == 2 with [0x0d0f] == 0, it displays the string at 0x1e2e (ENTER COMMAND) through the alternate display helpers and sends the current input character byte [0x001c] through helper 0x3652.
    • In the other path, helper 0x37a5 appends bytes from the buffer/string at 0x0fce into visible input buffer 0x0fa4 until [0x0ff8] reaches that source string length.
  • Named action opcode 0x8a (erase_input_line).
    • Handler 0x3726 repeatedly calls helper 0x3652(0x08) while input length word [0x0ff8] remains nonzero, except that display mode 2 with [0x0d0f] == 0 skips the erase loop.
  • Named action opcode 0x8b (calibrate_joystick).
    • Handler 0x613c initializes joystick/calibration globals, displays the string at 0x1549 (Please center your joystick...) when joystick state is available, waits for Enter or Escape, then computes centered bounds around [0x15c1] and [0x15c3] into [0x15c9], [0x15cd], [0x15cb], and [0x15cf].
    • It then loops helper 0x6425 while calibration records at 0x1531 or 0x153d are active, and finishes with helper 0x4482.
  • Named action opcode 0x8c (toggle_display_mode_bit).
    • Handler 0x794c requires [0x112e] == 0, byte variable 0 nonzero, and display mode word [0x1130] not equal to 2 or 3.
    • It calls 0x1364, toggles bit 0 of [0x1130], and rebuilds display state through helpers 0x2b28, 0x5528, 0x2b4f, and 0x681c.
  • Added implementation-facing notes to docs/src/runtime_model.md grouping these as UI, diagnostics, and device-state services around the VM.

Priority-screen action and trace-window configuration

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py --stats
  • python3 -B tools/disassemble_logic.py --limit 141 | rg -n "action (1d|87|88|89|8a|8b|8c|96|8f)" -C 6
  • ndisasm -b 16 -o 0x14a0 -e 0x16a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,130p'
  • ndisasm -b 16 -o 0x0250 -e 0x0450 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x36f0 -e 0x38f0 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'
  • ndisasm -b 16 -o 0x6100 -e 0x6300 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x7930 -e 0x7b30 build/cleanroom/AGI.decrypted.exe | sed -n '1,130p'
  • xxd -g 1 -s 0x0a10 -l 0x80 SQ2/AGIDATA.OVL
  • xxd -g 1 -s 0x0c00 -l 0x40 SQ2/AGIDATA.OVL
  • xxd -g 1 -s 0x0fbc -l 0x30 SQ2/AGIDATA.OVL
  • An attempted python3 -B tools/inspect_words.py 40 62 63 44 55 102 89 36 146 command failed because inspect_words.py accepts --id, not positional ids. It produced no evidence and was replaced by the local Python import below.
  • Local Python use of tools.inspect_words.decode_entries over SQ2/WORDS.TOK for word ids 0x0024, 0x0028, 0x002c, 0x0037, 0x003e, 0x003f, 0x0059, 0x0066, and 0x0092.
  • Full static ndisasm with rg for references to globals [0x1d08], [0x1d0a], [0x1d10], [0x1d12], [0x1d14], [0x1d16], [0x1d18], [0x1d1a], [0x1d1c], and [0x1d1e].
  • ndisasm -b 16 -o 0x8c60 -e 0x8e60 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'
  • ndisasm -b 16 -o 0x8e0b -e 0x900b build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x900b -e 0x920b build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • Local Python action-table dump for opcodes 0x90..0x96.
  • ndisasm -b 16 -o 0x02c0 -e 0x04c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'
  • python3 -B tools/disassemble_logic.py 140 | sed -n '1,80p'
  • An attempted rg command containing markdown backticks in the search pattern was misparsed by the shell and produced no evidence; the useful searches above were run with simpler patterns.

Documented result:

  • Named action opcode 0x1d (show_priority_screen).
    • Handler 0x731b sets [0x1755] = 1, calls full-screen refresh helper 0x5546, waits for an event through 0x4618, calls 0x5546 again, then clears [0x1755].
    • Helper 0x5546 swaps the high and low nibbles of every logical graphics-buffer byte while [0x1755] & 1 is set.
    • The only observed local phrase reaching this action is show pri; WORDS.TOK maps word id 0x0028 to “show” and word id 0x003f to “pri”.
    • The replacement-level behavior is therefore a temporary priority/control inspection display that returns to the normal display after input.
  • Named action opcode 0x95 (enable_action_trace_window) even though no local SQ2 logic path currently reaches it.
    • Handler 0x8c91 returns SI + 1 when word [0x1d10] is already nonzero.
    • Otherwise it calls helper 0x8cae, which starts a trace display only if flag 10 is set.
    • Helper 0x8cae sets [0x1d10] = 1, computes box bounds from input-line row [0x05dd], trace row offset [0x1d08], and trace height [0x1d0a], stores derived values in [0x1d14], [0x1d16], [0x1d18], [0x1d1a], [0x1d1c], and [0x1d1e], then draws the trace box with 0x5590.
  • Named action opcode 0x96 (configure_action_trace_window).
    • Handler 0x8d3d stores its three immediates in [0x1d12], [0x1d08], and [0x1d0a], clamping [0x1d0a] upward to at least 2.
    • The dispatcher at 0x02c3 tests [0x1d10] == 1 before each action dispatch and calls formatter helper 0x8da3.
    • Formatter helper 0x8e0b uses optional logic resource [0x1d12] for trace text, draws opcode/operand values into the trace box, and waits for input while trace mode is active.
    • Restart and room-switch paths also reload logic [0x1d12] when nonzero, so a new implementation should treat it as part of VM trace/session configuration, not as ordinary game-state logic.

Game-signature guard action

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py --stats
  • python3 -B tools/disassemble_logic.py 140 | rg -n "action 8f|message_count|logic 140|set_input_line_config|set_string_slot_from_message" -C 4
  • Two initial ndisasm probes around image offsets 0x0e70 and 0x5b40 accidentally used the image offset as the file skip. Those shifted outputs were rejected and produced no conclusions.
  • Corrected focused disassemblies with the executable header skip included:
    • ndisasm -b 16 -o 0x0e70 -e 0x1070 build/cleanroom/AGI.decrypted.exe | sed -n '1,140p'
    • ndisasm -b 16 -o 0x5b40 -e 0x5d40 build/cleanroom/AGI.decrypted.exe | sed -n '1,110p'
  • Local Python read of logic 140’s payload/message table for orientation. That raw dump did not decode the encrypted/compressed message text, but it did confirm the local bytecode context around the single static 0x8f use.

Documented result:

  • Named action opcode 0x8f (verify_game_signature).
    • Handler 0x0e7e reads one immediate message number.
    • It resolves that message through 0x21f0, pushes maximum length 7, and copies the string to absolute buffer 0x0002 through helper 0x4de8.
    • It then calls helper 0x5b49.
    • Helper 0x5b49 compares bytes at 0x0002 against an embedded SQ2\0 string at image offset 0x5b6c.
    • On the first mismatch it calls helper 0x02ae, the same helper seen in restart/exit-like paths.
    • The only observed local static use is in logic 140 immediately before 0x6f (set_input_line_config), consistent with a game-signature or game-configuration guard.

Normalized string-slot equality condition

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py --stats
  • sed -n '120,230p' docs/src/logic_bytecode.md
  • rg -n "helper_0eac|0x0f|0eac|09db|condition" docs/src tools
  • ndisasm -b 16 -o 0x09c0 -e 0x0bc0 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x0e80 -e 0x1080 build/cleanroom/AGI.decrypted.exe
  • xxd -g 1 -s 0x0b40 -l 0x40 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x4f90 -e 0x5190 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x18a0 -e 0x1aa0 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • sed -n '80,230p' tools/disassemble_logic.py
  • rg -n "0x094b|0x0c67|0xc67|0x0c75|0xc75|delimiter|punct|normalize|string slot|0x020d|0x20d" docs/src tools
  • xxd -g 1 -s 0x0e60 -l 0xa0 build/cleanroom/AGI.decrypted.exe
  • xxd -g 1 -s 0x0f40 -l 0x80 build/cleanroom/AGI.decrypted.exe
  • xxd -g 1 -s 0x1140 -l 0x100 build/cleanroom/AGI.decrypted.exe
  • sed -n '280,325p' docs/src/logic_bytecode.md
  • sed -n '1150,1185p' docs/src/clean_room_executable_notes.md
  • sed -n '30,65p' docs/src/runtime_model.md
  • rg -n "094b|0x94b|0x094b" -S .
  • Local byte-pattern probes over build/cleanroom/AGI.decrypted.exe, SQ2/AGIDATA.OVL, and related files to map known delimiter tables back to their storage file.
  • strings -a -t x build/cleanroom/AGI.decrypted.exe | rg "ENTER COMMAND|You are carrying|nothing|AGI|COMMAND|carrying|Press ENTER|Press ESC"
  • xxd -g 1 -s 0x0940 -l 0x80 SQ2/AGIDATA.OVL
  • xxd -g 1 -s 0x0c60 -l 0x30 SQ2/AGIDATA.OVL
  • Local byte reads of zero-terminated data at SQ2/AGIDATA.OVL offsets 0x094b, 0x0c67, 0x0c75, and 0x020d.
  • sed -n '140,225p' docs/src/logic_bytecode.md

Documented result:

  • Renamed condition opcode 0x0f from provisional helper_0eac to string_slots_equal_normalized.
  • The condition table entry dispatches to handler 0x09db, has two fixed operands, and has metadata byte 0x00.
  • Handler 0x09db reads two immediate byte operands, pushes them, and calls helper 0x0eac.
  • Helper 0x0eac allocates two local buffers, calls helper 0x0ef8 for each operand, and then compares the normalized buffers byte-for-byte through their zero terminators.
  • Helper 0x0ef8 computes a source string slot as 0x020d + slot * 0x28, walks it until a zero byte, skips bytes found in the zero-terminated table at DS:0x094b, lowercases ASCII uppercase bytes through helper 0x4fea, writes kept bytes to the destination buffer, and appends a zero terminator.
  • DS:0x094b is data in SQ2/AGIDATA.OVL, not the same offset in the EXE body. The local SQ2 table contains 20 09 2e 2c 3b 3a 27 21 2d 00, meaning space, tab, ., ,, ;, :, ', !, and - are ignored for this comparison.
  • Updated docs/src/runtime_model.md to separate this direct normalized string comparison from the dictionary-backed parsed-word condition 0x0e.

Object diagnostic action and field-name confirmation

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py --stats
  • rg -n "provisional|unknown|needs|still|action_00|object_motion_or_state|refresh_object_helper|picture|draw|0x5546|0x5762|0x593a|0x57cf|0x3ae7|0x39f7|0x4a3b|0x4acf" docs/src tools
  • python3 -B tools/disassemble_logic.py --limit 142 | rg -n "action 85|display_object_state_summary_var|logic [0-9]+|message_count" -C 8
  • ndisasm -b 16 -o 0x7280 -e 0x7480 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x1c00 -e 0x1e00 build/cleanroom/AGI.decrypted.exe | sed -n '1,200p'
  • rg -n "0x85|display_object_state_summary_var|72b5|0x72b5|object state|summary" docs/src tools
  • xxd -g 1 -s 0x1700 -l 0x80 SQ2/AGIDATA.OVL
  • python3 -B tools/inspect_words.py --id 0x0031 --limit 20
  • python3 -B tools/inspect_words.py --id 0x0017 --limit 20
  • python3 -B tools/inspect_words.py --id 0x001a --limit 20
  • python3 -B tools/inspect_words.py --id 0x002c --limit 20
  • sed -n '1,120p' tools/disassemble_logic.py
  • rg -n "message|messages|decode|crypt|logic_payload|message_count|21f0" tools docs/src/logic_resources.md docs/src/clean_room_executable_notes.md
  • python3 -B tools/disassemble_logic.py 99 | sed -n '1,140p'
  • sed -n '92,118p' docs/src/logic_resources.md
  • sed -n '255,295p' docs/src/clean_room_executable_notes.md
  • Local Python decoding of logic 99 messages using the previously documented logic-message format and XOR key at SQ2/AGIDATA.OVL:0x08f1.
  • sed -n '180,205p' docs/src/graphics_object_pipeline.md
  • sed -n '704,722p' docs/src/logic_bytecode.md
  • sed -n '632,646p' docs/src/clean_room_executable_notes.md
  • An attempted rg command containing a markdown backtick in the search pattern was misparsed by the shell and produced no evidence.
  • rg -n "display_object_state_summary_var|display_object_diagnostics_var|0x85|object #:|Object %d|stepsize" docs/src tools
  • sed -n '130,165p' docs/src/runtime_model.md

Documented result:

  • Renamed action opcode 0x85 from display_object_state_summary_var to display_object_diagnostics_var.
  • Handler 0x72b5 reads one operand as a variable slot, then reads the object index from var[arg0].
  • It multiplies the object index by 0x2b, adds the object array base [0x096b], and formats fields from that object:
    • object index from the variable value;
    • [object+0x03] as x;
    • [object+0x05] as y;
    • [object+0x1a] as xsize;
    • [object+0x1c] as ysize;
    • [object+0x24] as pri;
    • [object+0x1e] as stepsize.
  • The format string at DS:0x1713 in SQ2/AGIDATA.OVL reads:
Object %d:
x: %d  xsize: %d
y: %d  ysize: %d
pri: %d
stepsize: %d
  • The only local SQ2 use is in logic 99. The script accepts WORDS.TOK id 0x0031 (object) or 0x0017 (sp), prompts with decoded message 7 (object #:), stores the number in variable 64, and then calls action 0x85.
  • Logic 99 is a diagnostic command hub: nearby decoded messages include new room:, x:, y:, object number:, var number:, var value:, flag number:, and flag status messages.
  • Updated the object runtime model and graphics/object pipeline notes to use this diagnostic template as additional evidence for the field meanings.

Targeted object movement actions

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x6cc0 -e 0x6ec0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x1620 -e 0x1820 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • rg -n "object_motion_or_state|0x51|0x52|0x1672|\\+0x27|\\+0x28|\\+0x29|\\+0x2a|boundary completion|motion/control" docs/src tools
  • python3 -B tools/disassemble_logic.py --stats | sed -n '/actions/,$p' | sed -n '1,80p'
  • sed -n '430,455p' docs/src/graphics_object_pipeline.md
  • sed -n '780,812p' docs/src/clean_room_executable_notes.md
  • sed -n '136,160p' docs/src/runtime_model.md
  • xxd -g 2 -s 0x0a80 -l 0x40 SQ2/AGIDATA.OVL
  • xxd -g 1 -s 0x0a80 -l 0x28 SQ2/AGIDATA.OVL
  • Local Python read of the nine little-endian direction table words at SQ2/AGIDATA.OVL:0x0a85.
  • python3 -B tools/disassemble_logic.py 1 2 3 4 5 6 7 8 9 10 | rg -n "action (51|52)" -C 3
  • python3 -B tools/disassemble_logic.py --limit 141 | rg -n "action (51|52)" -C 2 | sed -n '1,160p'
  • sed -n '40,58p' docs/src/logic_bytecode.md
  • sed -n '470,488p' docs/src/logic_bytecode.md
  • sed -n '686,701p' docs/src/logic_bytecode.md
  • rg -n "object_motion_or_state|object_motion_or_state_var|motion_parameters|Motion control|Targeted-motion|targeted-motion|0x1672" docs/src tools/disassemble_logic.py
  • rg -n "object_motion_or_state|object_motion_or_state_var|move_object_to|0x0a85|target above|completion flag" docs/src tools/disassemble_logic.py
  • python3 -B tools/disassemble_logic.py 1 | rg -n "move_object_to|action 5[12]" -C 2

Documented result:

  • Renamed action opcode 0x51 from object_motion_or_state to move_object_to.
  • Renamed action opcode 0x52 from object_motion_or_state_var to move_object_to_var.
  • Handler 0x6ce4 (0x51) reads:
    • object index;
    • target X;
    • target Y;
    • optional step-size override, where zero means keep the current step size;
    • completion flag.
  • Handler 0x6d61 (0x52) has the same contract, except target X, target Y, and step-size override are read from variables.
  • Both handlers set object byte [+0x22] = 3, store target X/Y in [+0x27]/[+0x28], save old step size [+0x1e] into [+0x29], store the completion flag in [+0x2a], clear the completion flag, set object bit 0x0010, and call helper 0x1672.
  • Helper 0x1672 calls 0x16ed(current_x, current_y, target_x, target_y, step) and stores the returned direction-like byte in object byte [+0x21]. For object 0 it also mirrors that direction byte to global byte [0x000f].
  • Helper 0x16ed classifies the target X and Y relative to the current position and step size, then indexes the nine-word table at DS:0x0a85. The local SQ2 table is:
target above:  8 1 2
target level:  7 0 3
target below:  6 5 4
               left near right
  • The zero center entry means an object already at, or within one step of, the target completes immediately.
  • Completion helper 0x16b9 restores step byte [+0x1e] from [+0x29], sets flag [+0x2a], clears object byte [+0x22], and for object 0 sets [0x0139] = 1 and clears global direction byte [0x000f].
  • Updated the bytecode spec, object/graphics pipeline, and runtime model with the higher-level targeted-movement contract.

Autonomous object motion modes 1 and 2

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '680,735p' docs/src/logic_bytecode.md
  • sed -n '180,205p' docs/src/graphics_object_pipeline.md
  • sed -n '520,550p' docs/src/graphics_object_pipeline.md
  • sed -n '140,165p' docs/src/runtime_model.md
  • sed -n '1038,1064p' docs/src/clean_room_executable_notes.md
  • sed -n '160,190p' tools/disassemble_logic.py
  • ndisasm -b 16 -o 0x6df0 -e 0x6ec0 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x6df0 -e 0x6ff0 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x0b80 -e 0x0d80 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x3f30 -e 0x4130 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x0a80 -e 0x0c80 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x3f30 -e 0x4130 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x6df0 -e 0x6ff0 build/cleanroom/AGI.decrypted.exe
  • rg -n "0bb3|0x0bb3|3f5a|0x3f5a|16ed|0x16ed|\\+0x22 == 2|field_22_mode1|object_step_or_state_limited" docs/src tools
  • ndisasm -b 16 -o 0x1660 -e 0x1860 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x09f0 -e 0x0bf0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x0b30 -e 0x0d30 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x1680 -e 0x1880 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'
  • python3 -B tools/disassemble_logic.py --opcode 0x53
  • python3 -B tools/disassemble_logic.py --opcode 0x54
  • python3 -B tools/disassemble_logic.py --opcode 0x55
  • python3 -B tools/disassemble_logic.py --stats
  • tail -n 80 docs/src/clean_room_executable_notes.md
  • rg -n "object_step_or_state_limited|set_object_field_22_mode1|clear_object_field_22\\)|approach_first_object_until_near|start_random_motion|stop_motion_mode|min\\(arg1" docs/src tools/disassemble_logic.py

Rejected or non-evidence probes:

  • The first ndisasm command around 0x6df0 used -e 0x6ec0. For this decrypted executable image, the file skip must include the 0x200-byte MZ header, so the correct skip for image offset 0x6df0 is 0x6ff0. The shifted command was treated as rejected evidence.
  • The three python3 -B tools/disassemble_logic.py --opcode ... commands only produced argument-parser errors because the local disassembler has no --opcode option. They were not used as evidence.
  • The broad ndisasm commands without sed produced excessive trailing disassembly. Only the leading ranges later rechecked with focused sed commands were used as evidence.

Documented result:

  • Corrected action 0x53: handler 0x6e02 sets object byte [+0x22] = 2, reads operand 1, compares it with current step byte [+0x1e], and stores the larger value in [+0x27]. The earlier min(...) description was wrong.
  • Action 0x53 stores operand 2 as completion flag byte [+0x28], clears that flag through 0x74d0, initializes byte [+0x29] = 0xff, and sets object bit 0x0010.
  • Helper 0x0b36, reached from mode byte +0x22 == 2, computes the first object entry’s center X as first[+0x03] + first[+0x1a] / 2 and the current object’s center X as object[+0x03] + object[+0x1a] / 2.
  • The same helper calls 0x16ed(object_center_x, object_y, first_object_center_x, first_object_y, object[+0x27]). If the returned direction is zero, it clears object bytes [+0x21] and [+0x22] and sets completion flag [+0x28] through 0x74c6.
  • If mode 2 sees object bit 0x4000, it chooses a random nonzero direction through 0x3fa3, stores it in [+0x21], and computes a delay in [+0x29] from the object/first-object separation and current step byte. While [+0x29] is nonzero, the helper counts it down by step byte [+0x1e]; when the delay reaches zero it writes the direct approach direction to [+0x21].
  • Renamed opcode label 0x53 to approach_first_object_until_near.
  • Action 0x54 handler 0x6e68 sets [0x0139] = 0 when operating on the first object entry, then sets object byte [+0x22] = 1 and object bit 0x0010.
  • Helper 0x3f5a, reached from mode byte +0x22 == 1, decrements countdown byte [+0x27]. When the old countdown is zero, or object bit 0x4000 is set, it calls 0x3fa3, stores the random direction 0..8 in [+0x21], mirrors that direction to global byte [0x000f] for the first object, and reseeds [+0x27] by repeatedly taking random % 0x33 until the value is at least 6.
  • Renamed opcode label 0x54 to start_random_motion.
  • Action 0x55 handler 0x6ea1 only clears object byte [+0x22]. It does not clear direction byte [+0x21] or update the first-object globals.
  • Renamed opcode label 0x55 to stop_motion_mode.
  • Updated the bytecode spec, graphics/object pipeline, runtime model, and local disassembler labels with these higher-level motion-mode contracts.

Remaining action-table opcode pass

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' tools/disassemble_logic.py
  • sed -n '260,430p' tools/disassemble_logic.py
  • rg -n "action_[0-9a-f]{2}|condition_[0-9a-f]{2}|unknown|provisional|thin|remaining|TODO|needs" docs/src tools
  • python3 -B tools/disassemble_logic.py --stats
  • rg -n "061d|action table|condition table|dispatch table|TableEntry|load_table" docs/src tools/disassemble_logic.py
  • sed -n '1,80p' docs/src/logic_bytecode.md
  • sed -n '240,330p' docs/src/clean_room_executable_notes.md
  • Python one-off dump of all unnamed action-table entries at SQ2/AGIDATA.OVL:0x061d and condition-table bytes through opcode 0x25.
  • ndisasm -b 16 -o 0x4b00 -e 0x4d00 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x3ec0 -e 0x40c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x8270 -e 0x8470 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x4c00 -e 0x4e00 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x2700 -e 0x2900 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x7180 -e 0x7380 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x6020 -e 0x6220 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x5040 -e 0x5240 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • rg -n "0x0e72|0xe72|0x1530|1530|0x124a|124a|0x127a|127a|0x0143|0x143|0x05e1|0x5e1|0x1823|1823|0x1809|1809|0x1c8c|1c8c|0x5051|0x4b17|0x3ecd|0x828f|0x3ee9|0x4c15|0x2726|0x718b|0x719d|0x602f|0x4d10" docs/src tools
  • strings -a -t x build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • xxd -g 1 -s 0xe60 -l 0x90 SQ2/AGIDATA.OVL
  • xxd -g 1 -s 0x1800 -l 0x60 SQ2/AGIDATA.OVL
  • ndisasm -b 16 -o 0x02a0 -e 0x04a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • sed -n '90,140p' docs/src/agi_executable.md
  • sed -n '780,850p' docs/src/clean_room_executable_notes.md
  • sed -n '680,710p' docs/src/graphics_object_pipeline.md
  • sed -n '1360,1380p' docs/src/clean_room_executable_notes.md
  • sed -n '50,90p' docs/src/graphics_object_pipeline.md
  • sed -n '600,618p' docs/src/logic_bytecode.md
  • sed -n '440,470p' docs/src/logic_bytecode.md
  • sed -n '580,620p' docs/src/logic_bytecode.md
  • sed -n '138,170p' docs/src/logic_bytecode.md
  • sed -n '112,170p' docs/src/logic_bytecode.md
  • sed -n '600,630p' docs/src/logic_bytecode.md
  • sed -n '900,950p' docs/src/logic_bytecode.md
  • sed -n '960,1035p' docs/src/logic_bytecode.md
  • sed -n '930,990p' docs/src/logic_bytecode.md
  • sed -n '850,930p' docs/src/logic_bytecode.md
  • sed -n '45,75p' docs/src/graphics_object_pipeline.md
  • rg -n "action_[0-9a-f]{2}|object_step_or_state_limited|set_object_field_22_mode1|clear_object_field_22\\)|\\b0x1c\\b|\\b0x20\\b|\\b0x90\\b|\\b0x99\\b|\\b0x9b\\b|\\b0xaa\\b|\\b0xab\\b|\\b0xac\\b|\\b0xad\\b|\\b0xae\\b|\\b0xaf\\b" docs/src tools/disassemble_logic.py
  • Python one-off import of tools/disassemble_logic.py with sys.modules registration, followed by an unnamed-action audit.

Rejected or non-evidence probes:

  • The first Python one-off import of tools/disassemble_logic.py omitted sys.modules[spec.name] = module. Python 3.14’s dataclass implementation expected that registration and raised an AttributeError; this failed probe was not used as evidence.

Documented result:

  • Dumped the full action table at SQ2/AGIDATA.OVL:0x061d. Before this pass, unnamed action entries were 0x00, 0x1c, 0x20, 0x7f, 0x90, 0x99, 0x9b, and 0xaa..0xaf.
  • Added local labels for all remaining action-table entries:
    • 0x00 (end), a structural main-loop terminator.
    • 0x1c (overlay_picture_var), a variable-sourced picture path that selects a cached picture payload and enters picture decoder 0x6440 instead of 0x6445.
    • 0x20 (discard_view) and 0x99 (discard_view_var), which release or rewind cached view-like resources through helper 0x3f0d.
    • 0x7f (noop), 0x9b (noop_2), and 0xaf (noop_1_table_count), no-op table entries with different observed pointer-advance behavior.
    • 0x90 (append_message_to_log_file), which opens or creates logfile, appends room/input-line context, appends a resolved message, and closes the handle.
    • 0xaa (copy_save_description_to_string_slot), which copies up to 0x1f bytes from buffer 0x0e72 into a logic string slot.
    • 0xab (save_event_buffer_count) and 0xac (restore_event_buffer_count), which preserve and restore the pair-buffer count [0x0143] through [0x05e1].
    • 0xad (increment_global_1530), which only increments byte [0x1530].
    • 0xae (rebuild_priority_table_from_y), which rebuilds the 168-byte priority/control table at 0x127a from an immediate row/value.
  • The follow-up unnamed-action audit reports unnamed actions: [].
  • Dumped condition-table bytes through opcode 0x25. Entries 0x00..0x12 remain the valid-looking condition table; bytes after 0x12 decode as string/data bytes and then zero fill if forced through the same 4-byte shape. No local SQ2 condition list uses condition opcodes 0x13..0x25, so they are documented as invalid/reserved for this build rather than as real predicates.
  • Updated the bytecode spec, graphics/object pipeline note, and local disassembler labels.

Symbolic label map setup

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,220p' AGENTS.md
  • rg -n "symbolic|label|labels|address|offset|0x293c|0x061d|AGI.decrypted|clean_room_executable_notes|SUMMARY" docs/src tools AGENTS.md
  • sed -n '1,220p' docs/src/SUMMARY.md
  • git status --short
  • sed -n '1,120p' docs/src/README.md
  • sed -n '1,220p' docs/src/agi_executable.md
  • sed -n '1,120p' docs/src/runtime_model.md
  • tail -n 120 docs/src/clean_room_executable_notes.md
  • rg -n "0x293c|0x02c4|0x07e3|0x061d|0x08fd|0x07ab|0x08f1|0x21f0|0x119a|0x117d|0x12ae|0x4305|0x2e32|0x2e56|0x4a3b|0x4acf|0x4b3b|0x4bce|0x6445|0x6440|0x6475|0x5546|0x5528|0x5257|0x52f9" docs/src
  • rg -n "0x39f7|0x3ae7|0x3bb7|0x3c1b|0x3ccb|0x593a|0x5762|0x9db6|0x9db0|0x9db3|0x150a|0x16ed|0x1672|0x16b9|0x3fa3|0x3f5a|0x0b36|0x1ce8|0x1f2b|0x2374|0x1f54|0x2753|0x2512|0x28c6|0x26b0|0x85e5" docs/src
  • rg -n "0x096b|0x096d|0x0971|0x0977|0x0981|0x11b2|0x1377|0x1130|0x112e|0x136f|0x127a|0x0a85|0x1707|0x1709|0x0143|0x0009|0x0109|0x020d|0x0c7b|0x0ca3|0x094b" docs/src
  • sed -n '18,34p' docs/src/resource_files.md
  • sed -n '60,72p' docs/src/resource_files.md
  • rg -n "11b2|11b4|11b6|11b8|11ba" docs/src/clean_room_executable_notes.md docs/src/resource_files.md docs/src/symbolic_labels.md
  • rg -n 'Symbolic label map setup|symbolic_labels|Maintain `docs/src/symbolic_labels.md`|Symbolic Labels' AGENTS.md docs/src/SUMMARY.md docs/src/README.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md

Rejected or non-evidence probes:

  • A final rg sanity command used double quotes around a pattern containing backticks. The shell attempted command substitution of docs/src/symbolic_labels.md and printed a permission-denied diagnostic. The same search was rerun with single quotes and the failed command was not used as evidence.

Documented result:

  • Added docs/src/symbolic_labels.md as the cross-version label map. The map separates stable project names from SQ2-specific image, overlay, and data offsets.
  • Seeded the first map with labels already supported by the existing evidence trail: logic interpreter dispatch, message handling, resource loading, DOS wrappers, picture/display helpers, object/view/motion helpers, save/text helpers, and key runtime globals.
  • Rechecked the directory pointer order against the resource-file chapter and recorded view directory [0x11b4], picture directory [0x11b6], and sound directory [0x11b8].
  • Updated the mdBook summary and overview so the label map is part of the rendered documentation.
  • Updated AGENTS.md to require future passes to update the symbolic label map when assigning or revising routine/global/table names.

Picture and view decode/draw pass

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,240p' AGENTS.md
  • sed -n '1,260p' docs/src/graphics_object_pipeline.md
  • sed -n '260,760p' docs/src/graphics_object_pipeline.md
  • sed -n '1,220p' docs/src/symbolic_labels.md
  • ndisasm -b 16 -o 0x6440 -e 0x6640 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x5200 -e 0x5400 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x5680 -e 0x5880 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVL
  • xxd -g 2 -s 0x15d6 -l 0x20 SQ2/AGIDATA.OVL
  • ndisasm -b 16 -o 0x6475 -e 0x6675 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'
  • ndisasm -b 16 -o 0x6600 -e 0x6800 build/cleanroom/AGI.decrypted.exe | sed -n '1,210p'
  • ndisasm -b 16 -o 0x52f9 -e 0x54f9 build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'
  • xxd -g 2 -s 0x15f8 -l 0x60 SQ2/AGIDATA.OVL
  • xxd -g 2 -s 0x1618 -l 0x50 SQ2/AGIDATA.OVL
  • ndisasm -b 16 -o 0x526f -e 0x546f build/cleanroom/AGI.decrypted.exe | sed -n '1,80p'
  • ndisasm -b 16 -o 0x533b -e 0x553b build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'
  • sed -n '1,260p' tools/inspect_view.py
  • python3 -B tools/inspect_view.py 11
  • ndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVL | sed -n '1,180p'
  • ndisasm -b 16 -o 0x587d -e 0x5a7d build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'
  • Python one-off scan of all local view payloads through tools/inspect_view.py to count frame control-byte values and nonzero preview string offsets.
  • Python one-off scan to find the first local frame with control bit 0x80; the first match was view 0, group 0, frame 0.

Rejected or non-evidence probes:

  • The broad ndisasm reads around 0x6440, 0x5200, and 0x5680 produced excessive trailing disassembly because no output filter was applied. They were useful for orientation only; the focused sed-limited reruns above are the cited evidence for the documented details.

Documented result:

  • Expanded the picture decoder notes from a handler sketch into opcode-level semantics for command bytes 0xf0..0xfa, grounded in the local dispatch table at SQ2/AGIDATA.OVL:0x15d6.
  • Identified 0xf0/0xf1 as low-nibble draw enable/disable commands and 0xf2/0xf3 as high-nibble control draw enable/disable commands.
  • Identified the coordinate reader contract: 0x66c1 reads/clamps X to 0x9f, 0x66d4 reads/clamps Y to 0xa7, and bytes above 0xef terminate the current drawing command for the main scanner.
  • Split the path-drawing families:
    • 0xf4 starts with a vertical segment and then alternates horizontal/vertical corners.
    • 0xf5 starts with a horizontal segment and then alternates vertical/horizontal corners.
    • 0xf6 draws absolute point-to-point lines through helper 0x66e1.
    • 0xf7 draws relative vector steps from packed delta bytes.
  • Documented 0xf8 conservatively as a seed-fill command through helper 0x533b; its stack-state names remain open, but its seed/expand/write shape is stable.
  • Documented pattern command 0xf9 and patterned draw command 0xfa, including pattern pointer table DS:0x1619 and mask data rooted at DS:0x15f9.
  • Expanded the view frame model from the IBM object overlay:
    • frame byte +0x00 is width;
    • frame byte +0x01 is height/row count;
    • frame byte +0x02 low nibble is the transparent color/run code;
    • row data begins at +0x03;
    • zero ends a row;
    • nonzero bytes encode high-nibble color and low-nibble run length.
  • Documented object drawing’s priority/control comparison: the overlay writes object priority/control from object byte +0x24 into the destination high nibble, skips writes behind higher existing control values, and may scan downward in the same column to find a comparison value.
  • Documented helper 0x587d as the bit-0x80 frame rewrite path. It compares frame control bits 0x70 with object byte +0x0a, updates those bits, and rebuilds the row-encoded frame stream in place when they differ.
  • Added symbolic labels for the new picture command handlers, line/fill helpers, pattern data, picture draw-state globals, and the view-frame rewrite helper.

2026-07-03: source-first lifecycle pass for room, menu, save, and sound

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' docs/src/symbolic_labels.md
  • rg -n "room\\.switch|switch_state|main_cycle|menu_direction|sound|save|restore|selection|0x1792|0x0150|0x44a9|0x16b3" docs/src tools tests
  • python3 -B tools/disassemble_logic.py 0
  • ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x0150 -e 0x0350 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x4529 -e 0x4729 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x93d1 -e 0x95d1 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x2470 -e 0x2670 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x2700 -e 0x2900 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x51d0 -e 0x53d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x85e0 -e 0x87e0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x10d0 -e 0x12d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x1485 -e 0x1685 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x7060 -e 0x7260 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x681c -e 0x6a1c build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x4480 -e 0x4680 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • xxd -g 2 -s 0x16b3 -l 0x30 SQ2/AGIDATA.OVL
  • xxd -g 2 -s 0x16d7 -l 0x30 SQ2/AGIDATA.OVL

Rejected or corrected observations:

  • A first xxd read of offset 0x16b3 used build/cleanroom/AGI.decrypted.exe. That offset belongs to AGIDATA runtime data, so the dump was not used. The command was rerun against SQ2/AGIDATA.OVL, confirming the movement table bytes.

Documented result:

  • Refined code.room.switch_state (0x1792) from disassembly. The helper stops sound, resets heap/update-list state, clears parser/input state, initializes and enables resource-event recording, resets all object records, resets room caches through 0x10d0, sets object-boundary word [0x0139], resets horizon-like word [0x012d] to 0x24, updates current/previous room byte variables, loads the destination logic, reloads trace logic [0x1d12] when configured, consumes byte variable 2 as the entry-boundary selector, sets flag 5, refreshes display/status/input state, and returns zero. The exact cache behavior was refined later: 0x10d0 preserves the first logic cache record while clearing view, sound, and picture cache roots.
  • Re-read code.engine.main_cycle (0x0150) to explain the zero return. When code.logic.call_logic(0) returns zero, the main cycle clears temporary boundary bytes and calls logic 0 again immediately. This supports the model that room-switch bytecode intentionally aborts the current script stream so logic 0 can re-enter and later dispatch the current room with call_logic_var(v0).
  • Mapped input/event queue helpers around 0x44a9, 0x44f9, 0x4529, 0x467f, 0x46b6, and 0x46e8. The raw event queue is a 20-record circular queue rooted at DS:0x11ba, with write pointer 0x120a and read pointer 0x120c.
  • Confirmed data.input.menu_direction_event_map at AGIDATA 0x16b3 maps raw BIOS arrow/keypad words 0x4800, 0x4900, 0x4d00, 0x5100, 0x5000, 0x4f00, 0x4b00, and 0x4700 to movement codes 1..8. The adjacent display-adapter remap table at 0x16d7 maps numeric keypad bytes 0x38, 0x39, 0x36, 0x33, 0x32, 0x31, 0x34, and 0x37 to movement codes 1..8.
  • Refined code.menu.interact (0x93d1). Event type 1 handles Enter/Escape: Enter enqueues type-3 item ids only for enabled items, while Escape exits without a selection. Event type 2 dispatches movement codes through the navigation table and persists current heading/item globals [0x1d2e] and [0x1d30] after each non-exit movement.
  • Refined save/restore from 0x2472, 0x2512, 0x2753, and 0x85e5. The shared selector handles modal text/path UI and returns zero for cancel. Save writes a 31-byte header followed by length-prefixed engine, object, inventory, resource-event, and logic/cache blocks. Restore reads the same block families and calls code.restore.replay_resource_events (0x681c) to rebuild loaded resource/display/object state before menu refresh.
  • Refined sound source notes. Action 0x63 stops any prior active sound, stores and clears the completion flag, looks up an already loaded sound record through 0x50d8, and calls driver start helper 0x7f96. Action 0x64 calls 0x5234; that helper only sets the configured completion flag when active-state word [0x1258] is nonzero, then clears active state and calls driver stop helper 0x80af.
  • Added symbolic labels for the newly mapped room, heap, input/event, menu, restore, save, sound, and event-queue roles.

Initial graphics compatibility test scaffold

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • find . -maxdepth 3 -type f | sort | sed -n '1,220p'
  • sed -n '1,260p' tools/disassemble_logic.py
  • sed -n '1,260p' tools/inspect_view.py
  • sed -n '1,200p' .gitignore
  • sed -n '1,200p' docs/.gitignore
  • rg -n "test|suite|compat|QEMU|picture decoder|View payload|0xf8|seed_fill" docs/src tools AGENTS.md
  • find . -maxdepth 2 -type d | sort
  • python3 -B -m unittest tests.test_graphics_rendering
  • Python one-off import of tools.agi_graphics to locate present PICDIR entries; the first present picture entry was picture 1.
  • Python one-off import of tools.agi_graphics to calculate current renderer hashes for picture 1, view 0 group 0 frame 0, and view 11 group 0 frame 0.
  • python3 -B tools/render_picture.py 1 --output build/rendered/picture_001_visual.ppm
  • python3 -B tools/render_picture.py 1 --channel control --output build/rendered/picture_001_control.ppm
  • python3 -B tools/render_view.py 0 0 0 --output build/rendered/view_000_00_00.ppm
  • python3 -B tools/render_view.py 11 0 0 --output build/rendered/view_011_00_00.ppm
  • magick build/rendered/picture_001_visual.ppm build/rendered/picture_001_visual.png
  • magick build/rendered/picture_001_control.ppm build/rendered/picture_001_control.png
  • magick build/rendered/view_000_00_00.ppm build/rendered/view_000_00_00.png
  • magick build/rendered/view_011_00_00.ppm build/rendered/view_011_00_00.png
  • identify -verbose build/rendered/picture_001_visual.png | sed -n '1,80p'
  • identify -verbose build/rendered/picture_001_control.png | sed -n '1,80p'
  • identify -verbose build/rendered/view_000_00_00.png | sed -n '1,80p'
  • identify -verbose build/rendered/view_011_00_00.png | sed -n '1,80p'
  • python3 -B -m unittest
  • python3 -B -m unittest discover -s tests
  • mdbook build docs
  • git diff --check

Rejected or non-evidence probes:

  • The first renderer smoke test assumed picture 0 was present. The local directory entry for picture 0 is absent, so this failed with ValueError: picture 0 is absent and was not used as evidence.
  • The first unit-test run used placeholder hashes. Those failures only proved that expected values had not yet been seeded.
  • Plain python3 -B -m unittest reported zero tests, so it is not the suite command for this repository. The documented command now uses explicit test discovery with discover -s tests.

Documented result:

  • Added tools/agi_graphics.py as the first reusable local graphics decoding module. It parses picture and view resources from the local directory and volume files, writes simple PPM output, and exposes deterministic render buffers for tests.
  • Added tools/render_picture.py and tools/render_view.py as command-line helpers for generating picture and view fixtures under build/rendered/.
  • Added tests/test_graphics_rendering.py with six initial unit tests covering picture directory presence, picture 1 scan termination, deterministic picture rendering, all-view frame parsing, and deterministic rendering for two sample view cels.
  • The local test suite passed with python3 -B -m unittest discover -s tests.
  • ImageMagick inspection reported nonblank sample outputs:
    • picture_001_visual.png: 160 by 168, 11 colors.
    • picture_001_control.png: 160 by 168, 2 colors.
    • view_000_00_00.png: 7 by 33, 6 colors.
    • view_011_00_00.png: 20 by 5, 7 colors.
  • The picture renderer remains provisional for seed fill and pattern plotting. The new picture hashes are regression checks for the current implementation hypothesis, not final original-engine compatibility claims.

Graphics compatibility census expansion

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' docs/src/graphics_object_pipeline.md
  • sed -n '1,240p' docs/src/compatibility_testing.md
  • sed -n '1,460p' tools/agi_graphics.py
  • sed -n '1,220p' tests/test_graphics_rendering.py
  • Python one-off scan of all non-null PICDIR entries using read_volume_payload; 74 entries had valid volume headers, and entry 147 decoded to invalid target (0, 0x2ffff).
  • Python one-off scan of all valid picture payloads through render_picture; all 74 valid pictures rendered without an exception.
  • Python one-off command-byte census over all valid picture payloads.
  • Python one-off view-row scan over all valid view payloads; all decoded rows stayed within their declared frame widths.
  • xxd -g 1 SQ2/PICDIR | tail -n 8
  • Python one-off print of final bytes for LOGDIR, PICDIR, VIEWDIR, and SNDDIR.
  • python3 -B -m unittest discover -s tests
  • python3 -B tools/render_picture.py 45 --output build/rendered/picture_045_visual.ppm
  • python3 -B tools/render_picture.py 45 --channel control --output build/rendered/picture_045_control.ppm
  • magick build/rendered/picture_045_visual.ppm build/rendered/picture_045_visual.png
  • magick build/rendered/picture_045_control.ppm build/rendered/picture_045_control.png
  • identify -verbose build/rendered/picture_045_visual.ppm | sed -n '1,80p'
  • identify -verbose build/rendered/picture_045_control.ppm | sed -n '1,80p'
  • Python one-off hash calculation for rendered picture 45 cells, visual nibbles, and control nibbles.

Rejected or non-evidence probes:

  • The first all-picture scan treated every non-null directory entry as a valid resource. It failed on PICDIR entry 147 with ValueError('bad VOL.0 resource header at 0x2ffff'). This failure is now recorded as evidence of a sentinel-like directory entry, but the failed scan’s incomplete totals were not used.

Documented result:

  • Added iter_valid_resources(dir_name) to tools/agi_graphics.py. It keeps the raw directory reader unchanged but skips entries whose volume headers do not validate.
  • Expanded tests/test_graphics_rendering.py from 6 to 12 tests.
  • The picture tests now assert:
    • 74 valid PICDIR payloads;
    • invalid/sentinel-like entry 147 as (0, 0x2ffff);
    • every valid picture renders to a 160 by 168 buffer;
    • every valid picture payload ends with 0xff;
    • the exact all-picture command-byte census;
    • deterministic hashes for picture 1 and picture 45.
  • The view tests now assert:
    • 2,066 decoded frames;
    • 50,640 decoded rows;
    • no decoded row exceeds its frame width;
    • maximum observed cel dimensions of 88 by 129;
    • deterministic hashes for two sample cels.
  • Picture 45 is the longest valid picture payload observed in this pass, at 4,974 bytes. Its current provisional renderer full-cell hash is 7e8132ddf0658ada246440e409f0801a416d88f003495b7a9f55fbee23fb3974.
  • The all-picture command-byte census over valid payloads is:
    • 0xf0: 4,746
    • 0xf1: 309
    • 0xf2: 1,018
    • 0xf3: 425
    • 0xf6: 7,736
    • 0xf7: 9,282
    • 0xf8: 1,447
    • 0xf9: 22
    • 0xfa: 701
    • 0xff: 74
  • No valid local SQ2 picture payload uses command 0xf4 or 0xf5 in this scan.
  • ImageMagick inspection reported nonblank picture 45 samples:
    • picture_045_visual.ppm: 160 by 168, 11 colors.
    • picture_045_control.ppm: 160 by 168, 11 colors.

PPM inspection helper for QEMU validation

Commands run from /Users/peter/ai/agi/reverse:

  • find build -maxdepth 3 -type f | sort | sed -n '1,160p'
  • find . -maxdepth 3 -type f -name '*screen*' -o -name '*.ppm' -o -name '*.png' | sort | sed -n '1,160p'
  • identify build/dos622/sq2_01.ppm build/dos622/sq2_02.ppm build/dos622/screen0.ppm build/rendered/picture_001_visual.ppm build/rendered/picture_045_visual.ppm
  • Python one-off read of the first 64 bytes of selected QEMU PPM captures.
  • python3 -B -m unittest discover -s tests
  • python3 -B tools/inspect_ppm.py build/dos622/sq2_01.ppm
  • python3 -B tools/inspect_ppm.py build/rendered/picture_045_visual.ppm
  • python3 -B tools/inspect_ppm.py build/rendered/picture_045_control.ppm

Documented result:

  • Added tools/ppm_tools.py with a small binary PPM reader, RGB digest helper, unique-color collection, and first-pixel-background bounding-box helper.
  • Added tools/inspect_ppm.py as a CLI wrapper around those helpers.
  • Added a unit test proving local picture PPM output can be parsed by the same helper intended for QEMU screenshots.
  • Existing QEMU screenshots under build/dos622/ include both 720 by 400 DOS text-mode captures and 640 by 400 SQ2 game captures. For example, build/dos622/sq2_01.ppm parsed as 640 by 400, 4 colors, RGB SHA-256 80605890a86b4cfe5304c389a7fec9c7ece9c809812bec8923c60e464fcda12f, with non-background bounds (0, 16, 639, 331).
  • Local generated picture renders remain in the logical 160 by 168 coordinate space. Picture 45 visual PPM parsed as 160 by 168, 11 colors, RGB SHA-256 92dc42b905eab360dcec460dbdba5f2382c7c833d461efa2c9c5fc3e86ba213b; its control PPM parsed as 160 by 168, 11 colors, RGB SHA-256 354e29e62f1e27ef9f56a3a4db251ac04d5e86a2e095f3ff541d9232a08ef055.
  • The QEMU-to-local comparison layer still needs an explicit normalization transform because the emulator screenshot is a full VGA frame, not the raw 160 by 168 logical picture buffer.

Pattern plot renderer refinement

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x6524 -e 0x6724 build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'
  • ndisasm -b 16 -o 0x64f0 -e 0x66f0 build/cleanroom/AGI.decrypted.exe | sed -n '1,80p'
  • xxd -g 1 -s 0x15f8 -l 0x80 SQ2/AGIDATA.OVL
  • xxd -g 2 -s 0x1618 -l 0x40 SQ2/AGIDATA.OVL
  • rg -n "pattern|0x652a|0x15f8|0x15f9|0x1619|0xfa|0xf9" docs/src tools
  • Python one-off print of pattern_column_mask() and pattern_row_words() from the local AGIDATA bytes.
  • python3 -B -m unittest discover -s tests

Documented result:

  • Replaced the local picture renderer’s placeholder circular pattern plotting with the observed helper 0x652a algorithm.
  • Added pattern_column_mask(column) and pattern_row_words(radius) helpers that read the local AGIDATA.OVL pattern tables instead of hard-coding row shapes in the renderer.
  • Added a unit test for the observed column masks and selected row-word tables.
  • The observed column masks selected from DS:0x15f9 + column * 4 are: 0x8000, 0x2000, 0x0800, 0x0200, 0x0080, 0x0020, 0x0008, and 0x0002.
  • The pattern helper draws radius + 1 columns and 2 * radius + 1 rows after clipping from the source coordinate.
  • Mode bit 0x10 bypasses the row-word/column-mask test.
  • Mode bit 0x20 enables the byte recurrence seeded from [0x15f8] | 1: shift right, XOR with 0xb8 when carry was set, and draw only when bit 0 is clear and bit 1 is set.
  • Existing picture regression hashes for pictures 1 and 45 remained unchanged after this refinement, but they still require QEMU comparison before being treated as original-engine parity checks.

Seed fill renderer refinement

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x533b -e 0x563b build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x52f9 -e 0x543b build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • rg -n "0x533b|seed_fill|fill|0xf8|0x534|0x53" docs/src tools
  • ndisasm -b 16 -o 0x53f9 -e 0x553b build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x54a0 -e 0x55e2 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x5724 -e 0x5866 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'
  • python3 -B -m unittest discover -s tests
  • Python one-off hash calculation for pictures 1 and 45 after the seed-fill model update.
  • python3 -B tools/render_picture.py 45 --output build/rendered/picture_045_visual.ppm
  • python3 -B tools/render_picture.py 45 --channel control --output build/rendered/picture_045_control.ppm
  • python3 -B tools/inspect_ppm.py build/rendered/picture_045_visual.ppm
  • python3 -B tools/inspect_ppm.py build/rendered/picture_045_control.ppm
  • magick build/rendered/picture_045_visual.ppm build/rendered/picture_045_visual.png
  • magick build/rendered/picture_045_control.ppm build/rendered/picture_045_control.png

Rejected or non-evidence probes:

  • The first disassembly command in this pass used the previously noted seed label but landed on an unhelpful alignment window. The focused reruns around 0x53f9..0x55e5 are the evidence for this pass.

Documented result:

  • Refined the local seed-fill model from “fill each active channel separately” to the observed interpreter contract:
    • if visual drawing is active, select low nibble target 0xf;
    • otherwise, if control drawing is active, select high nibble target 0x40;
    • exit immediately if the selected replacement is the default target value;
    • for accepted cells, call the normal pixel write path, so both active channels can be changed by the fill.
  • The executable helper is a stack-backed horizontal span fill. The local renderer still uses an explicit queue over four-neighbor cells for traversal, but now uses the observed test-channel priority and normal pixel-write rule.
  • Picture 1 hashes did not change. Picture 45’s visual hash did not change, but its control and combined-cell hashes changed, matching the expectation that the refinement affects control side effects when both draw channels are active.
  • Updated the picture 45 full-cell regression hash to 7e8132ddf0658ada246440e409f0801a416d88f003495b7a9f55fbee23fb3974.
  • Updated the generated picture 45 control PPM RGB hash to 354e29e62f1e27ef9f56a3a4db251ac04d5e86a2e095f3ff541d9232a08ef055.
  • Added two synthetic picture bytecode tests for seed fill:
    • f2 02 f0 01 f8 00 00 ff starts with both control and visual drawing active. It expands through the low-nibble default target and writes every cell to 0x21, proving that visual is the test channel but both active channels can be written.
    • f2 02 f8 00 00 ff has only control drawing active. It expands through the high-nibble default target and writes every cell to 0x2f.
  • The local compatibility suite passed with 16 tests after these synthetic checks were added.

Picture line helper refinement

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x66e1 -e 0x68c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x526f -e 0x5460 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x66e1 -e 0x68e1 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x526f -e 0x546f build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • Python one-off comparison of the observed accumulator stepping model with the previous generic line algorithm over small coordinate ranges.
  • python3 -B -m unittest discover -s tests

Rejected or non-evidence probes:

  • The first 0x66e1 disassembly command used the wrong file skip offset and landed slightly early. The rerun with -e 0x68e1 is the evidence used for the line-helper notes.

Documented result:

  • Replaced the local diagonal line routine with the observed helper 0x66e1 accumulator structure:
    • horizontal and vertical special cases use dedicated helpers;
    • the caller plots the starting point before entering the helper;
    • the major axis supplies the loop count;
    • the minor-axis accumulator starts at half the major delta;
    • Y accumulator/step is processed before X accumulator/step;
    • each generated point is written through the normal pixel helper.
  • Existing SQ2 picture regression hashes did not change after this refinement.
  • Added synthetic tests for an absolute 0xf6 line and a packed relative 0xf7 line. Both forms currently assert the plotted point set (0,0), (1,0), (2,1), (3,1).
  • The local compatibility suite passed with 18 tests after these line checks were added.

Object-frame composition helper

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "object drawing|IBM_OBJS|0x9db|view frame|transparent|priority|\\+0x24|0x587d|rewrite" docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools
  • ndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVL | sed -n '1,260p'
  • ndisasm -b 16 -o 0x587d -e 0x5a7d build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • sed -n '260,860p' docs/src/graphics_object_pipeline.md
  • sed -n '1,260p' tools/inspect_view.py
  • python3 -B -m unittest discover -s tests

Documented result:

  • Added draw_frame_on_buffer() and compose_frame_on_picture() to tools/agi_graphics.py.
  • The helper models the central object overlay draw rule from IBM_OBJS.OVL:0x9db6: object top is baseline_y - frame.height + 1, transparent-color pixels do not write, and nontransparent pixels write (priority << 4) | color only when the destination high-nibble priority/control comparison permits it.
  • Added tests for baseline placement, transparent pixels, direct rejection by a higher existing priority, and rejection after scanning downward from a low-control cell.

Generated logic fixture for QEMU picture validation

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' docs/src/logic_resources.md
  • sed -n '1,220p' docs/src/logic_bytecode.md
  • rg -n "load_picture|prepare_picture|show_picture|draw|return|end|0x18|0x19|0x1a|logic header|message|messages|LOGIC" docs/src tools/disassemble_logic.py
  • python3 -B tools/disassemble_logic.py 0 | sed -n '1,220p'
  • xxd -g 1 -l 160 SQ2/VOL.1
  • Python one-off print of action-table entries for assignn, load_picture_var, prepare_picture_var, show_picture_like, and related display/input actions.
  • Python one-off scan of valid local logic resources to inspect zero-message payload trailers.
  • Python one-off print of VOL.* file sizes and first LOGDIR entries.
  • xxd -g 1 -l 32 SQ2/VOL.3
  • python3 -B -m unittest discover -s tests
  • python3 -B tools/qemu_fixture.py picture 45 --output build/qemu-fixtures/picture_045
  • xxd -g 1 -l 32 build/qemu-fixtures/picture_045/LOGDIR
  • xxd -g 1 -l 64 build/qemu-fixtures/picture_045/VOL.3
  • find build/qemu-fixtures/picture_045 -maxdepth 1 -type f | wc -l
  • Python one-off check of generated VOL.3 header/payload bytes and patched LOGDIR[0].
  • mdir -i build/dos622/dos622.img@@32256 ::
  • mmd -i build/dos622/dos622.img@@32256 ::/SQ2P45
  • mcopy -i build/dos622/dos622.img@@32256 build/qemu-fixtures/picture_045/* ::/SQ2P45
  • qemu-system-i386 -m 16 -boot c -drive file=build/dos622/dos622.img,format=raw,if=ide,index=0,media=disk -display vnc=127.0.0.1:5 -monitor stdio
  • QEMU monitor sendkey commands for cd \SQ2P45 and SIERRA.
  • QEMU monitor command screendump build/qemu-fixtures/picture_045/qemu_picture_045.ppm.
  • QEMU monitor command quit.
  • python3 -B tools/inspect_ppm.py build/qemu-fixtures/picture_045/qemu_picture_045.ppm
  • identify build/qemu-fixtures/picture_045/qemu_picture_045.ppm
  • Python one-off nearest-palette/downsample comparison between the QEMU capture and render_picture(45).visual_nibbles.
  • python3 -B tools/compare_picture_capture.py 45 build/qemu-fixtures/picture_045/qemu_picture_045.ppm
  • magick build/qemu-fixtures/picture_045/qemu_picture_045.ppm build/qemu-fixtures/picture_045/qemu_picture_045.png

Rejected or non-evidence probes:

  • python3 -B tools/disassemble_logic.py --logic 0 failed because the local disassembler takes logic numbers as positional arguments.
  • One rg command used a search pattern containing markdown backticks and failed shell quoting; it was not used as evidence.
  • The first synthetic scaled-PPM unit test wrote only 200 rows while declaring a 400-row image. The PPM parser rejected it, the test was corrected, and the malformed generated file was not used as evidence.

Documented result:

  • Added tools/qemu_fixture.py. For picture N, it copies the local SQ2/ files into build/qemu-fixtures/picture_NNN, replaces VOL.3 with a custom logic resource, and patches LOGDIR[0] to point to VOL.3 offset zero.
  • The generated picture-45 logic payload is:
    • resource header in VOL.3: 12 34 03 10 00;
    • logic code length: 0b 00;
    • bytecode: 03 fa 2d 18 fa 19 fa 1a fe fd ff;
    • message trailer: 00 02 00.
  • The bytecode means:
    • assignn(v250, 45);
    • load_picture_var(v250);
    • prepare_picture_var(v250);
    • show_picture_like();
    • jump -3, looping on the jump after the picture has been shown.
  • Copied the generated fixture to C:\SQ2P45 inside build/dos622/dos622.img and launched it with the original interpreter in QEMU.
  • Captured build/qemu-fixtures/picture_045/qemu_picture_045.ppm from QEMU. The capture is 640 by 400, has 11 nearest-palette colors, and has RGB SHA-256 615a1a8ae22d4e04774f725adb395bc3d05372b10d41c81a61a99eb098d1d34c.
  • A top-left aligned 4x2 downsample from the 640 by 400 QEMU capture to the 160 by 168 logical picture space matched render_picture(45).visual_nibbles with 0 mismatches out of 26,880 pixels.
  • Added tools/compare_picture_capture.py and a synthetic scaled-capture unit test for the comparison path.
  • The local compatibility suite passed with 25 tests after adding the fixture and comparison helpers.

Generated logic fixture for QEMU picture plus view validation

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/qemu_fixture.py picture-view 1 11 0 0 20 80 15 --output build/qemu-fixtures/picture_001_view_011_00_00
  • python3 -B -m unittest discover -s tests
  • mmd -i build/dos622/dos622.img@@32256 ::/SQ2V11
  • mcopy -i build/dos622/dos622.img@@32256 build/qemu-fixtures/picture_001_view_011_00_00/* ::/SQ2V11
  • qemu-system-i386 -m 16 -boot c -drive file=build/dos622/dos622.img,format=raw,if=ide,index=0,media=disk -display vnc=127.0.0.1:5 -monitor stdio
  • QEMU monitor sendkey commands for cd \SQ2V11 and SIERRA.
  • QEMU monitor command screendump build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm.
  • QEMU monitor command quit.
  • python3 -B tools/inspect_ppm.py build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm
  • magick build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.png
  • python3 -B tools/compare_picture_capture.py 1 build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm --view 11 0 0 --view-x 20 --view-baseline-y 80 --view-priority 15

Documented result:

  • Extended tools/qemu_fixture.py with picture-view, which draws a selected picture, loads a selected view, draws one selected cel at a controlled position, and then loops.
  • The generated picture-1/view-11/group-0/frame-0 logic payload is:
    • resource header in VOL.3: 12 34 03 1a 00;
    • logic code length: 15 00;
    • bytecode: 03 fa 01 18 fa 19 fa 1a 1e 0b 7a 0b 00 00 14 50 0f 0f fe fd ff;
    • message trailer: 00 02 00.
  • The bytecode means:
    • assignn(v250, 1);
    • load_picture_var(v250);
    • prepare_picture_var(v250);
    • show_picture_like();
    • load_view(11);
    • setup_transient_object(view=11, group=0, frame=0, x=20, baseline_y=80, priority=15, control=15);
    • jump -3, looping on the jump after the picture and cel have been shown.
  • Copied the generated fixture to C:\SQ2V11 inside build/dos622/dos622.img and launched it with the original interpreter in QEMU.
  • Captured build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm from QEMU. The capture is 640 by 400, has 15 unique RGB colors, has a non-background bounding box of (0, 0, 639, 335), and has RGB SHA-256 f63b82fb30ab0c2796f695e2678937f1c0a90e9cb3bbb85338bfccea5a6ac816.
  • Using the same top-left aligned 4x2 downsample as the picture-only fixture, the QEMU capture matched compose_frame_on_picture(render_picture(1), render_view_frame(11, 0, 0), left=20, baseline_y=80, priority=15) with 0 mismatches out of 26,880 pixels.
  • The local compatibility suite passed with 26 tests before running the QEMU fixture.

Bit-0x80 view-frame orientation rewrite

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/inspect_view.py 0 1 2 11 --groups 8 --frames 8
  • ndisasm -b 16 -o 0x587d -e 0x5a7d build/cleanroom/AGI.decrypted.exe
  • Python one-off scan of valid VIEWDIR resources for frames with control bit 0x80 whose cached bits (control & 0x70) >> 4 differ from the selected group number.
  • Python one-off print of view 0/group 0/frame 0 row bytes before and after the local rewrite model.
  • python3 -B -m unittest discover -s tests
  • python3 -B tools/qemu_fixture.py picture-view 1 0 1 0 20 80 15 --output build/qemu-fixtures/picture_001_view_000_01_00
  • mmd -i build/dos622/dos622.img@@32256 ::/SQ2V01
  • mcopy -i build/dos622/dos622.img@@32256 build/qemu-fixtures/picture_001_view_000_01_00/* ::/SQ2V01
  • qemu-system-i386 -m 16 -boot c -drive file=build/dos622/dos622.img,format=raw,if=ide,index=0,media=disk -display vnc=127.0.0.1:5 -monitor stdio
  • QEMU monitor sendkey commands for cd \SQ2V01 and SIERRA.
  • QEMU monitor command screendump build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm.
  • QEMU monitor command quit.
  • python3 -B tools/inspect_ppm.py build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm
  • magick build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.png
  • python3 -B tools/compare_picture_capture.py 1 build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm --view 0 1 0 --view-x 20 --view-baseline-y 80 --view-priority 15

Rejected or non-evidence probes:

  • The first local row-rewrite model treated the mirrored row’s emitted leading transparent width as width - tail_width, where tail_width started at the first nontransparent run. The disassembly keeps explicit leading transparent width in the accumulator before the first nontransparent run, so the correct emitted width is width - total_explicit_row_width. The failing unit test was not used as evidence.

Documented result:

  • Added _mirror_view_row_runs() and _orient_view_rows() to tools/agi_graphics.py.
  • The rewrite model matches helper 0x587d:
    • only frames with control bit 0x80 are candidates;
    • bits 0x70 of the frame control byte cache the current orientation/group;
    • when cached bits differ from the selected group, those bits are replaced;
    • each row is rebuilt by emitting the original implicit trailing transparent width, then copying the counted run bytes in reverse order, then writing a zero row terminator.
  • A local scan found 229 valid SQ2 frames where bit 0x80 is set and the selected group differs from the cached bits. View 0/group 1/frame 0 is the first such sample.
  • Added a unit test proving that render_view_frame(0, 1, 0) is the horizontal mirror of render_view_frame(0, 0, 0) and reports rewritten control byte 0x91.
  • Captured build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm from QEMU. The capture is 640 by 400, has 14 unique RGB colors, has a non-background bounding box of (0, 0, 639, 335), and has RGB SHA-256 1fb4fbfaa4d7b93b15fa007930e87d7c1982cb78626441a28d56ae46fdd8bd96.
  • Using the same top-left aligned 4x2 downsample as the previous fixtures, the QEMU capture matched compose_frame_on_picture(render_picture(1), render_view_frame(0, 1, 0), left=20, baseline_y=80, priority=15) with 0 mismatches out of 26,880 pixels.
  • The local compatibility suite passed with 27 tests after the rewrite model and orientation test were added.

Synthetic picture fuzzing framework

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' tools/qemu_fixture.py
  • sed -n '1,220p' tools/compare_picture_capture.py
  • sed -n '1,620p' tools/agi_graphics.py
  • find tests -maxdepth 2 -type f -print
  • sed -n '1,220p' tests/test_qemu_fixture.py
  • python3 -B -m unittest discover -s tests
  • python3 -B tools/picture_fuzz.py generate --count 64 --seed 4097 --output build/picture-fuzz/corpus --clean
  • python3 -B tools/picture_fuzz.py run-qemu base_003_visual_point --dos-dir FZVPOINT --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean
  • python3 -B tools/picture_fuzz.py run-qemu base_002_unknown_commands --dos-dir FZUNK --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py run-qemu base_004_clamped_absolute --dos-dir FZCLAMP --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py run-qemu base_009_visual_control_fill --dos-dir FZFILL --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py run-qemu base_011_pattern_random --dos-dir FZPATT --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py run-qemu base_013_truncated_pair --dos-dir FZTRUNC --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py compare-capture base_004_clamped_absolute build/picture-fuzz/fixtures/base_004_clamped_absolute/qemu_capture.ppm
  • ndisasm -b 16 -o 0x66e1 -e 0x68e1 build/cleanroom/AGI.decrypted.exe
  • Python one-off comparing captured and locally rendered changed pixels for base_004_clamped_absolute.
  • xxd -g 1 -l 96 build/picture-fuzz/fixtures/base_004_clamped_absolute/VOL.3
  • xxd -g 1 -l 12 build/picture-fuzz/fixtures/base_004_clamped_absolute/PICDIR
  • cat build/picture-fuzz/corpus/base_004_clamped_absolute/case.json
  • Python one-off simulating line helper 0x66e1 with and without 8-bit accumulator wrap.
  • python3 -B tools/picture_fuzz.py run-qemu base_005_exact_edge_absolute --dos-dir FZEDGE --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py compare-capture base_004_clamped_absolute build/picture-fuzz/fixtures/base_004_clamped_absolute/qemu_capture.ppm
  • python3 -B tools/picture_fuzz.py run-qemu base_010_visual_control_fill --dos-dir FZFIL3 --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py run-qemu base_012_pattern_random --dos-dir FZPAT3 --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py run-qemu base_014_truncated_pair --dos-dir FZTRN3 --boot-wait 5 --draw-wait 8

Rejected or non-evidence probes:

  • The first run-qemu base_003_visual_point attempt hid QEMU output and surfaced only a broken pipe. The harness was corrected to preserve QEMU output on early exit.
  • Picture fuzz cases marked safe_for_qemu: false are excluded from original-engine compatibility evidence. They can make the interpreter read past the synthetic resource and begin interpreting unrelated memory, which is security/exploit behavior rather than a stable semantic contract for AGI resource decoding.
  • Python-launched QEMU initially failed under the restricted sandbox with Failed to bind socket: Operation not permitted; the same command succeeded after the user allowed full access.
  • Running three QEMU fuzz cases in parallel was rejected as a bad probe. QEMU needs a single VNC display socket, and concurrent mtools/QEMU fixture copying can collide on the DOS image.
  • A compare for base_005_exact_edge_absolute was started in parallel with a corpus regeneration command. The compare raced the regenerated manifest and failed with FileNotFoundError; the case was present after regeneration and the QEMU comparison was rerun sequentially.
  • While probing mtools behavior, directory FZNEW99 was created in the DOS image. It was not used as evidence.

Documented result:

  • Added generic patch_dir_entry() and build_synthetic_picture_fixture() to tools/qemu_fixture.py. A synthetic fixture copies local SQ2/, writes VOL.3 with both custom LOGIC.0 and a synthetic picture payload, patches LOGDIR[0] to the logic record, and patches PICDIR[picture_no] to the picture record.
  • Added tools/picture_fuzz.py. It can:
    • generate deterministic curated and random picture-resource corpora;
    • record payload hashes and Python render hashes in case.json and manifest.json;
    • build original-engine fixture directories for a selected fuzz case;
    • copy a fixture into the DOS image, run it in QEMU, capture a screendump, and compare the original-engine output against the Python renderer;
    • compare an existing QEMU capture and report mismatch count, bounding box, and sample mismatched pixels.
  • Added tests/test_picture_fuzz.py and expanded tests/test_qemu_fixture.py. The local suite passed with 34 tests after the fuzz framework and line regression were added.
  • The current corpus command python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean generates 1,040 cases: 16 curated base cases and 1,024 deterministic random cases. It currently marks 1,038 cases safe for automated QEMU runs.
  • Updated tools/picture_fuzz.py run-qemu to reject cases marked safe_for_qemu: false before building or launching a fixture. This preserves the project boundary that malformed overread/exploit behavior is not part of the compatibility spec being built.
  • The first representative QEMU fuzz cases matched:
    • base_002_unknown_commands: 0 mismatches;
    • base_003_visual_point: 0 mismatches;
    • base_009_visual_control_fill before renumbering: 0 mismatches;
    • base_011_pattern_random before renumbering: 0 mismatches;
    • base_013_truncated_pair before renumbering: 0 mismatches.
  • base_004_clamped_absolute initially mismatched with 312 pixels. The mismatch samples showed a screen-scale diagonal line displaced relative to the Python renderer. The exact payload in the fixture was f0 02 f6 ef ef 00 00 ff, and PICDIR[0] correctly pointed to the synthetic picture in VOL.3, so the mismatch was treated as behavioral evidence rather than a fixture error.
  • A new curated case, base_005_exact_edge_absolute, using payload f0 02 f6 9f a7 00 00 ff, reproduced the same mismatch. That proved the gap was not out-of-range coordinate clamping but the diagonal line helper.
  • Simulating code.picture.draw_line (0x66e1) with 8-bit accumulator wrap reproduced the original-engine edge shape. In particular, the long line from (159,167) to (0,0) includes (25,0) and (25,1) and does not include (0,0).
  • Updated PictureRenderer.draw_line() to wrap the X and Y accumulators to 8 bits after addition and subtraction. Added test_long_diagonal_uses_byte_width_line_accumulators.
  • After the fix, base_004_clamped_absolute and base_005_exact_edge_absolute both matched QEMU with 0 mismatches out of 26,880 pixels.
  • Reran representative cases after the fix:
    • base_010_visual_control_fill: 0 mismatches;
    • base_012_pattern_random: 0 mismatches;
    • base_014_truncated_pair: 0 mismatches.

Picture fuzz batch runner and pattern edge correction

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_picture_fuzz
  • python3 -B tools/picture_fuzz.py generate --count 64 --seed 4097 --output build/picture-fuzz/corpus --clean
  • python3 -B tools/picture_fuzz.py batch-qemu --case base_000_stop_only --case base_001_ignored_data --case base_002_unknown_commands --case base_003_visual_point --case base_004_clamped_absolute --case base_005_exact_edge_absolute --case base_006_relative_mixed --case base_007_corner_y_first --case base_008_corner_x_first --case base_009_control_fill --case base_010_visual_control_fill --case base_011_pattern_mask --case base_012_pattern_random --case base_014_truncated_pair --dos-prefix FB --output build/picture-fuzz/batches/base_curated_001.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py batch-qemu --case rand_00000 --case rand_00001 --case rand_00002 --case rand_00003 --case rand_00004 --case rand_00005 --case rand_00006 --case rand_00007 --case rand_00008 --case rand_00009 --case rand_00010 --case rand_00011 --case rand_00012 --case rand_00013 --case rand_00014 --case rand_00015 --dos-prefix FR --output build/picture-fuzz/batches/random_00000_00015.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/picture_fuzz.py batch-qemu --case base_016_visual_fill_box --case base_017_visual_fill_outside_box --case base_018_pattern_edge_circle --case base_019_pattern_edge_rectangle --case base_020_pattern_random_sequence --dos-prefix FT --output build/picture-fuzz/batches/targeted_fill_pattern_001.json --boot-wait 5 --draw-wait 8
  • mdir -i build/dos622/dos622.img@@32256 ::
  • python3 -B tools/picture_fuzz.py batch-qemu --case base_016_visual_fill_box --case base_017_visual_fill_outside_box --case base_018_pattern_edge_circle --case base_019_pattern_edge_rectangle --case base_020_pattern_random_sequence --dos-prefix FV --output build/picture-fuzz/batches/targeted_fill_pattern_003.json --boot-wait 5 --draw-wait 8

Documented result:

  • Added batch-qemu to tools/picture_fuzz.py. It selects only safe cases, runs them serially through QEMU, prints per-case progress, and writes JSON reports containing status, capture paths, elapsed seconds, mismatch boxes, and mismatch samples.
  • The 14-case curated safe batch matched with 0 mismatches and 0 errors.
  • The first 16 safe random cases matched with 0 mismatches and 0 errors.
  • Added curated cases base_016 through base_020 for bounded fill barriers, lower-right pattern edge placement, rectangular pattern masks, and multiple pseudo-random pattern seeds.
  • The first targeted fill/pattern batch showed that bounded fill and pseudo-random pattern sequences matched, but lower-right circle and rectangle pattern plots mismatched at X 0.
  • The mismatch proved that pattern plotting can compute X 160 and write through the linear picture buffer address. This makes the byte appear at X 0 on the next scanline instead of being clipped.
  • Updated PictureRenderer.pattern_plot() to write through a linear buffer helper. After regenerating the corpus, base_018_pattern_edge_circle and base_019_pattern_edge_rectangle both matched QEMU with 0 mismatches.
  • A rerun initially failed with DOS-image Disk full errors, not renderer mismatches. The cause was old generated .ppm captures being copied back into the DOS image with fixture files. copy_fixture_to_dos() now excludes .ppm files, and generated fuzz directories were removed from the DOS image.

View/object QEMU batch validation

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest discover -s tests
  • python3 -B tools/view_batch.py --dos-prefix VB --output build/view-batch/batches/view_base_001.json --boot-wait 5 --draw-wait 8
  • Cached Python comparison of view_011_top_clip against alternate local left and baseline_y values.
  • python3 -B -m unittest tests.test_graphics_rendering tests.test_view_batch
  • python3 -B tools/view_batch.py --dos-prefix VC --output build/view-batch/batches/view_base_002.json --boot-wait 5 --draw-wait 8

Documented result:

  • Added tools/view_batch.py, a serial QEMU harness for picture-plus-view fixtures. It builds fixtures with build_picture_view_fixture(), runs each one in QEMU, compares the capture with local picture/view composition, and writes a JSON report.
  • Extended tools/compare_picture_capture.py to include mismatch bounding boxes and sample pixels in addition to mismatch counts.
  • Added tests/test_view_batch.py for case loading, stable DOS directory names, and report summaries.
  • The first six-case view batch matched normal view drawing, cached group-0 bit-0x80 drawing, mirrored group-1 bit-0x80 drawing, left-edge clipping, and a low-priority object case. The top-edge case mismatched with 75 pixels in rows 0 through 4.
  • Comparing the top-edge capture against local placements found an exact match at left 18, baseline 4, even though the fixture requested left 20, baseline 2. Since view 11/group 0/frame 0 has height 5, the requested top was -2; the observed overlay behavior is to add that negative top to left, raise the baseline by the absolute amount, and draw from top row 0.
  • Updated draw_frame_on_buffer() with that top-edge adjustment and added a regression test. The second view batch matched all six cases with 0 mismatches and 0 errors.

QEMU host-directory fixture access

Commands run from /Users/peter/ai/agi/reverse:

  • qemu-system-i386 --version
  • Python QEMU launch probe with: -drive file=fat:rw:build/qemu-share,format=raw,if=ide,index=1,media=disk
  • python3 -B tools/qemu_fixture.py picture 1 --output build/qemu-share/PIC001
  • QEMU monitor-driven run from DOS drive D:: D:, cd \PIC001, SIERRA, followed by screendump build/qemu-share/from_share_pic001.ppm
  • python3 -B tools/compare_picture_capture.py 1 build/qemu-share/from_share_pic001.ppm
  • qemu-img create -f qcow2 -F raw -b /Users/peter/ai/agi/reverse/build/dos622/dos622.img build/dos622/dos622-test.qcow2
  • QEMU snapshot probes with writable vvfat hard disk, read-only vvfat CD-ROM, and fat:floppy: host shares.

Documented result:

  • QEMU 11.0.2 accepts file=fat:rw:build/qemu-share as a secondary IDE disk. DOS 6.22 sees it as drive D: with volume label QEMU VVFAT.
  • A generated picture-1 fixture placed at build/qemu-share/PIC001 ran directly from D:\PIC001 without copying it into the DOS hard disk image. The capture build/qemu-share/from_share_pic001.ppm matched the local picture-1 renderer with 0 mismatches.
  • The generated fixture does not return to DOS after drawing; sending DIR after the draw left the game screen visible. Running multiple cases in one guest session therefore needs a reset/restore mechanism.
  • QEMU savevm fails when a writable vvfat drive is attached: The vvfat (rw) format ... does not support live migration.
  • A qcow2 overlay for the DOS boot disk can hold VM snapshots. A read-only vvfat CD-ROM permits savevm, but the current DOS image lacks an IDE/ATAPI CD-ROM driver, so DIR D:\ reports Invalid drive specification.
  • fat:floppy: read-only host shares can be snapshotted and are visible as A: only when the host directory fits FAT12 constraints. This is too small and awkward for full AGI fixture directories; a nested fixture directory appeared empty, and a root-level fixture exposed only a subset of files.
  • Practical next options:
    • use fat:rw: as D: now to avoid mtools copies and DOS-image pollution, still launching/resetting QEMU per case;
    • install/configure a DOS CD-ROM driver and use read-only vvfat CD-ROM plus savevm/loadvm for no-boot batches;
    • or generate a qcow2/FAT test disk containing prebuilt fixtures and use QEMU snapshots at the DOS prompt.

QEMU snapshot fixture disk

Commands run from /Users/peter/ai/agi/reverse:

  • Built a picture-only fixture and a picture-plus-view fixture: python3 -B tools/qemu_fixture.py picture 1 --output build/qcow-fixture-test/fixtures/PIC001 and python3 -B tools/qemu_fixture.py picture-view 1 11 0 0 20 80 15 --output build/qcow-fixture-test/fixtures/VIEW11.
  • Probed a partitionless FAT image, an MBR-partitioned FAT image starting at sector 63, and a DOS-geometry-like secondary hard disk. All could either be manipulated by mtools or attached to QEMU, but DOS did not expose them as a usable D: drive.
  • Copied build/dos622/dos622.img to a disposable raw image, copied fixture directories into its DOS partition with mtools at offset 32256, converted it to qcow2, booted it as C:, ran savevm ready, then used loadvm ready between PIC001 and VIEW11.
  • Compared the captures with: python3 -B tools/compare_picture_capture.py 1 build/qcow-fixture-test/snapshot_pic001.ppm and python3 -B tools/compare_picture_capture.py 1 build/qcow-fixture-test/snapshot_view11.ppm --view 11 0 0 --view-x 20 --view-baseline-y 80 --view-priority 15.

Documented result:

  • The secondary qcow2/FAT fixture disk approach is not yet usable with DOS 6.22 in this setup.
  • A disposable qcow2 clone of the DOS boot disk with fixture directories preloaded onto C: supports QEMU internal snapshots.
  • Both snapshot-run captures matched with 0 mismatches, proving that one QEMU boot plus savevm/loadvm can replace repeated boots for batches whose fixture set is known in advance.
  • Added tools/qemu_snapshot.py and tools/view_batch.py --snapshot to make this reusable for the view/object validation cases.
  • Added tools/picture_fuzz.py batch-qemu --snapshot so known-ahead fuzz batches can use the same one-boot fixture disk rather than rebooting QEMU for each case.
  • Verified the reusable path with tools/view_batch.py --snapshot: all six built-in view/object cases matched from one QEMU boot.
  • Verified tools/picture_fuzz.py batch-qemu --snapshot with base_016_visual_fill_box and base_019_pattern_edge_rectangle: both cases matched with 0 mismatches from one QEMU boot.

2026-07-02: object overlay priority probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest discover -s tests
  • python3 -B tools/object_overlay_probe.py --dos-prefix OP --output build/object-overlay-probes/batches/base_priority.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_overlay_probe.py --dos-prefix OQ --output build/object-overlay-probes/batches/priority_scan_down.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_overlay_probe.py --dos-prefix OR --output build/object-overlay-probes/batches/priority_nibbles.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_overlay_probe.py --dos-prefix OG --output build/object-overlay-probes/batches/expanded_all5_final.json --boot-wait 5 --draw-wait 8

Documented result:

  • Added build_synthetic_picture_view_fixture() to tools/qemu_fixture.py. This patches both LOGDIR[0] and a chosen PICDIR entry so the original engine can draw a generated picture resource and then overlay a chosen view cel through action 0x7a (setup_transient_object).
  • Added tools/object_overlay_probe.py, a snapshot-backed QEMU harness for targeted object overlay cases with controlled synthetic picture backgrounds.
  • The eight built-in priority probes matched QEMU with 0 mismatches. The cases confirm:
    • default cleared control priority 4 hides object priority 3 and allows object priority 4;
    • full-screen synthetic control priority 6 hides object priority 5 and allows object priority 6;
    • visible overlay gating uses the low nibble of object byte +0x24, not the high nibble staged by operand 7 of action 0x7a;
    • when the destination cell contains low control 2, the overlay routine scans downward to a control-6 row and applies the same comparison there.
  • The expanded 19-case object overlay batch matched QEMU with 0 mismatches. It adds evidence for:
    • right-edge transient placement: requested left 154, baseline 80 for view 11/group 0/frame 0 matched a local draw at left 140, baseline 67;
    • bottom-edge drawing for view 11 at baseline 167;
    • transparent-color variants in views 21, 29, and 10;
    • derived priority when 0x7a stages low priority zero, including the effect of action 0xae rebuilding the priority table at row 100;
    • persistent object-table setup and activation for a static object;
    • persistent fixed priority bytes with nonzero high nibbles hiding in the controlled priority probes, so those byte values should not yet be treated as normal visible priorities.

2026-07-02: targeted object movement QEMU probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest discover -s tests
  • python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_once.json --boot-wait 5 --draw-wait 8
  • Local best-position scan over the failed one-shot captures.
  • python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_reissued.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_edges.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_control.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_control_final.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_movement_probe.py --dos-prefix MX --output build/object-movement-probes/batches/expanded_movement_edges.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_movement_probe.py --dos-prefix MX --output build/object-movement-probes/batches/expanded_movement_edges_final.json --boot-wait 5 --draw-wait 8
  • ndisasm -b 16 -o 0x4719 -e 0x4919 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • Focused search for writes to object byte +0x02.
  • python3 -B tools/object_movement_probe.py --dos-prefix MC --output build/object-movement-probes/batches/movement_collision.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_movement_probe.py --dos-prefix MA --output build/object-movement-probes/batches/autonomous_modes_001.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_movement_probe.py --dos-prefix MB --output build/object-movement-probes/batches/autonomous_modes_002.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_movement_probe.py --dos-prefix MD --output build/object-movement-probes/batches/autonomous_modes_003.json --boot-wait 5 --draw-wait 8
  • Local Python scan for near call sites to 0x0b36, 0x3f5a, 0x1672, and 0x16b9.
  • ndisasm -b 16 -o 0x0563 -e 0x0763 build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'

Documented result:

  • Added reusable generated-logic helpers to tools/qemu_fixture.py: logic_resource, if_then, not_flag_set_condition, set_flag_action, run_once_logic, and persistent-object fixture support for guarded per-cycle action blocks.
  • Added tools/object_movement_probe.py, a snapshot-backed QEMU harness for persistent object-table movement tests. It builds generated logic resources that load a synthetic picture, initialize a persistent object once, then run selected per-cycle actions while a completion flag is clear.
  • A one-shot 0x51 (move_object_to) fixture initialized the object and called the action once. QEMU showed that the object moved in the initial direction but did not stop at the requested target: the horizontal case matched exactly at left 140, and the vertical case matched exactly at baseline 167. This establishes that a single action call does not recompute target arrival on later ticks.
  • The corrected fixture reissues 0x51 every interpreter cycle while the completion flag is clear. With that shape, the base horizontal target (50,80) and vertical target (20,100) both matched QEMU with 0 mismatches.
  • The expanded four-case movement batch also matched QEMU with 0 mismatches. It adds right-edge completion for an unreachable target X of 250, where view 11/group 0/frame 0 stops at left 140, and bottom-edge completion for an unreachable target Y of 250, where the same cel stops at baseline 167.
  • A first control-buffer acceptance hypothesis used a synthetic picture payload f2 00 f8 00 00 ff, which fills the decoded control channel with zero in the local renderer. The initial expectation was that movement would be rejected, but QEMU’s capture matched exact target arrival at (50,80). After updating the expected result, the final five-case batch matched QEMU with 0 mismatches. This records that this controlled control-zero picture is not a blanket movement blocker.
  • The movement comparison report now includes an optional best_position tuple (mismatches, x, baseline_y) for mismatches, so future failures can identify where QEMU actually drew the object.
  • The expanded 12-case movement batch matched QEMU with 0 mismatches after correcting the zero-step expectation. It confirms:
    • left and up directions use the same repeated-call target semantics;
    • diagonal movement can continue on one axis after the other axis enters the target band;
    • non-divisible distances complete when the remaining distance is within one step, not necessarily at the exact target coordinate;
    • already-at-target and already-within-step cases complete without movement;
    • operand 3 value zero preserves the current object step byte, and the generated persistent object has current step zero unless the fixture sets it.
  • Disassembled 0x4719 with the corrected MZ-header offset. The helper returns zero immediately when the moving object has bit 0x0200; otherwise it scans active candidates with (flags & 0x41) == 0x41, skips candidates with bit 0x0200, skips equal +0x02 grouping bytes, checks horizontal rectangle overlap, then checks baseline equality/crossing using current +0x05 and saved +0x18.
  • Startup initialization writes object byte +0x02 = object_index, so ordinary generated persistent object 0 and object 1 naturally have different grouping bytes and can collide.
  • Added two-object cases to tools/object_movement_probe.py. The final 14-case batch matched QEMU with 0 mismatches:
    • object 0 moving from (20,80) toward (80,80) stops at (25,80) before touching object 1 parked at (50,80);
    • setting bit 0x0200 on object 0 with action 0x43 lets the same movement reach (80,80).
  • Added bytecode fixture helpers for assignn, set_object_field_1e_var, set_object_field_01_var, approach_first_object_until_near, start_random_motion, and stop_motion_mode, then added a mode-2 approach case to the movement probe.
  • The first mode-2 QEMU pass used threshold 25 and did not isolate direct completion: the capture best fit was object 1 at (60,75), indicating that the object had reached the collision/stuck-recovery region near object 0.
  • The second pass used threshold 35 and initially expected boundary position (45,80), but QEMU’s best fit was (50,80). This records that the near-band completion check did not complete at the exact threshold boundary.
  • The corrected 15-case batch in build/object-movement-probes/batches/autonomous_modes_003.json matched QEMU with 0 mismatches. The new passing case initializes object 1 once, sets step 5, sets countdown byte +0x01 to 1, starts 0x53 toward object 0 with near threshold 35, and confirms autonomous mode 2 completes at (50,80) without reissuing 0x53 from script logic.
  • Later call-site analysis corrected this per-cycle ordering. Helper 0x0644, not 0x0563, scans active/update-eligible objects before logic execution and calls dispatcher 0x067a for objects whose countdown byte +0x01 is exactly 1. Dispatcher 0x067a calls 0x3f5a for mode +0x22 == 1, 0x0b36 for mode 2, and 0x1672 for mode 3. It then calls extra rectangle-boundary logic when global [0x013d] is nonzero and direction byte +0x21 is nonzero. Helper 0x0563 is called later, after logic 0, and performs automatic group/frame work before invoking 0x150a and rebuilding/drawing/refreshing the update list rooted at 0x16ff. A later generated QEMU movement fixture isolates the countdown-gated 0x067a -> 0x1672 path directly.

Countdown-gated motion and additional view cel probes

Commands/evidence:

  • python3 -B -m unittest discover -s tests
  • python3 -B tools/object_movement_probe.py --dos-prefix ME --output build/object-movement-probes/batches/motion_modes_004.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/object_overlay_probe.py --dos-prefix OE --output build/object-overlay-probes/batches/view_cel_selection_002.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/inspect_view.py 11 --groups 4 --frames 8
  • ndisasm -b 16 -o 0x0b36 -e 0x0d36 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x6b82 -e 0x6d82 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • python3 -B tools/disassemble_logic.py 1 | rg -n "set_object_field_23|clear_object_bit_0020|set_object_bit_0020|0x48|0x49|0x4a|0x4b" -C 2

Documented result:

  • Added two more motion cases to tools/object_movement_probe.py:
    • move_to_once_countdown_gated_completion sets object countdown byte +0x01 to 1, calls 0x51 once, and expects completion at (50,80);
    • random_motion_visible_somewhere sets step 5, sets countdown byte +0x01 to 1, starts 0x54, and accepts any capture that exactly matches the object at a valid final position.
  • The 17-case QEMU movement batch matched with 0 mismatches. It confirms that target mode 3 can complete through the autonomous 0x067a -> 0x1672 path without reissuing 0x51 from script logic when countdown byte +0x01 is ready. The recorded random-motion final position was (140,112).
  • Added three object overlay cases for view 11 group/frame selection: group 0 frame 1, group 1 frame 0, and group 1 frame 1. The 22-case QEMU overlay batch matched with 0 mismatches.
  • Disassembly of 0x0b36 refined the approach stuck-recovery model:
    • mode 2 computes object and first-object center X values by adding half the frame width to object X;
    • it calls 0x16ed with the near threshold stored in object byte +0x27;
    • a zero direction clears +0x21 and +0x22 and sets completion flag +0x28;
    • initial sentinel +0x29 == 0xff changes to 0 after the first non-complete direct approach step;
    • if bit 0x4000 says the object did not move, the helper chooses a random nonzero direction, computes a delay from half the center/baseline distance plus one, and stores either the current step size or a random delay at least as large as the step in +0x29;
    • while +0x29 is nonzero, the helper subtracts the current step byte from it on each pass and delays returning to the direct approach direction.
  • A source pass over action handlers 0x48..0x4b confirmed the setup side of object byte +0x23: mode 0 and mode 3 set bit 0x0020; mode 1 and mode 2 set bits 0x1030, store an immediate flag in +0x27, and clear that flag. The current QEMU work validates static cel/group selection, not automatic frame-cycling semantics; the runtime consumers of +0x23 remain a separate follow-up target.

2026-07-02: logic interpreter opcode coverage and control-flow probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B - <<'PY' ... comparing tools/disassemble_logic.py ACTION_NAMES and COND_NAMES against opcode paragraphs in docs/src/logic_bytecode.md.
  • python3 -B -m unittest discover -s tests
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LI --output build/logic-interpreter-probes/batches/control_flow_001.json --boot-wait 5 --draw-wait 8
  • Local best-position scan over the failed logic-interpreter captures.
  • ndisasm -b 16 -o 0x293c -e 0x2b3c build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LJ --output build/logic-interpreter-probes/batches/control_flow_002.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/disassemble_logic.py --stats | sed -n '1,140p'

Documented result:

  • The opcode-label audit found that tools/disassemble_logic.py labels all action opcodes 0x00..0xaf and all valid-looking SQ2 condition-table entries 0x00..0x12. The only missing paragraph in logic_bytecode.md was the structural action byte 0x00 (end), which is now documented as a normal catalog entry as well as in the interpreter-loop prose.
  • The logic bytecode chapter now also records the invalid and structural byte ranges: action bytes 0xb0..0xfb take the action dispatcher’s invalid-opcode path, 0xfc and 0xfd are invalid outside conditions, 0xfe is the main-stream jump byte, and 0xff is the main-stream condition byte. Condition bytes 0x13..0x25 are reserved/invalid for this SQ2 build, 0x26..0xfb are rejected by the condition dispatcher, and 0xfc..0xff are condition-list markers rather than predicates.
  • Added tests/test_logic_doc_coverage.py so the documentation now fails the local test suite if any action label or known condition label disappears from logic_bytecode.md.
  • Added tools/logic_interpreter_probe.py, a snapshot-backed QEMU harness that patches generated logic bytecode into LOGIC.0, replaces picture 0 with a blank synthetic picture, captures the original interpreter’s visible output, and compares it to local compose_frame_on_picture() expectations.
  • Added setup_transient_object_action() to tools/qemu_fixture.py so logic probes can draw a view cel without duplicating bytecode encoding.
  • The first QEMU run, control_flow_001.json, matched only if_false_skips_then_draw. jump_skips_first_draw, not_condition_runs_then_draw, and or_group_true_runs_then_draw captured a blank background where the expected transient view should have been. A local best-position scan found no exact object match anywhere in those captures.
  • Disassembly of code.logic.interpret_main (0x293c) confirmed the static control-flow model:
    • 0xfe reads a little-endian word through lodsw and adds it to SI;
    • 0xff scans a condition list and, on the true path, skips the false-delta word before executing the block;
    • on the false path, the scanner skips to the condition-list terminator, reads the false-delta word, and adds it to SI;
    • the 0xfd and 0xfc markers are handled in the condition-list parser, not the action dispatcher.
  • The failing generated bytecode ended immediately after a transient draw. The corrected fixtures append the same self-loop shape used by existing picture/view fixtures (fe fd ff) after the final draw so the screenshot is taken while the intended draw state remains visible.
  • The rerun saved at build/logic-interpreter-probes/batches/control_flow_002.json matched all four cases with 0 mismatches and 0 errors:
    • jump_skips_first_draw;
    • if_false_skips_then_draw;
    • not_condition_runs_then_draw;
    • or_group_true_runs_then_draw.

2026-07-02: expanded logic opcode-family probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B - <<'PY' ... to list the expanded tools.logic_interpreter_probe.base_cases() set.
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LK --output build/logic-interpreter-probes/batches/opcode_families_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B tools/logic_opcode_evidence.py
  • python3 -B tools/logic_opcode_evidence.py --check

Documented result:

  • Expanded tools/logic_interpreter_probe.py from four control-flow cases to 27 default QEMU cases. The new cases use a common pattern: set up state, execute the opcode or opcode family under test, then draw view 11 only if a condition observes the expected result. If the original interpreter disagrees with the expected behavior, the capture is missing the transient object and the comparison fails.
  • The expanded batch matched QEMU with 27 matches, 0 mismatches, and 0 errors.
  • Newly QEMU-validated families include:
    • condition 0x00 (always_false);
    • variable inc, dec, assignv, addn, addv, subn, subv;
    • indirect variable forms 0x09..0x0b;
    • multiplication/division forms 0xa5..0xa8, including low-product-byte and quotient behavior;
    • immediate and variable-selected flag actions 0x0c..0x11;
    • comparison predicates 0x02..0x06;
    • object position setter/getter 0x25/0x27;
    • object field +0x24 setter/getter 0x36/0x39;
    • object field +0x21 setter/getter 0x56/0x57.
  • Added tools/logic_opcode_evidence.py and generated docs/src/logic_opcode_evidence.md. The chapter records every action opcode 0x00..0xaf, every known condition opcode 0x00..0x12, and structural or invalid byte ranges with an evidence level. Rows are marked QEMU-validated only when a generated or existing QEMU harness has actually exercised the behavior; otherwise they remain source-backed or reserved/invalid.

2026-07-02: five follow-up logic-interpreter probe groups

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_fixture
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LL --output build/logic-interpreter-probes/batches/five_steps_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LA --output build/logic-interpreter-probes/batches/step1_call_resume_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case call_logic_draws_from_called_logic --case load_logic_then_call_logic_draws --case call_logic_var_draws_selected_logic --case save_restore_resume_actions_continue_to_draw
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LB --output build/logic-interpreter-probes/batches/step2_var_backed_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case set_object_pos_var_getter_observes_values --case var_resource_group_frame_setup_draws_persistent_object --case setup_transient_object_var_draws_selected_cel --case move_object_to_var_sets_flag_at_existing_target
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LC --output build/logic-interpreter-probes/batches/step3_object_predicates_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case object_left_rect_condition_true --case object_width_rect_condition_true --case object_center_rect_condition_true --case object_right_rect_condition_true
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LD --output build/logic-interpreter-probes/batches/step4_string_message_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case set_string_from_message_equal_normalized --case parse_string_slot_sets_input_word_sequence
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LE --output build/logic-interpreter-probes/batches/step5_inventory_table_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case inventory_marker_ff_condition_true --case inventory_marker_eq_var_condition_true --case inventory_marker_ff_var_and_getter --case inventory_marker_clear_and_getter --case inventory_marker_from_var --case inventory_marker_from_var_var
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Extended tools/qemu_fixture.py so logic_resource() can encode custom logic message tables. The helper builds the offset table relative to the table base and XOR-encrypts the message text with the locally documented Avis Durgan key, matching the loader’s decryption path.
  • Extended tools/logic_interpreter_probe.py so one fixture can patch multiple logic resources into VOL.3, patch the corresponding LOGDIR entries, and filter runs with repeated --case options.
  • A first attempt to run all 47 default logic probe cases in one snapshot disk filled the DOS image while copying the forty-fourth full SQ2 fixture directory. No interpreter behavior was observed in that run; it is a harness capacity limit caused by copying complete fixture directories.
  • The requested steps were then run as five filtered QEMU batches:
    • Step 1, logic call/load/resume smoke probes: 4 matches, 0 mismatches.
    • Step 2, variable-backed object/resource probes: 4 matches, 0 mismatches.
    • Step 3, object rectangle predicates: 4 matches, 0 mismatches.
    • Step 4, string/message probes: 2 matches, 0 mismatches.
    • Step 5, inventory/object-table marker probes: 6 matches, 0 mismatches.
  • New QEMU evidence from these batches covers:
    • actions 0x14, 0x16, and 0x17 for logic loading and calls;
    • actions 0x91 and 0x92 as executable resume-state opcodes that continue to subsequent bytecode in the smoke fixture;
    • variable-backed object/resource actions 0x26, 0x2a, 0x2c, 0x30, 0x52, and 0x7b;
    • condition opcodes 0x0b, 0x10, 0x11, and 0x12;
    • action 0x72, action 0x75, condition 0x0f, and condition 0x0e using custom messages HELLO!, hello, and look;
    • condition opcodes 0x09 and 0x0a, plus marker actions 0x5c..0x61.

2026-07-03: object/view getter and bitfield follow-up probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LF --output build/logic-interpreter-probes/batches/object_getter_bitfield_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case object_view_metadata_getters --case object_field_24_var_getter_observes_value --case object_distance_inactive_pair_sets_ff --case clear_object_fields_21_22_clears_direction --case object_bitfield_actions_dispatch_smoke
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Added five object/view follow-up cases to tools/logic_interpreter_probe.py.
  • The QEMU batch matched with 5 matches, 0 mismatches, and 0 errors.
  • Value probes now validate:
    • 0x31..0x35 reading view/object metadata after binding view 11 group 1 frame 1;
    • 0x37 writing object byte +0x24 from a variable, observed through getter 0x39;
    • 0x45 storing 0xff when measuring distance between inactive objects;
    • 0x4d clearing object byte +0x21, observed through getter 0x57.
  • Added a separate QEMU dispatch-smoke evidence level to docs/src/logic_opcode_evidence.md. The smoke case proves that selected bitfield/helper opcodes execute, consume operands, and return to subsequent bytecode under the original interpreter, but does not claim to expose every downstream state mutation. At the time of this pass, smoke rows included 0x38, 0x3a..0x3e, 0x40..0x42, 0x44, 0x46..0x47, 0x4c, 0x4e, and 0x58..0x59; later passes promoted several of those rows to behavior evidence.

2026-07-03: object state, random, and no-op runtime probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LG --output build/logic-interpreter-probes/batches/object_state_misc_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case object_add_pos_from_vars_getter_observes_sum --case random_equal_bounds_stores_bound --case noop_7f_continues_to_draw --case noop_9b_consumes_two_operands_then_draws --case noop_af_runtime_consumes_no_operand --case set_object_pos_dirty_getter_observes_values --case set_object_pos_dirty_var_getter_observes_values --case deactivate_object_removes_persistent_draw --case clear_all_object_bits_removes_persistent_draw
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LG --output build/logic-interpreter-probes/batches/object_state_misc_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_all_object_bits_keeps_current_draw_entry
  • python3 -B tools/logic_opcode_evidence.py
  • python3 -B tools/logic_opcode_evidence.py --check

Documented result:

  • Added QEMU-visible logic probes for additional object-state and misc actions:
    • 0x28 adds positive variable-sourced deltas to object position fields, observed through getter 0x27;
    • 0x82 stores the bound when its low and high random bounds are equal;
    • 0x7f, 0x9b, and 0xaf execute and continue to following drawing bytecode in the original interpreter;
    • 0x93 and 0x94 write object position fields, observed through getter 0x27;
    • 0x24 deactivates an active persistent object so only the following transient draw remains visible in the fixture.
  • The first nine-case batch matched 8 cases and mismatched the initial 0x22 hypothesis. The mismatch box was x=20..39, y=76..80, exactly the footprint of the previously activated persistent object. This showed that action 0x22 clearing active/update bits does not immediately unlink an object that was already activated for the current draw.
  • Extended tools/logic_interpreter_probe.py comparison expectations so a case can include additional expected sprites. The corrected 0x22 case, clear_all_object_bits_keeps_current_draw_entry, expects both the old persistent object at x=20 and the following transient object at x=50.
  • The corrected single-case rerun in build/logic-interpreter-probes/batches/object_state_misc_002.json matched with 1 match, 0 mismatches, and 0 errors.
  • Regenerated docs/src/logic_opcode_evidence.md; actions 0x22, 0x24, 0x28, 0x7f, 0x82, 0x93, 0x94, 0x9b, and 0xaf are now recorded with QEMU-backed evidence.

2026-07-03: variable view load and object field +0x23 probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LH --output build/logic-interpreter-probes/batches/load_view_field23_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case load_view_var_allows_following_draw --case object_field_23_mode0_dispatch_smoke --case object_field_23_mode1_clears_flag --case object_field_23_mode3_dispatch_smoke --case object_field_23_mode2_clears_flag
  • python3 -B tools/logic_opcode_evidence.py
  • python3 -B tools/logic_opcode_evidence.py --check

Documented result:

  • Added a preload_view_no option to tools/logic_interpreter_probe.py so individual cases can start without the default load_view 11 prelude. This was necessary for the 0x1f probe: if view 11 were preloaded, a following draw would not prove that load_view_var did the loading.
  • The five-case QEMU batch build/logic-interpreter-probes/batches/load_view_field23_001.json matched with 5 matches, 0 mismatches, and 0 errors.
  • 0x1f (load_view_var) is now QEMU-validated: a fixture assigns variable 1 to view 11, executes 0x1f, and then draws view 11 successfully without any earlier view preload.
  • 0x49 (set_object_field_23_mode1) and 0x4b (set_object_field_23_mode2) are QEMU-validated for the observable part of their setup contract: each clears its flag operand, and the generated logic draws only if that flag is observed clear.
  • 0x48 (set_object_field_23_mode0) and 0x4a (set_object_field_23_mode3) are recorded as QEMU dispatch-smoke evidence: the fixtures prove each opcode executes and returns to following draw bytecode, but they do not directly expose object byte +0x23.

2026-07-03: collision-skip clear-bit movement probe

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_qemu_fixture tests.test_object_movement_probe
  • python3 -B tools/object_movement_probe.py --dos-prefix MD --output build/object-movement-probes/batches/clear_skip_bit_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_collision_clear_skip_bit_blocks_again
  • python3 -B tools/logic_opcode_evidence.py
  • python3 -B tools/logic_opcode_evidence.py --check

Documented result:

  • Added clear_object_bit_0200_action() to tools/qemu_fixture.py, encoding action 0x44 with one fixed object operand.
  • Added filtered --case support to tools/object_movement_probe.py so a single movement case can be rerun without rebuilding the whole movement corpus.
  • Added movement case move_collision_clear_skip_bit_blocks_again: object 0 first sets collision-skip bit 0x0200 with 0x43, then clears it with 0x44, then moves toward object 1. QEMU matched the expected blocked result at (25,80), proving that 0x44 restores normal object-object collision testing after the bit was set.
  • Regenerated docs/src/logic_opcode_evidence.md; action 0x44 is now recorded as QEMU-validated instead of dispatch-smoke.

2026-07-03: horizon-bit placement probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LZ --output build/logic-interpreter-probes/batches/horizon_bits_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case horizon_clamps_object_when_bit_clear --case horizon_exempt_bit_keeps_object_above_horizon --case horizon_clear_exempt_bit_restores_clamp
  • python3 -B tools/logic_opcode_evidence.py
  • python3 -B tools/logic_opcode_evidence.py --check

Documented result:

  • Added three logic-interpreter QEMU cases around the horizon-like placement clamp:
    • horizon_clamps_object_when_bit_clear: 0x3f sets [0x012d] = 100; a reset object placed at baseline 80 clamps to baseline 101;
    • horizon_exempt_bit_keeps_object_above_horizon: after 0x3d sets object bit 0x0008, the same placement remains at baseline 80;
    • horizon_clear_exempt_bit_restores_clamp: after 0x3d sets and 0x3e clears bit 0x0008, the placement clamps to baseline 101 again.
  • The QEMU batch build/logic-interpreter-probes/batches/horizon_bits_001.json matched with 3 matches, 0 mismatches, and 0 errors.
  • Regenerated docs/src/logic_opcode_evidence.md; actions 0x3d, 0x3e, and 0x3f are now QEMU-validated instead of source-backed or dispatch-smoke.

2026-07-03: fixed-priority clear-bit probe

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LP --output build/logic-interpreter-probes/batches/fixed_priority_bit_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_fixed_priority_bit_uses_derived_priority
  • The first attempt failed under the restricted sandbox because QEMU could not bind 127.0.0.1:5 for VNC. The same command was rerun with elevated QEMU permission.
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Added logic-interpreter QEMU case clear_fixed_priority_bit_uses_derived_priority.
  • The fixture draws a synthetic control-6 picture, sets object 10 to fixed priority/control byte 5 with action 0x36, then clears object bit 0x0004 with action 0x38 before drawing.
  • QEMU matched the expected visible output: at baseline 80, placement derived priority 7 from Y and drew over the control-6 background. This validates the observable effect of 0x38, not just its dispatch.
  • Regenerated docs/src/logic_opcode_evidence.md; action 0x38 is now recorded as QEMU-validated instead of dispatch-smoke.

2026-07-03: clear motion mode with action 0x4e

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_object_movement_probe tests.test_qemu_fixture
  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B tools/object_movement_probe.py --dos-prefix ME --output build/object-movement-probes/batches/clear_field_22_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_field_22_after_random_motion_stops_motion
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Added clear_object_field_22_and_global_action() to tools/qemu_fixture.py, encoding action 0x4e with one fixed object operand.
  • Added movement case clear_field_22_after_random_motion_stops_motion: object 0 is initialized at (60,80), random motion is started with action 0x54, and action 0x4e is executed immediately afterward.
  • QEMU matched the expected stationary result at (60,80). This validates the visible object byte +0x22 clearing effect of 0x4e; the static side effect on global [0x0139] remains documented from disassembly rather than this capture.
  • Regenerated docs/src/logic_opcode_evidence.md; action 0x4e is now recorded as QEMU-validated instead of dispatch-smoke.

2026-07-03: control and rectangle object-bit probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_object_movement_probe tests.test_qemu_fixture
  • Exploratory batch: python3 -B tools/object_movement_probe.py --dos-prefix MC --output build/object-movement-probes/batches/control_bit_0002_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_blocks --case move_control_1_set_bit_0002_reaches_target --case move_control_1_clear_bit_0002_blocks_again
  • Exploratory batch: python3 -B tools/object_movement_probe.py --dos-prefix MR --output build/object-movement-probes/batches/control_bit_0002_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_reaches_target --case move_control_1_set_bit_0002_reaches_target --case move_control_1_clear_bit_0002_still_reaches_target --case move_rect_boundary_without_bit_0002_stops_at_edge --case move_rect_boundary_set_bit_0002_reaches_target --case move_rect_boundary_clear_bit_0002_stops_again
  • Matched rectangle-boundary batch: python3 -B tools/object_movement_probe.py --dos-prefix MB --output build/object-movement-probes/batches/rect_bit_0002_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_rect_boundary_without_bit_0002_stops_at_edge --case move_rect_boundary_set_bit_0002_reaches_target --case move_rect_boundary_clear_bit_0002_stops_again
  • Exploratory full acceptance batch: python3 -B tools/object_movement_probe.py --dos-prefix MX --output build/object-movement-probes/batches/control_bits_acceptance_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_blocks --case move_control_1_set_bit_0002_reaches_target --case move_control_1_clear_bit_0002_blocks_again --case move_rect_boundary_without_bit_0002_stops_at_edge --case move_rect_boundary_set_bit_0002_reaches_target --case move_rect_boundary_clear_bit_0002_stops_again --case move_control_2_set_bit_0100_blocks --case move_control_2_clear_bits_0900_reaches_target --case move_control_3_set_bit_0800_blocks --case move_control_3_clear_bits_0900_reaches_target
  • Matched control-class-1 hidden batch: python3 -B tools/object_movement_probe.py --dos-prefix M1 --output build/object-movement-probes/batches/control_class_1_hidden_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_blocks --case move_control_1_set_bit_0002_still_hidden --case move_control_1_clear_bit_0002_still_hidden
  • Matched control-class-2/3 rejection batch: python3 -B tools/object_movement_probe.py --dos-prefix M9 --output build/object-movement-probes/batches/control_bits_0900_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_2_set_bit_0100_blocks --case move_control_2_clear_bits_0900_reaches_target --case move_control_3_set_bit_0800_blocks --case move_control_3_clear_bits_0900_reaches_target
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Added fixture helpers for actions 0x40, 0x41, 0x42, 0x58, 0x59, and 0x5a.
  • Added picture_only comparison support to tools/object_movement_probe.py for cases where the original engine leaves no visible object and the capture should equal the rendered picture alone.
  • The first control-class-1 probes used fixed priority/control 15 and reached the target, exposing that code.object.control_acceptance skips the scan when object byte +0x24 == 0x0f.
  • Repeating control-class-1 probes at fixed priority/control 14 produced plain-picture captures whether bit 0x0002 was clear, set by 0x58, or set then cleared by 0x59. This is recorded as control-class/visibility evidence, not as the positive 0x58 movement oracle.
  • The positive 0x58/0x59 oracle is rectangle-boundary behavior in code.motion.pre_mode_and_boundary_update: with rectangle bounds (30,70)..(60,90), countdown-gated movement from (20,80) toward (50,80) stops at (30,80) when bit 0x0002 is clear, reaches (50,80) after 0x58, and stops at (30,80) again after 0x59.
  • QEMU validates 0x40: setting bit 0x0100 leaves a priority-14 object visible at (20,80) on a full control-class-2 picture and prevents movement to (50,80).
  • QEMU validates 0x41: setting bit 0x0800 leaves a priority-14 object visible at (20,80) on a full control-class-3 picture and prevents movement to (50,80).
  • QEMU validates 0x42: clearing bits 0x0100/0x0800 after 0x40 or 0x41 restores movement to (50,80).
  • Added symbolic labels code.object.control_acceptance, code.motion.pre_mode_and_boundary_update, and code.motion.rectangle_boundary_check.
  • Regenerated docs/src/logic_opcode_evidence.md; actions 0x40, 0x41, 0x42, 0x58, and 0x59 are now QEMU-validated instead of dispatch-smoke.

2026-07-03: static frame-timer and frame-mode analysis

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -c "import struct; data=open('build/cleanroom/AGI.decrypted.exe','rb').read(64); print(struct.unpack_from('<14H', data, 0))"
  • python3 -B -c "import sys; sys.path.insert(0,'tools'); from disassemble_logic import AGIDATA, load_table; data=AGIDATA.read_bytes(); table=load_table(data,0x061d,0xb0); [print(f'{op:02x} handler={table[op].handler:04x} argc={table[op].argc} meta={table[op].meta:02x}') for op in [0x3a,0x3b,0x3c,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x58,0x59,0x5a]]"
  • ndisasm -b 16 -o 0 build/cleanroom/AGI.decrypted.exe > build/cleanroom/AGI.decrypted.ndisasm
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_6ac8.bin bs=1 skip=27848 count=570
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0400.bin bs=1 skip=1536 count=1700
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_4800.bin bs=1 skip=18944 count=1200
  • ndisasm -b 16 -o 0x6ac8 build/cleanroom/slice_6ac8.bin
  • ndisasm -b 16 -o 0x0400 build/cleanroom/slice_0400.bin
  • ndisasm -b 16 -o 0x4800 build/cleanroom/slice_4800.bin
  • rg -n "call 0x48|call 0x4[0-9a-f]{3}|\\[di\\+0x20\\]|\\[di\\+0x23\\]|\\[di\\+0x1f\\]" build/cleanroom/AGI.decrypted.ndisasm

Documented result:

  • Re-centered this pass on static disassembly after the user pointed out that the work had drifted too far toward trial-and-error QEMU probing. QEMU remains useful as a validation tool, but the behavior model in this section comes from the executable.
  • Confirmed the MZ/header address convention for handler disassembly. The action dispatch table in SQ2/AGIDATA.OVL stores loaded-image offsets; the decrypted executable file stores the corresponding bytes at image offset +0x0200, because the executable header is 32 paragraphs. For example action 0x5a is table/image 0x7b4e and its bytes are at file offset 0x7d4e.
  • Confirmed action table entries:
    • 0x46 handler 0x6c97, one operand: clears object flag bit 0x0020.
    • 0x47 handler 0x6cbc, one operand: sets object flag bit 0x0020.
    • 0x48 handler 0x6b82, one operand: sets byte +0x23 = 0 and bit 0x0020.
    • 0x49 handler 0x6bae, two operands: sets byte +0x23 = 1, sets bits 0x1030, stores operand 1 in byte +0x27, and clears that flag.
    • 0x4a handler 0x6beb, one operand: sets byte +0x23 = 3 and bit 0x0020.
    • 0x4b handler 0x6c17, two operands: sets byte +0x23 = 2, sets bits 0x1030, stores operand 1 in byte +0x27, and clears that flag.
    • 0x4c handler 0x6c54, two operands: reads var[arg1] and copies the byte into both object bytes +0x1f and +0x20. This corrects the earlier wording that said +0x20 was cleared.
  • Labeled code.object.frame_timer_update at image 0x0563. It scans object records from [0x096b] to [0x096d], selecting records whose flag word satisfies (flags & 0x0051) == 0x0051. If bit 0x0020 is set and byte +0x20 is nonzero, it decrements +0x20; when the decrement reaches zero, it calls code.object.advance_frame_by_mode and reloads +0x20 from +0x1f.
  • Labeled code.object.advance_frame_by_mode at image 0x48b3. If bit 0x1000 is set, the helper clears that bit and returns without changing the selected frame. Otherwise byte +0x23 selects one of four frame behaviors: mode 0 increments and wraps; mode 1 increments toward the last frame and completes there; mode 2 decrements toward frame 0 and completes there, or completes immediately if already at frame 0; mode 3 decrements and wraps from frame 0 to the last frame.
  • Completion in frame modes 1 and 2 sets flag byte +0x27, clears object bit 0x0020, clears direction byte +0x21, and resets byte +0x23 to zero.
  • Updated docs/src/symbolic_labels.md, docs/src/logic_bytecode.md, docs/src/graphics_object_pipeline.md, and docs/src/current_status.md with this static model.

2026-07-03: QEMU validation of frame-timer actions

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -m unittest tests.test_qemu_fixture tests.test_object_movement_probe
  • python3 -B tools/logic_opcode_evidence.py --check
  • python3 -B tools/object_movement_probe.py --dos-prefix MA --output build/object-movement-probes/batches/frame_timer_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case animation_interval_mode1_reaches_frame1 --case animation_clear_bit_0020_prevents_frame_advance --case animation_set_bit_0020_restores_frame_advance
  • The first QEMU attempt failed under the restricted sandbox because QEMU could not bind 127.0.0.1:5 for VNC. The same command was rerun with approved elevated permission for python3 -B tools/object_movement_probe.py.
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Focused tests tests.test_qemu_fixture and tests.test_object_movement_probe passed, covering the newly added helper encodings and movement case registry.
  • QEMU batch build/object-movement-probes/batches/frame_timer_001.json matched with 3 matches, 0 mismatches, and 0 errors:
    • animation_interval_mode1_reaches_frame1: action 0x4c seeds the frame timer and action 0x49 starts mode 1; view 11/group 0 advances from frame 0 to frame 1.
    • animation_clear_bit_0020_prevents_frame_advance: action 0x46 clears bit 0x0020 after setup, so the frame remains 0.
    • animation_set_bit_0020_restores_frame_advance: action 0x47 sets bit 0x0020 after 0x46, restoring the frame advance to frame 1.
  • Promoted actions 0x46, 0x47, and 0x4c in tools/logic_opcode_evidence.py from QEMU dispatch-smoke to QEMU behavior evidence backed by object_movement_probe: frame_timer_001, then regenerated docs/src/logic_opcode_evidence.md.
  • Updated docs/src/logic_bytecode.md, docs/src/graphics_object_pipeline.md, and docs/src/compatibility_testing.md with the replay command and result.

2026-07-03: QEMU validation of remaining frame modes

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_48b3_49c8.bin bs=1 skip=19123 count=280
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_6b82_6ce0.bin bs=1 skip=28034 count=350
  • ndisasm -b 16 -o 0x48b3 build/cleanroom/slice_48b3_49c8.bin
  • ndisasm -b 16 -o 0x6b82 build/cleanroom/slice_6b82_6ce0.bin
  • python3 -B -c "import sys; sys.path.insert(0,'tools'); from disassemble_logic import AGIDATA, load_table; data=AGIDATA.read_bytes(); table=load_table(data,0x061d,0xb0); [print(f'{op:02x} handler={table[op].handler:04x} argc={table[op].argc} meta={table[op].meta:02x}') for op in [0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c]]"
  • python3 -B -m unittest tests.test_qemu_fixture tests.test_object_movement_probe
  • python3 -B tools/object_movement_probe.py --dos-prefix MF --output build/object-movement-probes/batches/frame_timer_modes_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case animation_mode0_forward_loop_wraps_to_frame0 --case animation_mode2_backward_completion_reaches_frame0 --case animation_mode3_backward_loop_wraps_to_frame1
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Re-read the frame-mode dispatcher and corrected the previous source note for mode 2. The branch at 0x4934 completes immediately only when the current frame is already 0; otherwise it decrements, and the shared completion path is reached only when the new frame becomes 0.
  • Added fixture helpers for action 0x48, 0x4a, 0x4b, condition var == immediate, and action 0x32 so looping frame modes can be stopped deterministically after the desired frame appears.
  • Added movement cases animation_mode0_forward_loop_wraps_to_frame0, animation_mode2_backward_completion_reaches_frame0, and animation_mode3_backward_loop_wraps_to_frame1.
  • Focused tests tests.test_qemu_fixture and tests.test_object_movement_probe passed with 37 tests.
  • QEMU batch build/object-movement-probes/batches/frame_timer_modes_002.json matched with 3 matches, 0 mismatches, and 0 errors:
    • animation_mode0_forward_loop_wraps_to_frame0: action 0x48 mode 0 wraps view 11/group 0 from frame 1 to frame 0.
    • animation_mode2_backward_completion_reaches_frame0: action 0x4b mode 2 moves from frame 1 to frame 0 and stops.
    • animation_mode3_backward_loop_wraps_to_frame1: action 0x4a mode 3 wraps backward from frame 0 to frame 1.
  • Promoted actions 0x48 and 0x4a from QEMU dispatch-smoke to behavior evidence, and extended 0x4b behavior evidence with the visible mode-2 completion probe. Regenerated docs/src/logic_opcode_evidence.md.

2026-07-03: object update-list partition actions

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B -c "import sys; sys.path.insert(0,'tools'); from disassemble_logic import AGIDATA, load_table; data=AGIDATA.read_bytes(); table=load_table(data,0x061d,0xb0); [print(f'{op:02x} handler={table[op].handler:04x} argc={table[op].argc} meta={table[op].meta:02x}') for op in range(0x36,0x3d)]"
  • rg -n "0x3a|0x3b|0x3c|clear_object_bit_0010|set_object_bit_0010|refresh_object_helper|0x0010|refresh" docs/src/logic_bytecode.md docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools tests
  • ndisasm -b 16 -o 0x6a30 -e 0x6c30 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x0440 -e 0x0640 build/cleanroom/AGI.decrypted.exe
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_logic_doc_coverage
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LR --output build/logic-interpreter-probes/batches/object_root_partition_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_0010_moves_object_behind_set_partition --case set_bit_0010_moves_object_over_clear_partition
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LR --output build/logic-interpreter-probes/batches/object_root_partition_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_0010_moves_object_behind_set_partition --case set_bit_0010_moves_object_over_clear_partition
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LR --output build/logic-interpreter-probes/batches/object_root_partition_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_0010_moves_object_behind_set_partition --case set_bit_0010_moves_object_over_clear_partition
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix LR --output build/logic-interpreter-probes/batches/object_root_partition_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_0010_moves_object_behind_set_partition --case set_bit_0010_moves_object_over_clear_partition
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Re-read action handlers 0x3a..0x3c and renamed action 0x3c from refresh_object_helper to refresh_object_lists. The handler computes the object record address from its operand, but the called helpers do not receive that address; the observed action is a global update-list refresh.
  • Added symbolic labels for the update-list wrappers and the bit-0x0010 membership helpers:
    • code.object.build_active_update_list at image 0x6a26.
    • code.object.build_inactive_partition_list at image 0x6a3d.
    • code.object.flush_update_lists_restore at image 0x6a54.
    • code.object.rebuild_draw_update_lists at image 0x6a8e.
    • code.object.refresh_update_lists at image 0x6aab.
    • code.object.clear_root_16ff_membership at image 0x6b44.
    • code.object.set_root_16ff_membership at image 0x6b62.
  • Source model: bit 0x0010 partitions active objects between root 0x16ff ((flags & 0x0051) == 0x0051) and root 0x1703 ((flags & 0x0051) == 0x0041). 0x3a clears the bit through helper 0x6b44; 0x3b sets it through helper 0x6b62; 0x3c flushes, rebuilds, draws, and refreshes both roots.
  • The first three QEMU batches were deliberately kept in the record as fixture corrections:
    • _001 placed two active objects at the same coordinates during activation; placement helper 0x593a could adjust an object before the partition effect was isolated.
    • _002 used 0x93 after activation; static re-read confirmed 0x93 calls placement helper 0x593a.
    • _003 used 0x25 after activation; the capture showed the object at both the old and new X positions because 0x25 rewrites both current and saved coordinates, so the restore pass no longer erases the old drawing.
  • Final QEMU batch build/logic-interpreter-probes/batches/object_root_partition_004.json matched with 2 matches, 0 mismatches, and 0 errors after the expected image explicitly modeled the stale 0x25 drawing as setup:
    • clear_bit_0010_moves_object_behind_set_partition: after 0x3a, the frame-1 object is drawn behind the still-bit-set frame-0 object.
    • set_bit_0010_moves_object_over_clear_partition: after 0x3a then 0x3b, the frame-1 object is drawn over a frame-0 object left in the clear partition.
  • Promoted actions 0x3a, 0x3b, and 0x3c in tools/logic_opcode_evidence.py from QEMU dispatch-smoke to QEMU behavior evidence backed by logic_interpreter_probe: object_root_partition_004, then regenerated docs/src/logic_opcode_evidence.md.

2026-07-03: object bit 0x2000 and automatic direction group selection

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0563_0620.bin bs=1 skip=1891 count=190
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_497b_49d0.bin bs=1 skip=19323 count=100
  • ndisasm -b 16 -o 0x0563 build/cleanroom/slice_0563_0620.bin
  • ndisasm -b 16 -o 0x497b build/cleanroom/slice_497b_49d0.bin
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_logic_doc_coverage
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix L2 --output build/logic-interpreter-probes/batches/object_bit_2000_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_allows_direction_group_selection --case set_bit_2000_suppresses_direction_group_selection
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix L2 --output build/logic-interpreter-probes/batches/object_bit_2000_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_allows_direction_group_selection --case set_bit_2000_suppresses_direction_group_selection

Documented result:

  • Action handler 0x2d at image 0x497b sets object bit 0x2000; handler 0x2e at image 0x49a3 clears that bit.
  • code.object.frame_timer_update at image 0x0563 tests bit 0x2000 at image 0x0593. If the bit is set, it skips automatic direction-based group selection. If the bit is clear, it may index one of two AGIDATA.OVL tables by object direction byte +0x21:
    • data.object.group_for_direction_two_or_three_groups at data 0x08dd when object byte +0x0b is 2 or 3.
    • data.object.group_for_direction_four_plus_groups at data 0x08e7 when object byte +0x0b is at least 4.
  • The helper only calls code.object.select_group (0x3bb7) when object byte +0x01 == 1, the table target is not sentinel 4, and the target differs from the current group byte +0x0a.
  • Initial QEMU batch object_bit_2000_001 used view 11 and a self-looping fixture. The first case mismatched because the persistent object still drew as group 0 frame 0. Direct comparison against rendered view-11 frames showed the original capture exactly matched group 0 frame 0, so the fixture was not exposing the per-cycle selection path.
  • The corrected fixture uses a guarded one-time initialization and a normal 0x00 end action so the engine can advance later cycles without the logic script repainting or resetting the object. It also uses view 4, whose four groups exercise the data.object.group_for_direction_four_plus_groups table.
  • Final QEMU batch object_bit_2000_002 matched with 2 matches, 0 mismatches, and 0 errors:
    • clear_bit_2000_allows_direction_group_selection: action 0x2e leaves bit 0x2000 clear; direction 6 selects view 4 group 1.
    • set_bit_2000_suppresses_direction_group_selection: action 0x2d sets bit 0x2000; the same direction leaves view 4 on group 0.
  • Promoted actions 0x2d and 0x2e in tools/logic_opcode_evidence.py to QEMU behavior evidence backed by logic_interpreter_probe: object_bit_2000_002, then regenerated docs/src/logic_opcode_evidence.md.

2026-07-03: expanded direction groups, scheduler order, and rectangle bounds

Commands run from /Users/peter/ai/agi/reverse:

  • xxd -g1 -l 32 -s 0x08dd SQ2/AGIDATA.OVL
  • xxd -g1 -l 32 -s 0x08e7 SQ2/AGIDATA.OVL
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0100_0270.bin bs=1 skip=768 count=368
  • ndisasm -b 16 -o 0x0100 build/cleanroom/slice_0100_0270.bin
  • Local Python scan for near calls to 0x0563, 0x0644, 0x150a, 0x293c, 0x6a8e, and 0x6aab.
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_logic_doc_coverage
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix L3 --output build/logic-interpreter-probes/batches/object_bit_2000_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_two_or_three_group_direction6_selects_group1 --case clear_bit_2000_two_or_three_group_direction5_is_sentinel --case clear_bit_2000_requires_field01_equal_one
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix L3 --output build/logic-interpreter-probes/batches/object_bit_2000_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_two_or_three_group_direction6_selects_group1 --case clear_bit_2000_two_or_three_group_direction5_is_sentinel --case clear_bit_2000_field01_countdown_eventually_selects_group --case clear_bit_2000_requires_field01_equal_one_when_forced
  • python3 -B -m unittest tests.test_qemu_fixture tests.test_object_movement_probe
  • python3 -B tools/object_movement_probe.py --dos-prefix RB --output build/object-movement-probes/batches/rect_bounds_clear_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_rect_boundary_clear_bounds_reaches_target

Documented result:

  • Expanded the 0x2000 direction/group probes:
    • object_bit_2000_004 matched with 4 matches, 0 mismatches, and 0 errors.
    • View 5 validates the two/three-group table at AGIDATA.OVL:0x08dd: direction 6 selects group 1.
    • Direction 5 in the same table maps to sentinel 4, so the group remains unchanged.
    • A one-shot +0x01 = 2 does not permanently block selection. The first code.object.frame_timer_update pass sees +0x01 != 1, then code.motion.update_objects decrements the countdown; a later cycle sees +0x01 == 1 and selects the direction group.
    • A per-cycle logic write that keeps +0x01 = 2 prevents the group change, confirming the exact gate.
  • Re-read the top-level cycle at image 0x0150:
    • 0x0198 calls code.motion.pre_mode_and_boundary_update at image 0x0644.
    • 0x01bd calls code.logic.call_logic (0x12ae) with logic number 0.
    • 0x024b calls code.object.frame_timer_update (0x0563) unless byte [0x1757] is nonzero.
    • code.object.frame_timer_update calls code.motion.update_objects (0x150a) at image 0x061e, then rebuilds/draws/refreshes the root 0x16ff update list.
  • Corrected symbolic labels for the pre-motion pass and rectangle helper:
    • code.motion.pre_mode_and_boundary_update is image 0x0644.
    • code.motion.rectangle_boundary_check is image 0x06d9.
  • Added QEMU movement case move_rect_boundary_clear_bounds_reaches_target. Batch rect_bounds_clear_001 matched with 1 match, 0 mismatches, and 0 errors, validating action 0x5b by setting bounds with 0x5a, clearing them with 0x5b, and observing that the object reaches (50,80) instead of stopping at the old boundary.
  • Promoted actions 0x5a and 0x5b in tools/logic_opcode_evidence.py to QEMU behavior evidence, then regenerated docs/src/logic_opcode_evidence.md.

2026-07-03: resource lifecycle, text input, menu, and sound probes

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "overlay_picture_var_composes_extra_picture|load_logic_var_then_call_logic_draws|discard_view_allows_reload" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src
  • sed -n '620,820p' tools/logic_interpreter_probe.py
  • sed -n '1,180p' tools/qemu_snapshot.py
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot tests.test_logic_doc_coverage
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix RL --output build/logic-interpreter-probes/batches/resource_lifecycle_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case load_logic_var_then_call_logic_draws --case overlay_picture_var_composes_extra_picture --case discard_picture_var_allows_reload_and_overlay --case discard_view_allows_reload_and_draw --case discard_view_var_allows_reload_and_draw
  • python3 -B -m json.tool build/logic-interpreter-probes/batches/resource_lifecycle_002.json
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix RL --output build/logic-interpreter-probes/batches/resource_lifecycle_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case load_logic_var_then_call_logic_draws --case overlay_picture_var_composes_extra_picture --case discard_picture_var_allows_reload_and_overlay --case discard_view_allows_reload_and_draw --case discard_view_var_allows_reload_and_draw
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TX --output build/logic-interpreter-probes/batches/text_input_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_message_then_ack_continues_to_draw --case display_message_var_then_ack_continues_to_draw --case display_message_configured_then_ack_continues_to_draw --case prompt_string_to_slot_accepts_typed_word --case prompt_number_to_var_accepts_digits
  • python3 -B -m json.tool build/logic-interpreter-probes/batches/text_input_001.json
  • magick build/logic-interpreter-probes/fixtures/prompt_string_to_slot_accepts_typed_word/qemu_capture.ppm build/logic-interpreter-probes/fixtures/prompt_string_to_slot_accepts_typed_word/qemu_capture.png
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TI --output build/logic-interpreter-probes/batches/text_input_prompt_string_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_string_to_slot_accepts_typed_word
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TN --output build/logic-interpreter-probes/batches/text_input_prompt_number_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_number_to_var_accepts_digits
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TX --output build/logic-interpreter-probes/batches/text_input_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_message_then_ack_continues_to_draw --case display_message_var_then_ack_continues_to_draw --case display_message_configured_then_ack_continues_to_draw --case prompt_number_to_var_accepts_digits
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix MS --output build/logic-interpreter-probes/batches/menu_sound_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_setup_dispatch_smoke --case menu_flag_dispatch_smoke --case sound_load_stop_dispatch_smoke
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Extended tools/qemu_snapshot.py so each SnapshotFixtureCase can request post_launch_keys and post_launch_wait. Existing callers keep the default no-input behavior. tools/logic_interpreter_probe.py passes those fields through from each logic case to the shared snapshot runner.
  • Extended LogicInterpreterCase and fixture generation to support additional synthetic picture resources and a separate expected_picture_payload for comparison. This lets one fixture load/overlay picture 1 while rendering the expected final picture state as picture 0 plus the overlay payload.
  • Added resource lifecycle cases:
    • load_logic_var_then_call_logic_draws validates 0x15 followed by 0x16.
    • overlay_picture_var_composes_extra_picture validates that 0x1c can overlay an already-loaded picture resource. The first QEMU run, resource_lifecycle_002, mismatched only on the overlay pixels because 0x1c updated logical picture state without a visible full-screen refresh. Adding 0x1a after 0x1c made the composed picture visible.
    • discard_picture_var_allows_reload_and_overlay validates a discard/reload/overlay path for 0x1b.
    • discard_view_allows_reload_and_draw and discard_view_var_allows_reload_and_draw validate 0x20 and 0x99 before a reload with 0x1e.
  • Final lifecycle batch resource_lifecycle_003 matched with 5 matches, 0 mismatches, and 0 errors.
  • Added message-window/input cases:
    • display_message_then_ack_continues_to_draw for 0x65.
    • display_message_var_then_ack_continues_to_draw for 0x66.
    • display_message_configured_then_ack_continues_to_draw for 0x97.
    • prompt_number_to_var_accepts_digits for 0x76, typing 42 and checking the destination variable through a conditional draw.
  • Trial case prompt_string_to_slot_accepts_typed_word for 0x73 visibly displayed WORD? and accepted typed look, but the editor remained active after Enter in the QEMU capture. It was removed from the default compatibility set until the exact completion/event path is isolated.
  • Final text/input batch text_input_002 matched with 4 matches, 0 mismatches, and 0 errors.
  • Added menu and sound smoke cases:
    • menu_setup_dispatch_smoke runs 0x9c, 0x9d, 0x9e, 0xa0, and 0x9f, then draws.
    • menu_flag_dispatch_smoke sets flag 0x0e, runs 0xa1, then draws.
    • sound_load_stop_dispatch_smoke runs 0x62 for sound 1, then 0x64, then draws.
  • Batch menu_sound_001 matched with 3 matches, 0 mismatches, and 0 errors. These are dispatch-smoke probes only; they do not claim full interactive menu selection, audio playback, or sound-completion flag semantics.
  • Regenerated docs/src/logic_opcode_evidence.md so the new rows are marked as QEMU-validated or QEMU dispatch-smoke as appropriate.

2026-07-03: string editor, text UI, and diagnostics probes

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "0x0da9|0x73|prompt_string|text_ui|diagnostics_system" docs/src tools
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0c20_10a0.bin bs=1 skip=3616 count=1152
  • ndisasm -b 16 -o 0x0c20 build/cleanroom/slice_0c20_10a0.bin
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_4420_46c0.bin bs=1 skip=17952 count=672
  • ndisasm -b 16 -o 0x4420 build/cleanroom/slice_4420_46c0.bin
  • xxd -g 1 -s 0x1060 -l 0x20 build/cleanroom/AGI.decrypted.exe
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/prompt_string_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_string_to_slot_returns_after_enter --case prompt_string_to_slot_stores_typed_word
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/prompt_string_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_string_to_slot_returns_after_enter --case prompt_string_to_slot_stores_typed_word
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/prompt_string_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_string_to_slot_returns_after_enter --case prompt_string_to_slot_stores_typed_word
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TU --output build/logic-interpreter-probes/batches/text_ui_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_formatted_message_then_ack_continues_to_draw --case display_formatted_message_var_then_ack_continues_to_draw --case display_message_configured_var_then_ack_continues_to_draw --case input_line_toggle_refresh_erase_dispatch_smoke --case text_rect_clear_dispatch_smoke --case close_text_window_state_dispatch_smoke
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TU --output build/logic-interpreter-probes/batches/text_ui_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_formatted_message_then_ack_continues_to_draw --case display_formatted_message_var_then_ack_continues_to_draw --case display_message_configured_var_then_ack_continues_to_draw --case input_line_toggle_refresh_erase_dispatch_smoke --case text_rect_clear_dispatch_smoke --case close_text_window_state_dispatch_smoke
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_clear_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_dispatch_smoke --case close_text_window_state_dispatch_smoke
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TU --output build/logic-interpreter-probes/batches/text_ui_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_formatted_message_then_ack_continues_to_draw --case display_formatted_message_var_then_ack_continues_to_draw --case display_message_configured_var_then_ack_continues_to_draw --case input_line_toggle_refresh_erase_dispatch_smoke --case text_rect_clear_dispatch_smoke --case close_text_window_state_dispatch_smoke
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix DS --output build/logic-interpreter-probes/batches/diagnostics_system_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case pause_message_then_ack_continues_to_draw --case heap_status_then_ack_continues_to_draw --case interpreter_version_then_ack_continues_to_draw --case diagnostic_global_actions_dispatch_smoke
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Re-read action 0x73 around image 0x0c44 and the shared editor helper at image 0x0da9. The handler clears fixed string slot 0x020d + slot * 0x28, optionally positions the prompt with 0x2b0d, shows the resolved message, calls the editor helper, then redraws or cleans up the prompt/status area.
  • Named code.input.edit_string at image 0x0da9. The helper clamps the requested length to 0x28, copies the destination string into a local edit buffer, displays it, waits through code.input.wait_event, and dispatches key values through the table at image/data 0x0e64.
  • The observed key dispatch table bytes at file offset 0x1060 map 0x08 to one-character backspace, 0x03 and 0x18 to clear-current-input, 0x0d to accept by zero-terminating and copying the local buffer back to the destination, and 0x1b to cancel without copying.
  • Re-read event helpers around image 0x4482..0x467f. 0x45d7 blocks until the event normalizer returns neither 0x0000 nor 0xffff; 0x4634 maps observed type-1 confirm/editor events 0x0101/0x0301 to Enter and 0x0201/0x0401 to Escape.
  • Extended the shared QEMU snapshot runner to support a post-launch key delay, a wait between typed text and named keys, and a separate list of named QEMU sendkey names. This lets fixtures type literal text and then send ret as a distinct key event.
  • Initial prompt_string_001 and prompt_string_002 runs showed the prompt text still visible in the comparison capture. The disassembly already showed Enter should accept; inspecting the output indicated that the interpreter had advanced, but the text-plane pixels remained over the later validation draw. Adding 0x1a before the validation draw removed that false mismatch.
  • Final batch prompt_string_003 matched with 2 matches, 0 mismatches, and 0 errors. It validates both return-after-Enter and copying typed look into the destination string slot for 0x73.
  • text_ui_001 failed on the same visible text-overlay issue for formatted messages. Adding a full picture refresh before the validation draw fixed the first four cases in text_ui_002.
  • The text_rect_clear_dispatch_smoke case initially mismatched because the fixture compared against the normal picture after intentionally clearing text rows. Adding a refresh before the validation draw made the probe test handler return instead of the permanent display-side clear.
  • Final batch text_ui_003 matched with 6 matches, 0 mismatches, and 0 errors. It validates 0x67, 0x68, and 0x98 as formatted/configured message paths, and dispatch-smokes 0x77, 0x78, 0x89, 0x8a, 0x69, 0x9a, and 0xa9.
  • Batch diagnostics_system_001 matched with 4 matches, 0 mismatches, and 0 errors. It validates message/ack/return behavior for 0x87, 0x88, and 0x8d, and dispatch-smokes 0x83, 0x84, 0x8e, 0xaa, 0xab, 0xac, 0xad, 0xa3, and 0xa4.
  • Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md with the new behavior and dispatch-smoke evidence rows.

2026-07-03: text/status configuration source pass and smoke probes

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • rg -n "0x6a|0x6b|0x6c|0x6d|0x6e|0x6f|0x70|0x71|0x74|0x79|0x77d5|0x78f0|0x3547|0x4c3d|0x38b4|status line|input prompt|map_key_event" docs/src tools tests
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_3400_3a00.bin bs=1 skip=13824 count=1536
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_7600_7b00.bin bs=1 skip=30720 count=1280
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_4c00_4d40.bin bs=1 skip=19968 count=320
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0d60_0df0.bin bs=1 skip=3936 count=144
  • ndisasm -b 16 -o 0x3400 build/cleanroom/slice_3400_3a00.bin
  • ndisasm -b 16 -o 0x7600 build/cleanroom/slice_7600_7b00.bin
  • ndisasm -b 16 -o 0x4c00 build/cleanroom/slice_4c00_4d40.bin
  • ndisasm -b 16 -o 0x0d60 build/cleanroom/slice_0d60_0df0.bin
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TS --output build/logic-interpreter-probes/batches/text_status_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_attribute_mode_dispatch_smoke --case screen_shake_dispatch_smoke --case input_prompt_config_dispatch_smoke --case status_line_show_hide_dispatch_smoke --case key_event_mapping_dispatch_smoke
  • python3 -B -m json.tool build/logic-interpreter-probes/batches/text_status_001.json
  • python3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/input_prompt_config_dispatch_smoke/qemu_capture.ppm
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TS --output build/logic-interpreter-probes/batches/text_status_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_attribute_mode_dispatch_smoke --case screen_shake_dispatch_smoke --case input_prompt_config_dispatch_smoke --case status_line_show_hide_dispatch_smoke --case key_event_mapping_dispatch_smoke
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Re-read status/input helpers around image 0x34bd..0x38d7:
    • code.text.redraw_status_line at image 0x34bd wraps a status redraw in text setup/cleanup helpers, tests word [0x05d9], clears the configured status row through 0x2ba6, positions output with 0x2b0d, displays text through 0x2390, and restores saved text attributes through 0x79c3.
    • Action 0x70 at image 0x3547 sets word [0x05d9] = 1 and calls the redraw helper.
    • Action 0x71 at image 0x355c clears word [0x05d9] and clears the row from [0x05db].
    • code.input.show_prompt_marker at image 0x37f7 and code.input.erase_prompt_marker at image 0x382e gate on prompt marker byte [0x05d7] and marker-visible word [0x0fa2].
    • Action 0x6c at image 0x38b4 resolves a message and stores its first byte in [0x05d7].
    • code.input.redraw_input_line at image 0x38d7 redraws the input-line area when word [0x05d3] is nonzero and display mode is not the special mode-2 path.
  • Re-read text-attribute and status configuration handlers around image 0x76ca..0x7a7f:
    • Action 0x6a sets byte [0x1757] = 1, derives attributes through 0x77d5, calls overlay entry 0x9803, then clears a text rectangle.
    • Action 0x6b calls helper 0x78cb, which clears [0x1757], recomputes attributes, calls overlay entry 0x9806, redraws the status line, and redraws the input line.
    • Action 0x6d calls code.text.set_attribute_pair (0x77d5), which stores derived values in [0x05d1], [0x05cd], and [0x05cf].
    • Action 0x6e reads a count byte and performs display-shake work through display-mode-specific helpers or direct CRT-controller writes.
    • Action 0x6f stores operand 0 in [0x05dd], operand 0 plus 0x15 in [0x05df], operand 1 in [0x05d5], operand 2 in [0x05db], and derives display offset [0x1379] from operand 0.
    • Helpers 0x7989 and 0x79c3 save and restore up to five triples of text attribute globals in the table rooted at 0x1759, with count word [0x1777].
  • Re-read action 0x79 at image 0x4c3d: it combines operand 0 and operand 1 into a little-endian key/event word, stores operand 2 as the mapped value, and inserts the pair into the first free four-byte slot in the table rooted at 0x0145, scanning up to 39 slots.
  • Re-read action 0x74 at image 0x0d70: it copies up to 0x28 bytes from a pointer read from DS:0x0c8f + operand1 * 2 into fixed string slot 0x020d + operand0 * 0x28. The local SQ2 sampled table remains zero-filled, so this action was not promoted dynamically in this pass.
  • Added five QEMU dispatch-smoke cases:
    • text_attribute_mode_dispatch_smoke for 0x6d, 0x6a, and 0x6b.
    • screen_shake_dispatch_smoke for a one-count 0x6e.
    • input_prompt_config_dispatch_smoke for 0x6c and 0x6f.
    • status_line_show_hide_dispatch_smoke for 0x70 and 0x71.
    • key_event_mapping_dispatch_smoke for 0x79.
  • Batch text_status_001 matched the first two cases, then mismatched input_prompt_config_dispatch_smoke. The mismatch bbox covered the validation sprite area, and the capture showed the interpreter had returned; the first 0x6f operand value 1 changed display offset state enough that the local expected renderer no longer aligned with the captured sprite. This is useful behavior evidence for a later dedicated 0x6f offset probe, but it was too broad for a dispatch-smoke fixture.
  • Changed the smoke fixture to use first operand 0 for 0x6f, preserving the handler dispatch while avoiding the non-default display offset.
  • Final batch text_status_002 matched with 5 matches, 0 mismatches, and 0 errors.
  • Updated symbolic labels for the status/input/text-attribute helpers and globals, promoted 0x6a..0x71 and 0x79 to QEMU dispatch-smoke evidence, and regenerated docs/src/logic_opcode_evidence.md.

2026-07-03: input offset, mapped-key, and string-table behavior probes

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "def compare_capture|expected_baseline_y|mismatch_bbox|0x1379|1379|set_input_line_config|key_event|0x79|map_key_event|input_prompt_config" tools docs/src tests
  • python3 -B -m json.tool build/logic-interpreter-probes/batches/input_prompt_config_operand1_shift_demo.json
  • Local Python comparison of the recreated operand-1 capture against expected baselines 70..90.
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0900_0a40.bin bs=1 skip=2816 count=320
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_4520_45c0.bin bs=1 skip=18208 count=160
  • ndisasm -b 16 -o 0x0900 build/cleanroom/slice_0900_0a40.bin
  • ndisasm -b 16 -o 0x4520 build/cleanroom/slice_4520_45c0.bin
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix IK --output build/logic-interpreter-probes/batches/input_key_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_config_operand1_offsets_display_by_8 --case mapped_key_sets_status_byte
  • xxd -g 1 -s 0x0c80 -l 0x80 SQ2/AGIDATA.OVL
  • xxd -g 1 -s 0x0c80 -l 0x80 build/logic-interpreter-probes/fixtures/input_line_config_operand1_offsets_display_by_8/AGIDATA.OVL
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix IK --output build/logic-interpreter-probes/batches/input_key_string_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_config_operand1_offsets_display_by_8 --case mapped_key_sets_status_byte --case set_string_from_table_copies_patched_pointer
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Quantified the earlier 0x6f(1, 0, 22) mismatch by comparing the recreated QEMU capture against expected baselines. The capture matched exactly at baseline 88, while the script draw used baseline 80. This confirms that in the observed display mode the first 0x6f operand contributes an eight-logical-row visible offset, consistent with the static assignment [0x1379] = arg0 << 3.
  • Added behavior case input_line_config_operand1_offsets_display_by_8, which runs 0x6f(1, 0, 22), refreshes with 0x1a, draws at script baseline 80, and expects the capture at baseline 88. Batch input_key_behaviour_001 matched this case.
  • Re-read condition 0x0d at image 0x09be and event mapping helper 0x4566. Condition 0x0d calls 0x459e directly and does not use the script mapping table. The top-level input helper path calls 0x4566, and when a type-1 event value matches a slot rooted at 0x0145, helper 0x4566 changes the record type to 3 and replaces the value with the mapped value.
  • The type-3 event path in the input helper writes byte [0x1218 + mapped_value] = 1. Condition 0x0c reads exactly this byte range, making it a clean observation point for a mapped-key behavior probe.
  • Added behavior case mapped_key_sets_status_byte: one-time logic installs 0x79('x', 0, 7), QEMU sends key x, and per-cycle logic draws only when condition 0x0c 7 is true. Batch input_key_behaviour_001 matched this case, validating both action 0x79 and condition 0x0c dynamically.
  • Inspected the original SQ2/AGIDATA.OVL bytes at 0x0c80..0x0cff; the pointer-table area around 0x0c8f is zero-filled through 0x0cd2, followed by static text. The same layout appears in generated fixtures.
  • Added fixture-local AGIDATA.OVL patch support to tools/logic_interpreter_probe.py.
  • Added behavior case set_string_from_table_copies_patched_pointer, which patches only the generated fixture: table entry 0 at 0x0c8f points to 0x0cc0, and 0x0cc0 contains look\0. The logic runs 0x74 into string slot 0, fills slot 1 from normal message text look, then draws only if condition 0x0f finds the two slots equal. Batch input_key_string_behaviour_001 matched this case.
  • Promoted action 0x6f, action 0x74, action 0x79, and condition 0x0c to QEMU behavior evidence in tools/logic_opcode_evidence.py, then regenerated docs/src/logic_opcode_evidence.md.

2026-07-03: inventory selection source pass and QEMU probes

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "inventory|show.obj|0x7c|post_launch|post_launch_key|dos_key" tools/logic_interpreter_probe.py tools/qemu_snapshot.py tests/test_logic_interpreter_probe.py docs/src/logic_bytecode.md docs/src/symbolic_labels.md
  • sed -n '1,220p' tools/qemu_snapshot.py
  • sed -n '960,1085p' tools/logic_interpreter_probe.py
  • sed -n '970,1030p' docs/src/logic_bytecode.md
  • ndisasm -b 16 -o 0x3180 build/cleanroom/slice_3180_33c0.bin
  • ndisasm -b 16 -o 0x9000 build/cleanroom/slice_9000_9480.bin
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot
  • Initial failed run: python3 -B tools/logic_interpreter_probe.py --dos-prefix IN --output build/logic-interpreter-probes/batches/inventory_selection_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case inventory_selection_enter_sets_var22 --case inventory_selection_escape_sets_ff --case inventory_selection_noninteractive_ack_returns
  • python3 -B -m json.tool build/logic-interpreter-probes/batches/inventory_selection_001.json
  • magick build/logic-interpreter-probes/fixtures/inventory_selection_enter_sets_var22/qemu_capture.ppm build/logic-interpreter-probes/fixtures/inventory_selection_enter_sets_var22/qemu_capture.png
  • Corrected run: python3 -B tools/logic_interpreter_probe.py --dos-prefix IN --output build/logic-interpreter-probes/batches/inventory_selection_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case inventory_selection_enter_sets_var19 --case inventory_selection_escape_sets_var19_ff --case inventory_selection_noninteractive_ack_returns
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • The inventory handler at 0x31d8 enters a text/list mode, calls a helper now labeled code.inventory.build_selection_list, restores text state, and returns. The list-building helper scans 3-byte entries rooted at data.inventory.table_root and includes only entries whose marker byte is 0xff.
  • Each displayed carried item row is an 8-byte stack-local record containing the original table index, item-name pointer, row, and column. The first item is drawn in the left column; the next item is drawn in the right column after computing 0x27 - strlen(name).
  • If no entries are carried, the helper inserts one fallback row pointing at the fixed “nothing” text.
  • Flag 13 controls interactivity. When flag 13 is clear, the handler displays the noninteractive prompt, waits through the blocking input helper, and returns without storing a selection result. When flag 13 is set, it waits for events, uses the normalizer, handles type-1 Enter/Escape, and handles type-2 movement events through a selection-move/redraw helper.
  • Enter stores the selected row’s original table index to absolute byte DS:0x0022; Escape stores 0xff to the same byte. Because the byte-variable array starts at DS:0x0009, this storage is exposed to logic bytecode as variable 0x19.
  • The first QEMU probe incorrectly checked variable 0x22. It returned after Enter but did not draw the validation sprite, producing a mismatch over the sprite area. This was retained as evidence that the source address needed to be translated through the byte-variable base.
  • Corrected QEMU batch inventory_selection_001 matched all three cases: inventory_selection_enter_sets_var19, inventory_selection_escape_sets_var19_ff, and inventory_selection_noninteractive_ack_returns.
  • The menu source pass assigned stable labels for heading allocation, item allocation, setup finalization, enable/disable-by-id, and the interactive menu path. The observed source path enqueues type-3 events with selected menu item ids for enabled items, but deterministic menu interaction probes are still pending.

2026-07-03: menu, view-resource, system, file/log, and sound follow-up probes

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_93d0_9900.bin bs=1 skip=38352 count=1328
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_5e80_60e0.bin bs=1 skip=24704 count=608
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_2470_28f0.bin bs=1 skip=9840 count=1152
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_8280_8400.bin bs=1 skip=33920 count=384
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_51d0_5280.bin bs=1 skip=21456 count=176
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_8c80_8df0.bin bs=1 skip=36480 count=368
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0e70_0ed0.bin bs=1 skip=4208 count=96
  • dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_6130_61d0.bin bs=1 skip=25392 count=160
  • ndisasm -b 16 -o 0x93d0 build/cleanroom/slice_93d0_9900.bin
  • ndisasm -b 16 -o 0x5e80 build/cleanroom/slice_5e80_60e0.bin
  • ndisasm -b 16 -o 0x2470 build/cleanroom/slice_2470_28f0.bin
  • ndisasm -b 16 -o 0x8280 build/cleanroom/slice_8280_8400.bin
  • ndisasm -b 16 -o 0x51d0 build/cleanroom/slice_51d0_5280.bin
  • ndisasm -b 16 -o 0x8c80 build/cleanroom/slice_8c80_8df0.bin
  • ndisasm -b 16 -o 0x0e70 build/cleanroom/slice_0e70_0ed0.bin
  • ndisasm -b 16 -o 0x6130 build/cleanroom/slice_6130_61d0.bin
  • rizin -q -c "/x 0x221d" -c q build/cleanroom/AGI.decrypted.exe
  • rizin -q -c "/x 0x833e221d" -c q build/cleanroom/AGI.decrypted.exe
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot
  • Initial menu run: python3 -B tools/logic_interpreter_probe.py --dos-prefix MN --output build/logic-interpreter-probes/batches/menu_interaction_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_interactive_enter_sets_status_byte
  • Final menu run: same command after adding a picture refresh before the validation draw.
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix VW --output build/logic-interpreter-probes/batches/view_resource_display_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case view_resource_display_immediate_returns --case view_resource_display_var_returns
  • Initial system run: python3 -B tools/logic_interpreter_probe.py --dos-prefix SY --output build/logic-interpreter-probes/batches/system_dialog_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case signature_check_matching_message_returns --case restart_confirm_escape_continues_to_draw --case confirm_restart_like_escape_continues_to_draw --case joystick_calibration_no_joystick_returns --case display_mode_toggle_guarded_noop_continues --case trace_window_config_enable_dispatch_smoke
  • Final system run: same command after narrowing the trace case to the flag-clear gated path.
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix FL --output build/logic-interpreter-probes/batches/file_log_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case log_file_append_dispatch_smoke --case save_game_escape_continues_to_draw --case restore_game_escape_continues_to_draw
  • Initial sound runs for sound_start_clears_completion_flag and then sound_start_stop_dispatch_smoke, both without a preceding 0x62 load.
  • Final sound run: python3 -B tools/logic_interpreter_probe.py --dos-prefix SN --output build/logic-interpreter-probes/batches/sound_completion_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case sound_start_stop_dispatch_smoke
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Corrected the slice workflow reminder: focused slices use the documented image offset plus the executable file-header adjustment (+0x200) when reading bytes from build/cleanroom/AGI.decrypted.exe.
  • code.menu.interact (0x93d1) draws the menu, waits through code.input.wait_event, normalizes the event, and calls 0x46e8. For a type-1 Enter event on an enabled item, it calls 0x44a9(3, item_id), cleans up the menu display, clears word [0x1d22], and returns.
  • The only observed direct references to menu request word [0x1d22] are the setter in 0xa1, the input/event caller check around image 0x338b, and the cleanup clear inside code.menu.interact.
  • Added menu_interactive_enter_sets_status_byte: it creates a one-item menu with item id 7, sets flag 0x0e, runs 0xa1, sends Enter, and draws only when condition 0x0c 7 observes the enqueued type-3 menu event. The first run mismatched only because the menu text strip remained visible; adding 0x1a before the validation draw produced a 1/1 QEMU match.
  • Re-read the shared view-resource display helper 0x5edb. It loads the view resource with temporary [0x0f18] = 1, builds a temporary object-like record, optionally renders/caches a preview if memory allows, displays text derived from the view resource, restores any preview rectangle, and discards the resource when it was not already cached.
  • Added view_resource_display_immediate_returns and view_resource_display_var_returns; QEMU batch view_resource_display_001 matched 2/2.
  • Added system/dialog cases for signature acceptance (0x8f), restart confirmation Escape cancellation (0x80), 0x86(0) confirmation Escape cancellation, no-joystick calibration return (0x8b), display-mode guarded no-op (0x8c with variable 0 clear), and trace configuration/flag-clear gated 0x95. QEMU batch system_dialog_001 matched 6/6 after narrowing the trace case.
  • The initial enabled trace case drew a visible trace box and mismatched the normal graphics comparison. This confirms the source-backed enabled drawing path but is not a stable sprite-comparison fixture.
  • Re-read 0x90 and helper 0x833f: the handler opens or creates logfile, seeks to the end, appends the room/input/message text, closes the handle, and returns. QEMU batch file_log_001 matched log_file_append_dispatch_smoke, save_game_escape_continues_to_draw, and restore_game_escape_continues_to_draw.
  • Re-read 0x63: it stops prior sound, stores completion flag word [0x126a], clears that flag, locates the sound resource, then starts playback. Probes that ran 0x63 without first loading sound 1 did not reach the validation draw. Adding 0x62(1) before 0x63(1,77) and then 0x64 produced a 1/1 match in sound_completion_001.
  • Promoted the newly matched opcodes in tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md. Trace/log/sound are marked according to their current evidence scope rather than overclaiming deeper side effects.

2026-07-03: priority, diagnostics, menu edges, sound flag, and log file follow-up

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x731b -e 0x751b build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x72b5 -e 0x74b5 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x828f -e 0x848f build/cleanroom/AGI.decrypted.exe
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot
  • Attempted room-switch batch: python3 -B tools/logic_interpreter_probe.py --dos-prefix RS --output build/logic-interpreter-probes/batches/room_priority_diag_sound_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_sets_room_and_previous_room --case switch_room_var_sets_room_and_previous_room --case priority_screen_enter_returns --case object_diagnostics_var_enter_returns --case sound_stop_sets_completion_flag
  • Corrected but still-failing room attempts using target-room-only and target-logic-draw assertions, also under output build/logic-interpreter-probes/batches/room_priority_diag_sound_001.json.
  • Stable priority/diagnostics/sound run: python3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/priority_diag_sound_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case priority_screen_enter_returns --case object_diagnostics_var_enter_returns --case sound_stop_sets_completion_flag
  • Initial menu edge run: python3 -B tools/logic_interpreter_probe.py --dos-prefix ME --output build/logic-interpreter-probes/batches/menu_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_escape_exits_without_status_byte --case menu_disabled_item_enter_does_not_set_status_byte --case menu_enable_after_disable_allows_enter_status_byte --case menu_down_arrow_selects_second_item_status_byte
  • Focused down-arrow retry: python3 -B tools/logic_interpreter_probe.py --dos-prefix MD --output build/logic-interpreter-probes/batches/menu_down_arrow_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_down_arrow_selects_second_item_status_byte
  • Stable menu edge run: python3 -B tools/logic_interpreter_probe.py --dos-prefix ME --output build/logic-interpreter-probes/batches/menu_edges_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_escape_exits_without_status_byte --case menu_disabled_item_enter_does_not_set_status_byte --case menu_enable_after_disable_allows_enter_status_byte
  • Log file run and extraction: python3 -B tools/logic_interpreter_probe.py --dos-prefix LF --output build/logic-interpreter-probes/batches/log_file_contents_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case log_file_append_dispatch_smoke
  • qemu-img convert -f qcow2 -O raw build/logic-interpreter-probes/snapshot/logic_interpreter.qcow2 build/logic-interpreter-probes/snapshot/logic_interpreter_after_log.raw
  • mdir -i build/logic-interpreter-probes/snapshot/logic_interpreter_after_log.raw@@32256 ::/LF00000
  • mcopy -o -i build/logic-interpreter-probes/snapshot/logic_interpreter_after_log.raw@@32256 ::/LF00000/LOGFILE build/logic-interpreter-probes/fixtures/log_file_append_dispatch_smoke/logfile_from_qemu.txt
  • xxd -g 1 build/logic-interpreter-probes/fixtures/log_file_append_dispatch_smoke/logfile_from_qemu.txt
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Reconfirmed 0x1792 room-switch helper from disassembly. It stops sound, restores heap/list state, clears active/update state for each object record, stores the target in byte variable 0, copies the previous room byte into byte variable 1, clears selected flags/bytes, loads the destination logic, handles entry-boundary placement from byte variable 2, sets flag 5, and refreshes display/input state.
  • Three QEMU fixture shapes for 0x12/0x13 were attempted and rejected as reusable evidence: direct var1 == 0 previous-room assertion, target-room var0 assertion, and target-logic draw after making the target logic self-contained. These actions remain source-backed until a fuller synthetic room-cycle fixture models the logic-0/current-room relationship.
  • Reconfirmed 0x1d at image 0x731b: it sets word [0x1755], calls full refresh 0x5546, waits for an event, refreshes again, then clears [0x1755]. QEMU case priority_screen_enter_returns matched.
  • Reconfirmed 0x85 at image 0x72b5: it reads an object index from a variable operand, gathers object fields, formats them through template 0x1713, and displays the result. QEMU case object_diagnostics_var_enter_returns matched.
  • QEMU case sound_stop_sets_completion_flag matched: after 0x62(1) and 0x63(1,77), action 0x64 sets flag 77 before the validation draw. This validates the configured completion-flag effect of the stop helper, while exact audio output and asynchronous playback lifetime remain source-backed.
  • QEMU batch priority_diag_sound_001 matched 3/3.
  • QEMU batch menu_edges_002 matched 3/3. Escape exits without setting status byte 7. Enter on disabled item 7 does not set status byte 7 before Escape exits. Disabling and then re-enabling item 7 restores Enter selection and status byte 7.
  • The attempted down-arrow menu navigation case did not reach status byte 8 even after increasing the delay between Down and Enter. It remains an attempted-but-not-promoted QEMU fixture; arrow navigation is still source-backed from the code.menu.interact event dispatch table.
  • log_file_contents_001 matched visually. Converting the post-run qcow2 image to raw and extracting LF00000\LOGFILE showed bytes 0a 0a 52 6f 6f 6d 20 30 0a 49 6e 70 75 74 20 6c 69 6e 65 3a 20 0a 4c 4f 47, which decodes as two leading newlines, Room 0, Input line: , and LOG.
  • Promoted 0x1d, 0x64, 0x85, 0x90, and the tested 0x9c..0xa0 menu setup/toggle paths in the opcode evidence generator, regenerated logic_opcode_evidence.md, and updated symbolic labels for the newly touched routines/globals.

2026-07-03: room-switch re-entry and menu-direction source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x0150 -e 0x0350 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x4529 -e 0x4729 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x46e8 -e 0x48e8 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x93d1 -e 0x95d1 build/cleanroom/AGI.decrypted.exe
  • xxd -s 0x16b3 -l 0x60 -g 2 SQ2/AGIDATA.OVL
  • xxd -s 0x0145 -l 0x80 -g 2 SQ2/AGIDATA.OVL
  • ndisasm -b 16 -o 0x10d0 -e 0x12d0 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x293c -e 0x2b3c build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x12ae -e 0x14ae build/cleanroom/AGI.decrypted.exe
  • python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow
  • Attempted room re-entry batch: python3 -B tools/logic_interpreter_probe.py --dos-prefix RV --output build/logic-interpreter-probes/batches/room_reentry_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_sets_new_room_flag --case switch_room_var_sets_new_room_flag
  • Attempted corrected room re-entry batch with 0x92 before 0x12/0x13: python3 -B tools/logic_interpreter_probe.py --dos-prefix RV --output build/logic-interpreter-probes/batches/room_reentry_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_sets_new_room_flag --case switch_room_var_sets_new_room_flag
  • Non-stopping room retry: python3 -B tools/logic_interpreter_probe.py --dos-prefix RV --output build/logic-interpreter-probes/batches/room_reentry_003.json --boot-wait 5 --draw-wait 8 --case switch_room_immediate_sets_new_room_flag --case switch_room_var_sets_new_room_flag
  • Attempted down-arrow key-map probe: python3 -B tools/logic_interpreter_probe.py --dos-prefix DK --output build/logic-interpreter-probes/batches/down_key_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case mapped_down_arrow_sets_status_byte

Documented result:

  • Re-read code.engine.main_cycle (0x0150). When code.logic.call_logic(0) returns zero, the loop clears selected variables/flags and immediately calls logic 0 again. It does not run the frame-timer branch until the logic call returns nonzero.
  • Re-read code.logic.interpret_main (0x293c). It starts execution at the current logic record’s resume pointer field [record+0x06]. Action 0x00 returns the current SI, and an action handler that returns zero stops the interpreter with AX = 0.
  • Re-read code.logic.call_logic (0x12ae). It saves the old current-logic record, locates or loads the requested logic, calls code.logic.interpret_main, frees a transiently loaded record when needed, restores the old current record, and returns the interpreter result.
  • Re-read 0x91/0x92: 0x91 writes the current bytecode pointer to [current_logic+0x06]; 0x92 restores [current_logic+0x06] from the entry pointer [current_logic+0x04].
  • A new synthetic room-switch fixture attempted to use a private init flag and flag 5 as the validation condition after 0x12/0x13. It did not reach the validation draw. Adding 0x92 before the room-switch action also did not reach the draw. The failing cases were removed from the reusable probe list; 0x12/0x13 remain source-backed.
  • Re-read input/event path 0x4529..0x46e8. Helper 0x467f drains BIOS key events, maps raw key words through table 0x16b3 via helper 0x46b6, and enqueues type-2 movement events through code.input.enqueue_event (0x44a9). The observed table maps 0x4800, 0x4900, 0x4d00, 0x5100, 0x5000, 0x4f00, 0x4b00, and 0x4700 to movement codes 1..8; code 0x5000 -> 5 is the source-backed down-arrow candidate.
  • Re-read code.menu.interact (0x93d1). Type-1 Enter/Escape are handled separately; type-2 movement values dispatch through the table at 0x9517. In that table, movement value 5 enters the branch at 0x94da, which advances to the next item in the current menu’s item list.
  • A QEMU probe that mapped raw key word 0x5000 through action 0x79 and sent monitor key down did not set the requested status byte. This is treated as an input-instrumentation gap rather than a semantic result; the down-arrow source table is still source-backed, not QEMU-validated.
  • Added symbolic labels for code.logic.save_resume_ip_action, code.logic.restore_entry_ip_action, code.input.enqueue_event, and data.input.menu_direction_event_map.

2026-07-03: real SQ2 logic-0 room dispatch pass

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/disassemble_logic.py 0
  • python3 -B tools/disassemble_logic.py 0 | rg "action (12|13|14|15|16|17|91|92)"
  • python3 -B tools/disassemble_logic.py 1 2 3 4
  • python3 -B tools/disassemble_logic.py 1 2 3 4 5 6 7 8 9 10 | rg -n "^logic|^0000: if|cond 07 flag_set\\(#5\\)|then_start|action 00 end|call_logic_var\\(v0\\)|switch_room_like"
  • python3 -B tools/disassemble_logic.py 99 100 101 103 104 105 | rg -n "^logic|code_len|switch_room_like|call_logic|call_logic_var|flag_set\\(#5\\)|save_logic_resume|restore_logic_entry| action 00 end"
  • python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow
  • Attempted logic-0-shaped room dispatch batch: python3 -B tools/logic_interpreter_probe.py --dos-prefix RD --output build/logic-interpreter-probes/batches/room_dispatch_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_then_logic0_calls_current_room --case switch_room_var_then_logic0_calls_current_room
  • xxd -g 1 -l 12 build/logic-interpreter-probes/fixtures/switch_room_immediate_then_logic0_calls_current_room/LOGDIR
  • xxd -g 1 -s 0 -l 120 build/logic-interpreter-probes/fixtures/switch_room_immediate_then_logic0_calls_current_room/VOL.3

Documented result:

  • SQ2 logic 0 is the global per-cycle script. Its early blocks handle boot/menu setup, global status/menu/input handling, and special global transitions. The room dispatch point is logic bytecode offset 0x053e, where it executes action 0x17 (call_logic_var(v0)). This calls the logic resource selected by byte variable 0.
  • Room logics sampled from resources 1 through 10 begin with an if flag 5 block. Those blocks perform room-entry setup: load views/sounds/pictures, prepare the picture, configure objects and ego, show the picture, and then end or fall through into per-cycle room behavior. Later blocks in those room logics handle room-local events and may call switch_room_like.
  • Script variables now have clearer room roles:
    • byte variable 0 at DS:0x0009 is the current room;
    • byte variable 1 at DS:0x000a is the previous room;
    • byte variable 2 at DS:0x000b is the room-entry boundary selector consumed and cleared by code.room.switch_state.
  • Built a new synthetic fixture that more closely copied the SQ2 pattern: logic 0 switched rooms once, then used call_logic_var(v0) to invoke a self-contained target room logic. The QEMU batch still mismatched with the same blank-screen signature as earlier room probes. The fixture’s LOGDIR and VOL.3 bytes were inspected and showed the extra logic resource was patched at the expected directory offset, so this failure is not explained by a missing target logic resource.
  • Removed the failed synthetic room-dispatch cases from the reusable probe registry. The source model is stronger, but 0x12/0x13 remain source-backed until a fixture can reproduce the full runtime room lifecycle.

2026-07-03: room-switch re-entry QEMU validation

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "room|switch_room|call_logic_var|restore_logic|save_resume|logic0|case|def .*logic|class|Fixture|extra_logic" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md
  • sed -n '1,260p' tools/logic_interpreter_probe.py
  • sed -n '240,620p' tools/logic_interpreter_probe.py
  • sed -n '620,980p' tools/logic_interpreter_probe.py
  • sed -n '980,1360p' tools/logic_interpreter_probe.py
  • sed -n '1360,1760p' tools/logic_interpreter_probe.py
  • sed -n '1,260p' tests/test_logic_interpreter_probe.py
  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • First attempted run, rejected because this harness does not expose a bare --snapshot flag: python3 -B tools/logic_interpreter_probe.py --snapshot --case switch_room_reentry_dispatches_current_room --case switch_room_v_reentry_dispatches_current_room --dos-prefix RS --output build/logic-interpreter-probes/batches/room_switch_reentry_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • Successful QEMU run: python3 -B tools/logic_interpreter_probe.py --case switch_room_reentry_dispatches_current_room --case switch_room_v_reentry_dispatches_current_room --dos-prefix RS --output build/logic-interpreter-probes/batches/room_switch_reentry_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B -m unittest discover -s tests
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Added two reusable logic-interpreter probe cases: switch_room_reentry_dispatches_current_room for action 0x12, and switch_room_v_reentry_dispatches_current_room for action 0x13.
  • The fixture shape is deliberately source-like. Logic 0 sets a private init flag before the switch action. The switch action returns zero, so the current interpreter invocation aborts and code.engine.main_cycle immediately calls logic 0 again. On the second pass, logic 0 skips the switch and calls call_logic_var(v0). The destination room logic checks flag 5 and performs its own picture/view load and validation draw.
  • QEMU room_switch_reentry_001 matched 2/2 with 0 mismatches. This promotes the visible room-switch re-entry/current-room dispatch shape from source-backed to QEMU-validated for both immediate and variable-selected room operands.
  • The earlier failed room-switch fixtures remain useful negative evidence about fixture shape. A validation draw after 0x12/0x13, or a destination logic that relies on pre-switch picture/view state, does not model the original runtime lifecycle.
  • Full local compatibility suite passed after adding the cases: Ran 99 tests in 18.114s, OK.
  • Regenerated docs/src/logic_opcode_evidence.md; action rows 0x12 and 0x13 now cite the matched room-switch re-entry probes. Broader internal effects of helper 0x1792, including object/resource reset, previous-room update, entry-boundary placement, and resource-event recording, remain source-backed unless separately probed.

2026-07-03: room entry-boundary selector QEMU validation

Commands run from /Users/peter/ai/agi/reverse:

  • Oversized first disassembly attempt, useful for confirming the helper but too broad for citation: ndisasm -b 16 -o 0x1792 -e 0x1992 build/cleanroom/AGI.decrypted.exe
  • Focused corrected dump of room-switch helper 0x1792: ndisasm -b 16 -o 0x1792 -e 0x1992 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'
  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • First attempted boundary batch: python3 -B tools/logic_interpreter_probe.py --case switch_room_boundary_1_sets_object0_bottom_y --case switch_room_boundary_2_sets_object0_left_x --case switch_room_boundary_3_sets_object0_top_y --case switch_room_boundary_4_sets_object0_right_x --dos-prefix RB --output build/logic-interpreter-probes/batches/room_boundary_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • sed -n '1,220p' build/logic-interpreter-probes/batches/room_boundary_001.json
  • python3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/switch_room_boundary_1_sets_object0_bottom_y/qemu_capture.ppm
  • Generated diagnostic object-0 getter case under build/logic-interpreter-probes/diagnostics/object0_getter.json.
  • Diagnostic getter run: python3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/diagnostics/object0_getter.json --dos-prefix DG --output build/logic-interpreter-probes/batches/diagnostic_object0_getter_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • Generated diagnostic marker-map case under build/logic-interpreter-probes/diagnostics/boundary1_marker_map.json.
  • Diagnostic marker-map run: python3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/diagnostics/boundary1_marker_map.json --dos-prefix DM --output build/logic-interpreter-probes/batches/diagnostic_boundary1_marker_map_001.json --boot-wait 5 --draw-wait 8
  • Corrected boundary batch: python3 -B tools/logic_interpreter_probe.py --case switch_room_boundary_1_sets_object0_bottom_y --case switch_room_boundary_2_sets_object0_left_x --case switch_room_boundary_3_sets_object0_top_y --case switch_room_boundary_4_sets_object0_right_x --dos-prefix RB --output build/logic-interpreter-probes/batches/room_boundary_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Re-read helper 0x1792: after loading the destination logic, it reads byte variable 2 at DS:0x000b, dispatches selector values 1 through 4 through a small jump table, writes object 0 fields, clears DS:0x000b, sets flag 5, and refreshes display/input state.
  • Added four reusable logic-interpreter probe cases: switch_room_boundary_1_sets_object0_bottom_y, switch_room_boundary_2_sets_object0_left_x, switch_room_boundary_3_sets_object0_top_y, and switch_room_boundary_4_sets_object0_right_x.
  • The first QEMU batch room_boundary_001 mismatched on the first case with an all-white capture. A diagnostic case proved action 0x27 can read object 0 fields after ordinary setup. The actual fixture issue was pre-switch setup: object 0 was being bound to view 11 without first loading view 11, so the fixture did not reach the intended room-switch path.
  • After changing the pre-switch setup to load view 11 before binding object 0, QEMU batch room_boundary_002 matched 4/4 with 0 mismatches.
  • The matched cases validate bytecode-visible entry-boundary behavior for action 0x12: selector 1 sets object 0 Y to 0xa7; selector 2 sets object 0 X to 0; selector 3 sets object 0 Y to 0x25; selector 4 sets object 0 X to 0xa0 - object_width. In the fixture, view 11 frame 0 has width 20, so selector 4 yields X 140. All four selectors clear byte variable 2.
  • Regenerated docs/src/logic_opcode_evidence.md; action row 0x12 now cites both the re-entry fixture and the boundary selector cases.

2026-07-03: room current/previous variable QEMU validation

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "room_reentry_logic0_code|room_boundary_logic0_code|room_switch_reentry_case|room_boundary_case|switch_room_boundary|switch_room_reentry" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src/current_status.md docs/src/logic_bytecode.md docs/src/compatibility_testing.md
  • sed -n '240,380p' tools/logic_interpreter_probe.py
  • sed -n '780,850p' tools/logic_interpreter_probe.py
  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B -m py_compile tools/logic_interpreter_probe.py
  • QEMU validation: python3 -B tools/logic_interpreter_probe.py --case switch_room_sets_current_previous_and_clears_boundary --case switch_room_v_sets_current_previous_and_clears_boundary --dos-prefix RP --output build/logic-interpreter-probes/batches/room_previous_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Added two reusable logic-interpreter probe cases: switch_room_sets_current_previous_and_clears_boundary for action 0x12, and switch_room_v_sets_current_previous_and_clears_boundary for action 0x13.
  • Each fixture writes a synthetic old room number into byte variable 0 before switching to room 1 and writes invalid boundary selector 7 into byte variable 2. The switch helper should copy old v0 into v1, write destination room 1 into v0, and clear v2 even though selector 7 is not a placement case.
  • The destination room logic validates these bytes with normal logic conditions before drawing a validation sprite.
  • QEMU batch room_previous_001 matched 2/2 with 0 mismatches. This validates the byte-variable current/previous-room update for both immediate and variable-selected room-switch actions.
  • Regenerated docs/src/logic_opcode_evidence.md; action rows 0x12 and 0x13 now cite the previous-room variable probes in addition to the existing room re-entry evidence.

2026-07-03: variable-room entry-boundary selector QEMU validation

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '240,390p' tools/logic_interpreter_probe.py
  • sed -n '780,865p' tools/logic_interpreter_probe.py
  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B -m py_compile tools/logic_interpreter_probe.py
  • QEMU validation: python3 -B tools/logic_interpreter_probe.py --case switch_room_v_boundary_1_sets_object0_bottom_y --case switch_room_v_boundary_2_sets_object0_left_x --case switch_room_v_boundary_3_sets_object0_top_y --case switch_room_v_boundary_4_sets_object0_right_x --dos-prefix VB --output build/logic-interpreter-probes/batches/room_boundary_var_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Parameterized room_boundary_case so it can use either the immediate 0x12 action or a caller-supplied variable-selected 0x13 action.
  • Added four reusable logic-interpreter probe cases: switch_room_v_boundary_1_sets_object0_bottom_y, switch_room_v_boundary_2_sets_object0_left_x, switch_room_v_boundary_3_sets_object0_top_y, and switch_room_v_boundary_4_sets_object0_right_x.
  • The variable-selected fixtures set variable 10 to destination room 1, set byte variable 2 to selector 1 through 4, then execute 0x13(v10). The destination room logic reads object 0 with action 0x27 and validates the expected position plus cleared byte variable 2 before drawing.
  • QEMU batch room_boundary_var_001 matched 4/4 with 0 mismatches. This confirms that the variable-selected room-switch action shares the same bytecode-visible entry-boundary side effects as the immediate action: selector 1 sets object 0 Y to 0xa7, selector 2 sets object 0 X to 0, selector 3 sets object 0 Y to 0x25, selector 4 sets object 0 X to 0xa0 - object_width, and all four clear v2.
  • Regenerated docs/src/logic_opcode_evidence.md; action row 0x13 now cites the variable-room boundary selector cases.

2026-07-03: room-switch persistent-object reset QEMU validation

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "expected_extra_sprites|setup_object_for_view11|activate|clear_all_object_bits|compose_frame_on_picture|extra_sprites|room_boundary_case|previous_room" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src/logic_bytecode.md
  • sed -n '390,470p' tools/logic_interpreter_probe.py
  • sed -n '1810,1828p' tools/logic_interpreter_probe.py
  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B -m py_compile tools/logic_interpreter_probe.py
  • QEMU validation: python3 -B tools/logic_interpreter_probe.py --case switch_room_removes_preexisting_persistent_object --case switch_room_v_removes_preexisting_persistent_object --dos-prefix RO --output build/logic-interpreter-probes/batches/room_object_reset_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Added reusable helper room_pre_switch_logic0_code for room-switch probes that need setup work before the switch action runs.
  • Added room_pre_switch_object_reset_case, which loads and shows picture 0, loads view 11, binds object 10 to that view, places it at X 20 / baseline Y 80, activates it as a persistent object with action 0x23, and then changes rooms.
  • Added two QEMU-backed cases: switch_room_removes_preexisting_persistent_object for immediate action 0x12, and switch_room_v_removes_preexisting_persistent_object for variable-selected action 0x13.
  • The destination room logic draws only a validation sprite. The local expected renderer therefore fails if the object activated before the switch survives into the destination room as an extra drawn sprite.
  • QEMU batch room_object_reset_001 matched 2/2 with 0 mismatches. This validates that pre-switch active persistent-object draw state is not visibly carried into the destination room for either 0x12 or 0x13.
  • This does not yet prove the contents of every object table field after the switch. It is an observable rendering compatibility fact; broader field reset details remain to be mapped from the disassembly or narrower bytecode probes.

2026-07-03: room-switch object and cache reset source correction

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,240p' docs/src/current_status.md
  • sed -n '1,220p' docs/src/symbolic_labels.md
  • rg -n "000017[0-9A-Fa-f]|000014[0-9A-Fa-f]|1792|1485|code\\.room\\.switch_state|reset_dynamic_state|room switch|room-switch" build/cleanroom/AGI.decrypted.ndisasm docs/src/*.md tools/*.py
  • Oversized exploratory disassembly reads, corrected by the focused reads below:
    • ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exe
    • ndisasm -b 16 -o 0x1485 -e 0x1685 build/cleanroom/AGI.decrypted.exe
    • ndisasm -b 16 -o 0x7060 -e 0x7260 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x10d0 -e 0x12d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x30c0 -e 0x32c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'
  • ndisasm -b 16 -o 0x4470 -e 0x4670 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x4c00 -e 0x4e00 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x3920 -e 0x3b20 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x49c0 -e 0x4bc0 build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'
  • ndisasm -b 16 -o 0x50a0 -e 0x52a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'
  • sed -n '300,380p' docs/src/graphics_object_pipeline.md
  • rg -n "\\+0x1e|\\+0x1f|\\+0x20|0x0010|0x0040|0x0001|0xffbe|0x17b6|17B6|room-switch object|object reset" docs/src tools tests
  • Compact confirmation reads:
    • ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'
    • ndisasm -b 16 -o 0x10d0 -e 0x12d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,55p'
    • ndisasm -b 16 -o 0x3920 -e 0x3b20 build/cleanroom/AGI.decrypted.exe | sed -n '30,65p'
    • ndisasm -b 16 -o 0x49c0 -e 0x4bc0 build/cleanroom/AGI.decrypted.exe | sed -n '1,35p'
    • ndisasm -b 16 -o 0x50a0 -e 0x52a0 build/cleanroom/AGI.decrypted.exe | sed -n '15,42p'
    • ndisasm -b 16 -o 0x4470 -e 0x4670 build/cleanroom/AGI.decrypted.exe | sed -n '8,28p'

Documented result:

  • Corrected the room-switch object reset description. At 0x17b6..0x17e5, code.room.switch_state iterates object records in 0x2b-byte steps, clears bits 0x0001 and 0x0040 with AND 0xffbe, sets bit 0x0010, clears pointer fields +0x10, +0x08, and +0x14, then stores byte 1 into +0x00, +0x01, +0x20, +0x1f, and +0x1e.
  • The previous prose saying +0x1e, +0x1f, and +0x20 are cleared was wrong. The instruction stream keeps AL = 1; the repeated sub ah,ah clears only the high byte and does not change AL. Those bytes are therefore seeded to 1. In the object field map they are step size, frame-timer reload, and frame-timer current countdown.
  • Refined the room-switch cache reset model. Helper 0x10d0 calls 0x10f7, 0x396d, 0x50cc, and 0x49dc. The logic helper 0x10f7 does not clear the root word [0x0977]; if the root is nonzero, it writes zero to the first logic cache record’s +0x00 next-link field. This preserves the first logic cache record and unlinks later records.
  • The remaining cache helpers clear their roots directly: view-like cache root [0x0ffa] = 0 through 0x396d, sound-like cache root [0x125a] = 0 through 0x50cc, and picture-like cache root/static record word [0x120e] = 0 through 0x49dc.
  • Added symbolic labels for code.logic.truncate_cache_to_head, code.resource.reset_room_caches, code.view.clear_cache_root, code.picture.clear_cache_root, code.sound.clear_cache_root, and code.input.reset_event_state, plus data labels for the view/picture/sound cache roots.
  • Updated docs/src/logic_bytecode.md, docs/src/logic_resources.md, docs/src/current_status.md, and docs/src/symbolic_labels.md so the high-level room-switch model distinguishes QEMU-validated visible behavior from source-backed exact memory-field effects.

2026-07-03: resource-event recording and restore replay source pass

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' docs/src/current_status.md
  • rg -n "0x70b1|0x707c|0x706d|0x681c|resource.event|resource-event|1707|1709|0141|0143|code.restore.replay_resource_events|data.resource" docs/src tools tests
  • git status --short
  • ndisasm -b 16 -o 0x7058 -e 0x7258 build/cleanroom/AGI.decrypted.exe | sed -n '1,130p'
  • ndisasm -b 16 -o 0x705e -e 0x725e build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'
  • ndisasm -b 16 -o 0x681c -e 0x6a1c build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'
  • ndisasm -b 16 -o 0x716a -e 0x736a build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'
  • ndisasm -b 16 -o 0x0000 -e 0x0200 build/cleanroom/AGI.decrypted.exe | rg "call 0x70b1|call 0x7060|call 0x706d|call 0x707c|call 0x712f|call 0x714c|call 0x681c"
  • ndisasm -b 16 -o 0x113d -e 0x133d build/cleanroom/AGI.decrypted.exe | sed -n '1,75p'
  • ndisasm -b 16 -o 0x39b1 -e 0x3bb1 build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'
  • ndisasm -b 16 -o 0x39f7 -e 0x3bf7 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x4a16 -e 0x4c16 build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'
  • ndisasm -b 16 -o 0x4a3b -e 0x4c3b build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'
  • ndisasm -b 16 -o 0x3f0d -e 0x410d build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'
  • ndisasm -b 16 -o 0x5126 -e 0x5326 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x2d52 -e 0x2f52 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x5ebf -e 0x60bf build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x1720 -e 0x1920 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'
  • ndisasm -b 16 -o 0x78f0 -e 0x7af0 build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'
  • rg -n "0x8e|0xab|0xac|load_view|prepare_picture|overlay_picture|discard_view|load_sound|resource event|event buffer|code.event|data.event" docs/src/logic_bytecode.md docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md docs/src/current_status.md docs/src/progress_log.md
  • Follow-up check for re-enable paths:
    • ndisasm -b 16 -o 0x2512 -e 0x2712 build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'
    • ndisasm -b 16 -o 0x2753 -e 0x2953 build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'
    • ndisasm -b 16 -o 0x0000 -e 0x0200 build/cleanroom/AGI.decrypted.exe | rg "call 0x705e|call 0x706d|call 0x681c"

Documented result:

  • Assigned stable labels for the resource-event helpers: code.event.disable_recording, code.event.enable_recording, code.event.reset_pair_buffer, code.event.record_pair, code.event.prepare_replay_cursor, code.event.next_replay_pair, and the action handlers for opcodes 0x8e, 0xab, and 0xac.
  • Refined the data labels for the replay log: data.event.pair_capacity ([0x0141]), data.event.pair_count ([0x0143]), data.event.saved_pair_count ([0x05e1]), data.event.pair_buffer_base ([0x1707]), data.event.pair_buffer_write ([0x1709]), data.event.pair_buffer_read ([0x170b]), data.event.recording_enabled ([0x170d]), and data.event.pair_high_water ([0x170f]).
  • The event log is a sequence of two-byte pairs (kind, value). Capacity is stored as a pair count, while the allocated byte size is capacity * 2. code.event.record_pair appends only if flag 7 is clear and data.event.recording_enabled is nonzero. It reports error code 0x0b when the write pointer reaches base + capacity * 2.
  • Room switching calls code.event.reset_pair_buffer and then code.event.enable_recording, so each new room starts with a fresh event log. Restore replay calls code.event.disable_recording before replaying saved events so the replayed operations do not append duplicate pairs.
  • Mapped restore event kinds from the dispatch table at 0x6915:
    • 0: load logic, then restore logic resume metadata through 0x13a5;
    • 1: load/refresh view through code.view.load_resource;
    • 2: load picture through code.picture.load_resource;
    • 3: load sound through code.sound.load_resource;
    • 4: prepare/decode picture through code.picture.prepare;
    • 5: replay the transient-display-object packet;
    • 6: discard picture through code.picture.discard;
    • 7: discard view through 0x3f0d;
    • 8: overlay picture through code.picture.overlay_prepare.
  • Kind 5 is a four-pair packet. Helper 0x2d52 records (5, 0), then records byte pairs from 0x0eae..0x0eb3; replay reads those next three pairs back into the same globals before calling 0x2d52.
  • Mapped event-producing resource paths:
    • 0x14/0x15 record kind 0 after loading logic through 0x117d;
    • 0x1e/0x1f record kind 1 only when creating a new cached view entry;
    • 0x18 records kind 2 only when creating a new cached picture entry;
    • 0x62 records kind 3 only when creating a new cached sound entry;
    • 0x19, 0x1c, 0x1b, 0x20, and 0x99 record kinds 4, 8, 6, and 7 through their shared helpers.
  • The temporary view-resource display helper 0x5edb, used by actions 0x81 and 0xa2, disables recording before its internal load/display sequence and re-enables recording before returning. If it loaded the view only for the display, it discards that view while recording is still disabled. This keeps temporary preview work out of the persistent restore model.
  • The restore action at 0x2512 calls replay at 0x681c and then continues through display/menu refresh helpers, but the checked caller slice does not call code.event.enable_recording. A full call-site scan found only two code.event.enable_recording calls: room switching at 0x17a3 and the temporary view-display cleanup at 0x6024. Display-mode toggle action 0x8c also calls replay at 0x797f without an observed re-enable in that immediate path. Therefore the post-replay event-recording lifecycle remains an explicit open question; the docs no longer assume automatic re-enable after restore replay.
  • Updated docs/src/logic_bytecode.md with the higher-level event-log model, and updated docs/src/symbolic_labels.md with the new code/data labels.

2026-07-03: replay save-block correction and display-mode QEMU probe

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n reads of docs/src/progress_log.md, docs/src/clean_room_executable_notes.md, docs/src/symbolic_labels.md, docs/src/logic_bytecode.md, docs/src/current_status.md, docs/src/compatibility_testing.md, and docs/src/graphics_object_pipeline.md
  • ndisasm slices around image offsets 0x2512, 0x2753, 0x681c, 0x794c, 0x00c4, and 0x821c
  • Pattern scans of build/cleanroom/AGI.decrypted.exe and build/cleanroom/AGI.decrypted.ndisasm for stores to [0x170d] and calls to code.event.disable_recording, code.event.enable_recording, and code.restore.replay_resource_events
  • python3 -B -m unittest discover -s tests
  • QEMU monitor-driven display-mode replay probes using build/logic-interpreter-probes/snapshot/logic_interpreter.qcow2, followed by info registers, memory reads, and screendump
  • python3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/display_mode_replay_skips_flag7_unrecorded_picture/manual_memory_probe.ppm

Important correction:

  • The saved whole-file disassembly uses file offsets, while MZ code image addresses are two hundredh bytes lower. For this executable, file offset = image offset + 0x200. Earlier helper slices that did not account for that relationship were plausible-looking but pointed at the wrong bytes.

Save/restore dependency map:

  • In the save action (0x2753, file offset 0x2953), helper 0x28c6 writes length-prefixed blocks. The first large state block is length 0x05e1 bytes starting at DS:0x0002, not a small block rooted at [0x05e1]. That range includes data.event.pair_capacity ([0x0141]) and data.event.pair_count ([0x0143]).
  • The active replay pair bytes are a later block whose length is [0x0141] << 1 and whose pointer is data.event.pair_buffer_base ([0x1707]).
  • data.event.recording_enabled ([0x170d]) is not part of those save blocks.
  • Restore action 0x2512 reads the same block families through helper 0x26b0, then calls code.restore.replay_resource_events.
  • Helper 0x1364 serializes logic-cache resume metadata into [0x0985] as four-byte entries containing a logic resource byte and a resume offset, terminated by word 0xffff. Helper 0x13a5 restores a loaded logic record’s resume pointer by matching the resource number and adding the saved offset to the loaded entry pointer.

Static recording-gate scan:

  • Direct stores to data.event.recording_enabled were found only in the helper bodies:
    • file 0x7263 / image 0x705e: clear to zero;
    • file 0x7272 / image 0x706d: set to one.
  • Direct calls to the enable helper were found at file 0x19a3 / image 0x17a3 (room switch) and file 0x6224 / image 0x6024 (temporary view-resource display helper).
  • Direct calls to the disable helper were found at file 0x60e3 / image 0x5ee3 (temporary view-resource display helper) and file 0x6a2a / image 0x682a (restore/display-mode replay).
  • Direct calls to code.restore.replay_resource_events were found at file 0x287a / image 0x267a (restore success path) and file 0x7b7f / image 0x797f (display-mode toggle action 0x8c).
  • This scan did not show a direct re-enable inside replay or its immediate restore/display-mode callers. The dynamic probe below proved recording was enabled again by the time the following script action recorded a transient object packet. A later source pass corrected this apparent open question: the replay dispatch table hid the post-loop call 0x706d at image 0x6927.

Display-mode replay QEMU probe:

  • The fixture patched AGIDATA.OVL words 0x112e and 0x1130 to zero and launched the game with SIERRA -p -c, so action 0x8c could pass its source guard and call replay.

  • Runtime info registers showed DS = 0x16a5, so the data segment physical base was 0x16a50.

  • Memory reads after the fixture stopped:

    • [0x112e] at physical 0x17b7e: 00 00;
    • [0x1130] at physical 0x17b80: 01 00, proving 0x8c toggled bit 0;
    • around [0x0141]/[0x0143]: bytes 00 32 00 08, meaning capacity 0x32 and active pair count 8;
    • [0x1707] = 0x4f33, [0x1709] = 0x4f43, [0x170b] = 0x4f3b, [0x170d] = 0x0001, and [0x170f] = 0x0008.
  • Pair buffer at physical DS*16 + 0x4f33 = 0x1b983:

    00 01  02 00  04 00  01 0b  05 00  0b 00  00 32  50 ff
    

    Decoded as pairs: (0,1), (2,0), (4,0), (1,11), (5,0), (11,0), (0,50), (80,255).

  • The pair buffer proves the replay log includes the room-switch logic load, picture 0 load/prepare, view 11 load, and final transient object packet. It does not include picture 1 when that picture was drawn with flag 7 set or after 0xab/0xac rolled the pair count back.

  • A fresh screenshot from the same paused VM still matched the earlier automated capture and visibly showed an alternating-row background: sha256_rgb e0f5d9669c5d1ecc326a42b28c0b517d4cdc3d1770f53ce38b49a887e1ed5123. Comparing it against the picture-0-only expectation produced 13,473 mismatches with bbox (0,1,159,167), while comparing it against the picture-1-only expectation produced 13,466 mismatches with bbox (0,0,159,166). The downsampled rows alternate: even rows are nibble 6, odd rows are nibble 4.

Documentation and harness result:

  • Added per-case launch-command support to the QEMU snapshot harness and mapped the DOS monitor key name for -, allowing logic fixtures to launch as SIERRA -p -c.
  • Added two display-mode replay fixtures: display_mode_replay_skips_flag7_unrecorded_picture and display_mode_replay_uses_rolled_back_event_count.
  • The automated screenshot expectations now reflect the original engine’s observable behavior in this fixture: the background alternates rows from the recorded and unrecorded/rolled-back pictures. The replay-log semantics are documented from source plus memory inspection rather than inferred from the screenshot.
  • The corrected QEMU batch build/logic-interpreter-probes/batches/replay_visible_001.json matched with 2 matches, 0 mismatches, and 0 errors.

2026-07-04: display-mode replay classified as CGA remapping artifact

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,280p' docs/src/current_status.md
  • rg -n "0x8c|display-mode|row-interleav|CGA|EGA|\\[0x1130\\]|0x1130|0x112e|0x2b28|0x5528|0x2b4f|0x681c|0x5685|0x9899" docs/src/logic_bytecode.md docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md docs/src/current_status.md tools tests
  • rg -n "1130|112e|1365|1379|5685|9899|99b8|9be3|9916|794c|2b28|5528|2b4f|681c" build/cleanroom/AGI.decrypted.ndisasm
  • ndisasm -b 16 -o 0x00c4 -e 0x02c4 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x40a0 -e 0x42a0 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x794c -e 0x7b4c build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x2b20 -e 0x2d20 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x5520 -e 0x5720 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x9800 SQ2/EGA_GRAF.OVL
  • ndisasm -b 16 -o 0x9800 SQ2/CGA_GRAF.OVL
  • ndisasm -b 16 -o 0x9800 SQ2/VG_GRAF.OVL
  • ndisasm -b 16 -o 0x9800 SQ2/JR_GRAF.OVL
  • xxd -g 1 -s 0x1d30 -l 0x90 SQ2/AGIDATA.OVL
  • local Python table parse of SQ2/AGIDATA.OVL bytes at 0x1d36
  • ndisasm -b 16 -o 0x4a80 -e 0x4c80 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x6440 -e 0x6640 build/cleanroom/AGI.decrypted.exe

Corrected interpretation:

  • The row-interleaved display observed after the 0x8c QEMU replay probe is a CGA-style display remapping artifact. It is not evidence that the unrecorded or rolled-back picture survives the replay.
  • The command-line parser at image 0x00c4 sets display mode word [0x1130] directly from single-letter switches: -c stores 0, -r stores 1, -e stores 3, -h stores 2, and -v stores 4. The same parser stores hardware selector [0x112e] = 0 for -p, 2 for -t, and 8 for -s.
  • Action 0x8c at image 0x794c only enters its rebuild path when [0x112e] == 0, byte variable 0 is nonzero, and [0x1130] is not 2 or 3. Therefore the fixture that launches SIERRA -p -c is intentionally forcing the hardware-0 CGA-style path; the full 16-color EGA target path is outside this handler’s active branch.
  • Picture command 0xf0 calls code.display.map_visual_color_for_adapter (0x5685) before storing the visual draw value and masks. That mapper returns the input color unchanged for [0x112e] != 0, and also returns unchanged for modes 2 and 3. It delegates to graphics-overlay entry 0x9815 only when [0x112e] == 0 and the mode is not 2 or 3.
  • In SQ2/CGA_GRAF.OVL, entry 0x9815 is a color mapper, not a mode setup entry. It indexes a three-byte-per-color table at AGIDATA.OVL:0x1d36. When [0x1130] != 1, it returns table byte 0 duplicated into AL and AH; when [0x1130] == 1, it returns the following two-byte word. For AGI color 6, the observed table bytes are 08 0b 0e.
  • In SQ2/EGA_GRAF.OVL, entry 0x9815 is instead a graphics-mode setup routine that sets BIOS mode 0x0d, configures palette/register state, and stores video segment 0xa000 in [0x1371]. The EGA target path does not use the CGA color-mapping table for picture command 0xf0.
  • Replay kind 4 calls code.picture.prepare (0x4acf), which calls code.picture.decode_with_clear (0x6445). That entry fills the logical buffer with 0x4f4f through code.display.fill_buffer_word (0x5257) before decoding the picture command stream. Combined with the QEMU memory proof that the event log excludes picture 1, this rules out the old picture as the source of the row-interleaved background.

Documentation result:

  • Updated the bytecode, graphics pipeline, compatibility, status, and symbolic label docs so the row-interleaved replay fixture is described as CGA-only adapter evidence rather than a full EGA compatibility requirement.
  • Added symbolic labels for code.display.map_visual_color_for_adapter, overlay.cga.map_visual_color_for_mode, and data.display.cga_color_map.

2026-07-04: replay recording re-enable source correction

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,240p' docs/src/current_status.md
  • tail -80 docs/src/clean_room_executable_notes.md
  • git status --short
  • An exploratory rg command containing unescaped backticks was rejected by the shell before producing useful evidence; no interpretation was derived from that failed command.
  • sed -n '4728,4905p' docs/src/clean_room_executable_notes.md
  • sed -n '60,95p' docs/src/symbolic_labels.md
  • sed -n '185,200p' docs/src/symbolic_labels.md
  • sed -n '236,270p' docs/src/runtime_model.md
  • ndisasm -b 16 -o 0x681c -e 0x6a1c build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x705e -e 0x725e build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x2512 -e 0x2712 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x794c -e 0x7b4c build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x1364 -e 0x1564 build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'
  • ndisasm -b 16 -o 0x1720 -e 0x1920 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x5ebf -e 0x60bf build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • rg -n "call 0x705e|call 0x706d|call 0x681c|mov word \\[0x170d\\]|\\[0x170d\\]" build/cleanroom/AGI.decrypted.ndisasm
  • ndisasm -b 16 -o 0x6927 -e 0x6b27 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'
  • xxd -g 1 -s 0x6b10 -l 0x30 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x6904 -e 0x6b04 build/cleanroom/AGI.decrypted.exe | sed -n '1,45p'

Correction:

  • The earlier static scan that failed to find a replay-time re-enable was a disassembly-boundary false negative, not an engine behavior. In the linear slice that starts before the event-kind dispatch table, ndisasm treats the bytes immediately after the table as data-like instructions.
  • The replay loop at code.restore.replay_resource_events (0x681c) branches to image 0x6927 when code.event.next_replay_pair (0x714c) returns zero. Disassembling at 0x6927 decodes bytes e8 43 07 as call 0x706d, which is code.event.enable_recording.
  • The event-kind dispatch table is at image 0x6915; the raw bytes around file offset 0x6b10 show the table words for handlers 0x688e, 0x689e, 0x68ab, 0x68b1, 0x68b7, 0x68bd, 0x68f2, 0x68f8, and 0x68fe, followed by e8 43 07 at file offset 0x6b27 / image 0x6927.
  • Therefore restore/display-mode replay disables recording only while replaying the saved pairs. After the pair stream ends, replay re-enables recording, then scans object records to restore saved flags, rebind view payloads, and refresh display/input state.
  • The save-block correction remains unchanged: data.event.recording_enabled is not saved as part of the length-prefixed state blocks. Restore establishes the runtime gate by replay control flow, not by reading a saved word.

Documentation result:

  • Updated logic_bytecode.md, graphics_object_pipeline.md, compatibility_testing.md, current_status.md, and symbolic_labels.md to remove the stale unresolved re-enable note.
  • Added symbolic labels for table.restore.replay_event_dispatch and code.restore.finish_replay_and_reenable_recording.

2026-07-04: raw-key predicate and focused edge-render probes

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n reads of PROGRESS.md, tools/logic_interpreter_probe.py, tests/test_logic_interpreter_probe.py, tools/logic_opcode_evidence.py, tools/object_overlay_probe.py, tests/test_object_overlay_probe.py, docs/src/logic_bytecode.md, docs/src/compatibility_testing.md, docs/src/graphics_object_pipeline.md, and docs/src/runtime_model.md
  • rg -n "raw_key|0x0d|0D|key event|keyboard|status byte|condition 0x0d|key_event|last key" docs/src/logic_bytecode.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools/logic_interpreter_probe.py
  • rg -n "def load_cases|--case|args.case|case_id" tools/logic_interpreter_probe.py
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_object_overlay_probe
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix RK --output build/logic-interpreter-probes/batches/raw_key_condition_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case raw_key_event_available_draws_after_typed_key
  • Sandboxed attempt: python3 -B tools/object_overlay_probe.py --dos-prefix OC --output build/object-overlay-probes/batches/clip_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case left_clip_view11_priority15 --case top_clip_view11_priority15
  • Escalated rerun of the same object-overlay command, after QEMU reported Failed to bind socket: Operation not permitted for its local VNC display.
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Added logic probe case raw_key_event_available_draws_after_typed_key. It installs no 0x79 key mapping, sends plain key x, and draws only when condition 0x0d succeeds. Batch raw_key_condition_001 matched QEMU with 1 match, 0 mismatches, and 0 errors, promoting condition 0x0d (raw_key_event_available) from source-backed to QEMU-validated.
  • Promoted actions 0x62 (load_sound) and 0x63 (start_sound_with_flag) from dispatch-smoke to QEMU-validated opcode-level evidence because existing QEMU case sound_stop_sets_completion_flag loads sound 1, starts it with completion flag 77, stops it with 0x64, and reaches the validation draw only after flag 77 is set. Actual audio playback and asynchronous timing remain partial.
  • Promoted actions 0xab (save_event_buffer_count) and 0xac (restore_event_buffer_count) from dispatch-smoke to QEMU-validated replay-log evidence through display_mode_replay_uses_rolled_back_event_count. The automated capture and paired memory notes show the rolled-back picture is excluded from the active pair buffer used by replay.
  • Added --case CASE_ID filtering to tools/object_overlay_probe.py, matching the existing logic-probe workflow for focused QEMU runs.
  • Added object overlay cases left_clip_view11_priority15 and top_clip_view11_priority15. The first validates view 11 flush with the left edge at left 0, baseline 80; the second revalidates the top-edge placement adjustment where requested left 20, baseline 2 matches local output at left 18, baseline 4.
  • Focused object overlay batch clip_edges_001 matched QEMU with 2 matches, 0 mismatches, and 0 errors after rerunning with permission for QEMU’s VNC bind.
  • Regenerated docs/src/logic_opcode_evidence.md from the local evidence generator. PROGRESS.md now counts 153 covered action opcodes (152 QEMU-validated plus structural 0x00), 23 partial action opcodes, and all 19 condition opcodes QEMU-validated.
  • Added implementation-facing state-machine summaries for resource lifecycle, object drawing lifecycle, and motion/animation lifecycle to docs/src/runtime_model.md.

2026-07-04: action 0x84 movement effect and text/input lifecycle model

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,380p' PROGRESS.md
  • rg -n "QEMU dispatch-smoke|source-backed|0x69|0x6a|0x6b|0x6c|0x6d|0x6e|0x70|0x71|0x77|0x78|0x83|0x84|0x89|0x8a|0x8e|0x95|0x96|0x9a|0xa3|0xa4|0xa9|0xaa|0xad" docs/src/logic_opcode_evidence.md PROGRESS.md docs/src/logic_bytecode.md
  • sed -n reads of tools/qemu_snapshot.py, tools/logic_interpreter_probe.py, tools/object_movement_probe.py, tools/qemu_fixture.py, tests/test_logic_interpreter_probe.py, tests/test_object_movement_probe.py, and the relevant docs sections.
  • Attempted 0xaa probe: python3 -B tools/logic_interpreter_probe.py --dos-prefix SD --output build/logic-interpreter-probes/batches/save_description_copy_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case copy_save_description_to_string_slot_copies_buffer
  • cat build/logic-interpreter-probes/batches/save_description_copy_001.json
  • xxd -g 1 -s 0x0e60 -l 0x60 build/logic-interpreter-probes/fixtures/copy_save_description_to_string_slot_copies_buffer/AGIDATA.OVL
  • ndisasm -b 16 -o 0x2720 -e 0x2920 build/cleanroom/AGI.decrypted.exe
  • python3 -B -m unittest tests.test_object_movement_probe tests.test_logic_interpreter_probe tests.test_qemu_fixture
  • python3 -B tools/logic_opcode_evidence.py
  • python3 -B tools/object_movement_probe.py --dos-prefix G84 --output build/object-movement-probes/batches/action_84_motion_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case action_84_after_random_motion_stops_motion

Results:

  • Attempted to promote action 0xaa (copy_save_description_to_string_slot) with a fixture-local patch that put look at AGIDATA.OVL:0x0e72, the source pointer used by the handler at image 0x2726. The QEMU run did not reach the validation draw: the capture mismatched exactly where the expected view would have been, indicating the string comparison failed. The fixture file did contain look at offset 0x0e72, so this attempt suggests runtime initialization or save-selector state controls that buffer. The case was removed from the reusable base-case registry and 0xaa remains dispatch-smoke only.
  • Added helper set_global_0139_and_clear_object0_field_22_action() for action 0x84 and object movement case action_84_after_random_motion_stops_motion. The fixture starts random motion on object 0, immediately executes 0x84, and expects the object to remain at (60,80).
  • QEMU batch action_84_motion_001 matched with 1 match, 0 mismatches, and 0 errors. This promotes the object-0 motion-byte effect of action 0x84 to QEMU-validated; the global [0x0139] = 1 side effect remains documented from source.
  • Updated tools/logic_opcode_evidence.py and regenerated docs/src/logic_opcode_evidence.md, promoting 0x84 out of dispatch-smoke.
  • Added an implementation-facing text/input UI lifecycle state machine to docs/src/runtime_model.md, tying input-line enable/disable, prompt/status configuration, modal text windows, alternate text mode, and event/edit loops to the current opcode evidence.
  • Updated PROGRESS.md: logic action opcode coverage is now 154 of 176 at [x] level (153 QEMU-validated plus structural 0x00), with 22 partial action opcodes remaining.

2026-07-04: text rectangle clear behavior probes

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' PROGRESS.md
  • sed -n '240,380p' PROGRESS.md
  • rg -n "0x69|0x6a|0x6b|0x6c|0x6d|0x70|0x71|0x77|0x78|0x83|0x89|0x8a|0x8e|0x95|0x96|0x9a|0xa3|0xa4|0xa9|0xaa|0xad" docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md tools tests
  • sed -n reads of tools/logic_interpreter_probe.py, tools/qemu_fixture.py, tests/test_logic_interpreter_probe.py, docs/src/logic_bytecode.md, and docs/src/runtime_model.md.
  • ndisasm -b 16 -o 0x34bd -e 0x36bd build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x3726 -e 0x3926 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x76ca -e 0x78ca build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x78cb -e 0x7acb build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x2bc4 -e 0x2dc4 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'
  • ndisasm -b 16 -o 0x2b78 -e 0x2d78 build/cleanroom/AGI.decrypted.exe | sed -n '1,80p'
  • python3 -B -m unittest tests.test_logic_interpreter_probe
  • python3 -B -m py_compile tools/logic_interpreter_probe.py
  • First attempted QEMU run: python3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_rect_clear_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_rows_removes_formatted_text --case text_rect_clear_bounds_removes_formatted_text
  • Corrected QEMU runs: python3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_rect_clear_behaviour_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_rows_removes_formatted_text --case text_rect_clear_bounds_removes_formatted_text and python3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_rect_clear_behaviour_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_rows_removes_formatted_text --case text_rect_clear_bounds_removes_formatted_text
  • cat build/logic-interpreter-probes/batches/text_rect_clear_behaviour_001.json
  • cat build/logic-interpreter-probes/batches/text_rect_clear_behaviour_002.json
  • python3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/text_rect_clear_bounds_removes_formatted_text/qemu_capture.ppm
  • Local one-off PPM measurement of black pixel ranges in rows 60..75 of build/logic-interpreter-probes/fixtures/text_rect_clear_bounds_removes_formatted_text/qemu_capture.ppm.
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Added LogicInterpreterCase.expected_visual_rects to model display-surface effects that are not picture-resource mutations. The comparator applies these rectangles to the low visual nibble before composing any expected view cels, preserving the priority/control nibble.
  • Added QEMU cases text_rect_clear_rows_removes_formatted_text and text_rect_clear_bounds_removes_formatted_text. Each displays formatted message text, accepts Enter, runs the clear action, and compares the capture without using 0x1a to repaint the picture.
  • The first row-clear attempt mismatched because the expected screen assumed the original white picture remained. The actual capture had a black band at logical Y 40..55, proving that 0x69(5, 6, 0) clears the visible display surface rather than restoring picture pixels.
  • After adding an expected black rectangle, text_rect_clear_behaviour_002 matched the 0x69 case but mismatched the bounded 0x9a case. Measuring the capture showed that 0x9a(8, 5, 8, 20, 0) clears logical X 20..83/Y 64..71. This validates the EGA target’s text grid as four logical pixels per text column and eight logical pixels per text row.
  • Final QEMU batch text_rect_clear_behaviour_003 matched with 2 matches, 0 mismatches, and 0 errors. Actions 0x69 (clear_text_rect) and 0x9a (clear_text_rect_bounds) were promoted from dispatch-smoke to QEMU-validated behavior coverage.
  • Added symbolic labels code.text.clear_rows (0x2b78) and code.text.clear_bounds (0x2bc4), updated the opcode/runtime docs, and regenerated docs/src/logic_opcode_evidence.md.
  • Updated PROGRESS.md: logic action opcode coverage is now 156 of 176 at [x] level (155 QEMU-validated plus structural 0x00), with 20 partial action opcodes remaining.

2026-07-04: status/input single-row clear behavior probes

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n reads of PROGRESS.md, docs/src/progress_log.md, docs/src/logic_bytecode.md, docs/src/runtime_model.md, docs/src/logic_opcode_evidence.md, docs/src/compatibility_testing.md, docs/src/symbolic_labels.md, tools/logic_interpreter_probe.py, tools/logic_opcode_evidence.py, and tests/test_logic_interpreter_probe.py.
  • rg -n "0x70|0x71|0x77|disable_input|show_status|hide_status|status_line|input_line" docs/src/clean_room_executable_notes.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/logic_opcode_evidence.md
  • ndisasm -b 16 -o 0x2ba6 -e 0x2da6 build/cleanroom/AGI.decrypted.exe | sed -n '1,60p'
  • python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectangles
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TH --output build/logic-interpreter-probes/batches/text_hide_clear_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_disable_clears_configured_row --case status_line_hide_clears_configured_row
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Re-read code.text.clear_row at image 0x2ba6. The helper pushes [bp+0xa], [bp+0x8], and [bp+0x8], then calls code.text.clear_rows (0x2b78). This makes it a single-row wrapper: top row and bottom row are identical, and the second argument is the clear attribute.
  • Added QEMU case input_line_disable_clears_configured_row. The fixture displays formatted text on row 5, acknowledges it, runs 0x6f(0, 5, 22) to set the input-row global [0x05d5], then runs 0x77. The original-engine capture matches only when logical Y 40..47 is modeled as cleared to visual color 0 before the final object draw.
  • Added QEMU case status_line_hide_clears_configured_row. The fixture displays formatted text on row 5, acknowledges it, runs 0x6f(0, 0, 5) to set the status-row global [0x05db], then runs 0x71. The capture likewise matches with logical Y 40..47 cleared to visual color 0.
  • QEMU batch text_hide_clear_behaviour_001 matched with 2 matches, 0 mismatches, and 0 errors. Actions 0x71 (hide_status_line_like) and 0x77 (disable_input_line_like) are now behavior-level QEMU-validated for the normal EGA display path.
  • Added symbolic label code.text.clear_row (0x2ba6), updated the opcode/runtime/compatibility docs, and regenerated docs/src/logic_opcode_evidence.md.
  • Updated PROGRESS.md: logic action opcode coverage is now 158 of 176 at [x] level (157 QEMU-validated plus structural 0x00), with 18 partial action opcodes remaining.

2026-07-04: input-line enable and alternate text-attribute surface probes

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n reads of PROGRESS.md, docs/src/logic_bytecode.md, docs/src/runtime_model.md, docs/src/compatibility_testing.md, docs/src/symbolic_labels.md, docs/src/clean_room_executable_notes.md, tools/logic_interpreter_probe.py, tools/logic_opcode_evidence.py, and tests/test_logic_interpreter_probe.py.
  • rg -n "0x70|0x78|0x89|0x8a|0x6c|0x6d|0xa3|0xa4|0x0d0f|0x05d3|0x05d7|0x05d9|0x0ff8|0x0fa4|0x0fce|0x38d7|0x37a5|0x3652|0x34bd" docs/src/clean_room_executable_notes.md docs/src/logic_bytecode.md docs/src/runtime_model.md tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py
  • ndisasm -b 16 -o 0x34bd -e 0x36bd build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'
  • ndisasm -b 16 -o 0x3726 -e 0x3926 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'
  • ndisasm -b 16 -o 0x38b4 -e 0x3ab4 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'
  • ndisasm -b 16 -o 0x76ca -e 0x78ca build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'
  • ndisasm -b 16 -o 0x78cb -e 0x7acb build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x77d5 -e 0x79d5 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'
  • python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectangles
  • First attempted QEMU run: python3 -B tools/logic_interpreter_probe.py --dos-prefix TE --output build/logic-interpreter-probes/batches/text_enable_attr_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_enable_clears_configured_row --case text_attribute_enable_clears_visible_surface
  • Corrected QEMU run: python3 -B tools/logic_interpreter_probe.py --dos-prefix TE --output build/logic-interpreter-probes/batches/text_enable_attr_behaviour_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_enable_clears_configured_row --case text_attribute_enable_clears_visible_surface
  • cat build/logic-interpreter-probes/batches/text_enable_attr_behaviour_001.json
  • python3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/text_attribute_enable_clears_visible_surface/qemu_capture.ppm
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Re-read action 0x78 at image 0x3898 and helper code.input.redraw_input_line at image 0x38d7. The action sets word [0x05d3] = 1; in the normal non-display-mode-2 path, the helper erases any visible prompt marker, calls code.text.clear_row (0x2ba6) for row [0x05d5] with attribute [0x05cf], positions the cursor at that row, and writes fixed string slot 0, the visible input buffer, and the prompt marker.
  • Added QEMU case input_line_enable_clears_configured_row. The fixture sets the prompt marker to an empty message, displays formatted text on row 5, acknowledges it, runs 0x6f(0, 5, 22), and then runs 0x78. The capture matches only when logical Y 40..47 is cleared to visual color 0 before the final object draw.
  • Re-read action 0x6a at image 0x76ca. The handler erases the prompt marker, sets byte [0x1757] = 1, derives text attributes through code.text.set_attribute_pair (0x77d5), calls overlay entry 0x9803, then calls code.text.clear_rows (0x2b78) for rows 0..24.
  • Added QEMU case text_attribute_enable_clears_visible_surface. The first expected model composed the usual transient-object validation draw after 0x6a, but QEMU batch text_enable_attr_behaviour_001 mismatched only in the object area. Inspecting the capture showed a single black color across the screen, so the visible surface clear was correct and the object composition expectation was wrong for this active alternate text mode.
  • Updated the probe helper with an explicit compare_view switch and changed the 0x6a case to compare only the visible surface. QEMU batch text_enable_attr_behaviour_002 then matched with 2 matches, 0 mismatches, and 0 errors.
  • Actions 0x78 (enable_input_line_like) and 0x6a (enable_text_attr_mode_1757) are now behavior-level QEMU-validated for the observed EGA paths.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, and symbolic-label docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 160 of 176 at [x] level (159 QEMU-validated plus structural 0x00), with 16 partial action opcodes remaining.

2026-07-04: prompt-marker suppression and text-attribute exit probes

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n reads of PROGRESS.md, tools/logic_interpreter_probe.py, tests/test_logic_interpreter_probe.py, tools/logic_opcode_evidence.py, docs/src/logic_bytecode.md, docs/src/compatibility_testing.md, docs/src/runtime_model.md, docs/src/symbolic_labels.md, and this notes file.
  • rg -n "0xfce|0FA4|0ff8|parse_string|set_string|input buffer|prompt_marker|0x6C|0x89|0x8A" tools/logic_interpreter_probe.py docs/src/logic_bytecode.md docs/src/clean_room_executable_notes.md docs/src/runtime_model.md tests/test_logic_interpreter_probe.py
  • ndisasm -b 16 -o 0x38b4 -e 0x39b4 build/cleanroom/AGI.decrypted.exe | sed -n '1,80p'
  • ndisasm -b 16 -o 0x37f7 -e 0x38b4 build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'
  • python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectangles
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TP --output build/logic-interpreter-probes/batches/text_prompt_attr_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_attribute_disable_restores_picture_draw --case input_prompt_empty_message_suppresses_marker
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Added QEMU case text_attribute_disable_restores_picture_draw. The fixture runs 0x6a, then 0x6b, then refreshes the picture and draws the validation object. The original-engine capture matched the normal composed object view, validating that 0x6b leaves the alternate text-attribute mode and restores ordinary picture/object drawing.
  • Added QEMU case input_prompt_empty_message_suppresses_marker. The fixture first runs 0x6c with a nonempty message, displays and acknowledges text on row 5, then runs 0x6c with an empty message before 0x6f(0, 5, 22) and 0x78. The capture matches only when the input row is black with no prompt marker glyph, validating the source-backed behavior that 0x6c stores the first byte of the resolved message and that byte zero suppresses marker drawing.
  • QEMU batch text_prompt_attr_behaviour_001 matched with 2 matches, 0 mismatches, and 0 errors.
  • Actions 0x6b (disable_text_attr_mode_1757) and 0x6c (set_input_prompt_char) are now behavior-level QEMU-validated for the focused visible effects above. Nonempty prompt-marker glyph shape remains a text-rendering detail not yet modeled.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, and symbolic-label docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 162 of 176 at [x] level (161 QEMU-validated plus structural 0x00), with 14 partial action opcodes remaining.

2026-07-04: text-attribute pair behavior probe

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n reads of PROGRESS.md, tools/logic_interpreter_probe.py, tests/test_logic_interpreter_probe.py, tools/logic_opcode_evidence.py, docs/src/logic_bytecode.md, docs/src/runtime_model.md, docs/src/compatibility_testing.md, docs/src/symbolic_labels.md, and this notes file.
  • rg -n "0x77d5|0x7803|0x78a1|0x78ad|set_attribute_pair|0x6d|0x6D" docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md docs/src/logic_bytecode.md docs/src/runtime_model.md
  • ndisasm -b 16 -o 0x77d5 -e 0x79d5 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x76ca -e 0x78ca build/cleanroom/AGI.decrypted.exe
  • python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectangles
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix TA --output build/logic-interpreter-probes/batches/text_attr_pair_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_attribute_pair_changes_attr_mode_clear_color
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Re-read action 0x6d at image 0x77af. It reads two immediate operands and calls code.text.set_attribute_pair (0x77d5).
  • Re-read code.text.set_attribute_pair at image 0x77d5. It stores [0x05d1] = helper_0x7803(arg0, arg1), [0x05cd] = helper_0x78a1(arg0), and [0x05cf] = helper_0x78ad(arg1). In normal text mode, 0x78ad returns 0xff for a nonzero argument; when byte [0x1757] is set, 0x7803 packs the pair as arg0 | (arg1 << 4).
  • Added QEMU case text_attribute_pair_changes_attr_mode_clear_color. The fixture runs 0x6d(0, 1), then 0x6a, and compares the visible surface without expecting the normal validation sprite while alternate text mode is active.
  • QEMU batch text_attr_pair_behaviour_001 matched with 1 match, 0 mismatches, and 0 errors. This validates that the stored pair is reused by 0x6a and produces a full-screen visual color 15 clear, matching packed text attribute low byte 0xf0 for the observed EGA path.
  • Promoted action 0x6d (set_text_window_pair) to behavior-level QEMU-validated evidence. Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, and symbolic-label docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 163 of 176 at [x] level (162 QEMU-validated plus structural 0x00), with 13 partial action opcodes remaining.

2026-07-04: input-line refresh/erase and status-line show probes

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n reads of PROGRESS.md, tools/logic_interpreter_probe.py, tests/test_logic_interpreter_probe.py, tools/logic_opcode_evidence.py, docs/src/logic_bytecode.md, docs/src/runtime_model.md, docs/src/compatibility_testing.md, docs/src/symbolic_labels.md, and this notes file.
  • rg -n "\\[~\\]|Highest-Value|Remaining|0x70|0x89|0x8a|0xa3|0xa4|0xa9|0xaa|0xad|0x83|0x8e|0x95|0x96|0x6e" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/clean_room_executable_notes.md tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py tools/logic_opcode_evidence.py
  • ndisasm -b 16 -o 0x3652 -e 0x3852 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x3726 -e 0x3926 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x34bd -e 0x36bd build/cleanroom/AGI.decrypted.exe
  • python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectangles tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_can_match_without_glyph_model tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_report_mismatch
  • First QEMU attempt: python3 -B tools/logic_interpreter_probe.py --dos-prefix IR --output build/logic-interpreter-probes/batches/input_refresh_status_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case status_line_show_draws_configured_row --case input_line_typed_text_visible_baseline --case input_line_erase_clears_typed_buffer --case input_line_erase_then_refresh_restores_typed_buffer
  • Capture checks: python3 -B -m json.tool build/logic-interpreter-probes/batches/input_refresh_status_001.json and python3 -B tools/inspect_ppm.py over the three input-line captures.
  • Local row-count script over the downsampled captures for input_line_typed_text_visible_baseline, input_line_erase_clears_typed_buffer, and the failed refresh case.
  • Focused unit rerun: python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_can_match_without_glyph_model tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_report_mismatch
  • Corrected QEMU input batch: python3 -B tools/logic_interpreter_probe.py --dos-prefix IR --output build/logic-interpreter-probes/batches/input_refresh_status_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_typed_text_visible_baseline --case input_line_erase_clears_typed_buffer --case input_line_refresh_repaints_entered_buffer
  • QEMU status batch: python3 -B tools/logic_interpreter_probe.py --dos-prefix ST --output build/logic-interpreter-probes/batches/status_show_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case status_line_show_draws_configured_row
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Added a narrow rectangle-check comparison mode to tools/logic_interpreter_probe.py for UI text rows whose exact font glyphs are not yet modeled by the local renderer. Existing exact full-frame comparisons remain the default.
  • Re-read helper code.input.handle_input_char (0x3652), action 0x8a (0x3726), action 0x89 (0x3753), helper code.input.append_source_to_visible (0x37a5), and status-line helper 0x34bd.
  • Added QEMU baseline input_line_typed_text_visible_baseline, which validates that typed live-edit characters produce visible color-15 pixels on the configured input row.
  • Added QEMU case input_line_erase_clears_typed_buffer. The fixture configures input row 5, sends look, then runs 0x8a every cycle. It matched only when logical Y 40..47 was black before the final object draw, validating the visible erase path for action 0x8a.
  • The first attempted refresh case assumed 0x89 would repaint unaccepted live-edit characters after 0x8a. QEMU batch input_refresh_status_001 matched the baseline and erase cases but mismatched the refresh case. Row counts showed the baseline row had color-15 glyph pixels while both the erase and failed refresh captures had zero color-15 pixels in logical Y 40..47.
  • Corrected the refresh case to type look plus Enter before checking 0x89. This matches the disassembly: Enter copies visible buffer 0x0fa4 into source buffer 0x0fce, clears visible length [0x0ff8], and redraws; the normal EGA 0x89 path then copies from 0x0fce back into 0x0fa4.
  • QEMU batch input_refresh_status_002 matched with 3 matches, 0 mismatches, and 0 errors. This promotes actions 0x89 (refresh_input_line) and 0x8a (erase_input_line) to behavior-level QEMU-validated evidence for the observed EGA path.
  • Added QEMU case status_line_show_draws_configured_row. The fixture configures status row 5 through 0x6f(0, 0, 5), runs 0x70, and checks for visible color-15 pixels in logical Y 40..47. QEMU batch status_show_001 matched with 1 match, 0 mismatches, and 0 errors, promoting action 0x70 (show_status_line_like) to behavior-level QEMU-validated evidence.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 166 of 176 at [x] level (165 QEMU-validated plus structural 0x00), with 10 partial action opcodes remaining.

2026-07-04: enabled trace-window validation

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • rg -n "trace_window|0x95|0x96|1d10|1d08|1d0a" tools/logic_interpreter_probe.py docs/src/logic_bytecode.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md PROGRESS.md
  • sed -n reads of tools/logic_interpreter_probe.py, tests/test_logic_interpreter_probe.py, docs/src/logic_bytecode.md, docs/src/symbolic_labels.md, docs/src/runtime_model.md, docs/src/compatibility_testing.md, PROGRESS.md, and this notes file.
  • ndisasm -b 16 -o 0x8c91 -e 0x8e91 build/cleanroom/AGI.decrypted.exe during the preceding trace inspection pass.
  • Focused unit tests: python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_trace_window_rect_check_tracks_source_bounds tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_can_match_without_glyph_model tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_report_mismatch
  • First enabled trace QEMU run: python3 -B tools/logic_interpreter_probe.py --dos-prefix TR --output build/logic-interpreter-probes/batches/trace_window_enable_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case trace_window_enable_draws_box_when_flag10_set
  • Capture inspection: python3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/trace_window_enable_draws_box_when_flag10_set/qemu_capture.ppm
  • Local downsample/color-count script over the same PPM capture.
  • magick build/logic-interpreter-probes/fixtures/trace_window_enable_draws_box_when_flag10_set/qemu_capture.ppm build/logic-interpreter-probes/fixtures/trace_window_enable_draws_box_when_flag10_set/qemu_capture.png
  • Stricter focused unit rerun, same unittest command as above.
  • Final enabled trace QEMU run: python3 -B tools/logic_interpreter_probe.py --dos-prefix TR --output build/logic-interpreter-probes/batches/trace_window_enable_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case trace_window_enable_draws_box_when_flag10_set
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Re-read the trace action pair from disassembly. Handler 0x95 at image 0x8c91 either consumes an extra byte when trace state [0x1d10] is already nonzero, or calls helper 0x8cae to enable the trace window only if flag 10 is set. Handler 0x96 at image 0x8d3d stores trace logic/resource, row-offset, and height globals in [0x1d12], [0x1d08], and [0x1d0a], clamping height upward to at least 2.
  • Added QEMU case trace_window_enable_draws_box_when_flag10_set. The fixture configures the base text row with 0x6f(0, 0, 5), configures the trace window with 0x96(0, 1, 2), sets flag 10, and runs 0x95 as a one-shot path so the repeated-active SI + 1 behavior cannot consume fixture bytes by accident.
  • The first enabled trace run matched a broad white-window check. Inspection of the capture showed the original engine draws a red-bordered, white-filled trace box with black text such as 0: 12(94). The downsampled capture has red border pixels around logical row 5, large white fill through the trace window, and black glyph pixels in rows 18..30.
  • Tightened the case to require all three visible signals: red border, white fill, and black trace text. QEMU batch trace_window_enable_002 matched with 1 match, 0 mismatches, and 0 errors.
  • Promoted actions 0x95 (enable_action_trace_window) and 0x96 (configure_action_trace_window) from QEMU dispatch-smoke to behavior-level QEMU-validated evidence. The older flag-clear case remains useful as gated no-draw coverage.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 168 of 176 at [x] level (167 QEMU-validated plus structural 0x00), with 8 partial action opcodes remaining.

2026-07-04: input-width flag and inactive close-window cleanup probes

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "Highest-Value|Remaining|0x6e|0x83|0x8e|0xa3|0xa4|0xa9|0xaa|0xad" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md tools/logic_interpreter_probe.py tools/logic_opcode_evidence.py
  • sed -n reads of PROGRESS.md, docs/src/logic_bytecode.md, docs/src/runtime_model.md, docs/src/symbolic_labels.md, docs/src/compatibility_testing.md, tools/logic_interpreter_probe.py, tests/test_logic_interpreter_probe.py, tools/qemu_snapshot.py, and this notes file.
  • ndisasm -b 16 -o 0x3652 -e 0x3852 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x3939 -e 0x3b39 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x1f2b -e 0x212b build/cleanroom/AGI.decrypted.exe
  • Focused unit tests: python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_input_width_flag_cases_have_distinct_row_checks tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_can_match_without_glyph_model tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_report_mismatch
  • First QEMU attempt with a visible long slot-0 prefix: python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag
  • Capture/report inspections using python3 -B tools/inspect_ppm.py and local downsample/color-count scripts over the generated input_width_flag_* fixture captures.
  • Second QEMU attempt with a long blank slot-0 prefix: python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag
  • Third QEMU attempt with the check moved to the wrapped row: python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag
  • Final QEMU batch: python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag
  • python3 -B tools/logic_opcode_evidence.py

Results:

  • Re-read code.input.handle_input_char (0x3652). At entry it computes a live-input cap in DI: if word [0x0d0f] is nonzero it uses 0x24; otherwise it computes 0x28 - strlen(0x020d), where 0x020d is fixed string slot 0. The prompt marker byte can reduce this by one, and byte [0x21] applies another cap. The printable-character path appends to visible buffer 0x0fa4 only while current visible length [0x0ff8] is below this cap.
  • Re-read action 0xa3 at 0x3939: it sets word [0x0d0f] = 1 and returns the current bytecode pointer. Action 0xa4 at 0x394b clears the same word.
  • Re-read action 0xa9 at 0x1f2b: if word [0x0d1d] is nonzero, it restores saved display rectangle [0x0d23]/[0x0d25] through helper 0x560c, then clears both [0x0d0f] and [0x0d1d]. The QEMU case below validates the unconditional [0x0d0f] clear with no active saved window; the active rectangle restore remains source-backed from this disassembly.
  • Added cases input_width_flag_a3_allows_long_live_input, input_width_flag_a4_restores_long_slot_limit, and close_text_window_state_clears_input_width_flag.
  • First attempt used a visible 38-character string slot 0. 0xa3 matched, but 0xa4 mismatched because the long prefix itself painted many white pixels on the input row, masking the accepted-input distinction.
  • Second attempt changed slot 0 to 38 spaces. This removed the visible prefix glyphs, but QEMU showed the typed characters wrapping into logical rows 48..55, not rows 40..47.
  • Third attempt checked for white pixels on the wrapped row. 0xa3 matched, but 0xa4 still mismatched because the wrapped row is blank white fill even without accepted typed glyphs.
  • Final attempt checked for black glyph pixels inside logical rows 48..55. With 0xa3, accepted typed characters create black glyph pixels in that white-filled wrapped row. With 0xa4, and with 0xa9 after 0xa3, the same row remains blank white fill with no black glyph signal.
  • QEMU batch input_width_flag_004 matched 3/3 with 0 mismatches and 0 errors. This promotes 0xa3 and 0xa4 to behavior-level QEMU evidence for the input-width flag and promotes 0xa9 for the inactive-window unconditional flag-clear side.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 171 of 176 at [x] level (170 QEMU-validated plus structural 0x00), with 5 partial action opcodes remaining.

2026-07-04: action 0x83 direction-mirror timing

Commands:

  • rg -n "0139|0x0139|clear_global_0139|set_global_0139|field_22|object0|first object" ...
  • ndisasm -b 16 -o 0x702f -e 0x722f build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x0150 -e 0x0350 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix L83 --output build/logic-interpreter-probes/batches/object0_direction_mirror_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_global_0139_allows_object0_direction_to_seed_global
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix L83 --output build/logic-interpreter-probes/batches/object0_direction_mirror_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_global_0139_allows_object0_direction_to_seed_global
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix L83 --output build/logic-interpreter-probes/batches/object0_direction_mirror_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_global_0139_allows_object0_direction_to_seed_global
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix L83 --output build/logic-interpreter-probes/batches/object0_direction_mirror_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_global_0139_allows_object0_direction_to_seed_global
  • python3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/manual_direction_value_cases.json --dos-prefix L8D --output build/logic-interpreter-probes/batches/object0_direction_value_diag_001.json --boot-wait 5 --draw-wait 8 --case diagnostic_object0_direction_value_after_0139_mirror
  • python3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/manual_direction_phase_cases.json --dos-prefix L8P --output build/logic-interpreter-probes/batches/object0_direction_phase_diag_001.json --boot-wait 5 --draw-wait 8 --case diagnostic_object0_direction_phase2_reached
  • python3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/manual_direction_active_cases.json --dos-prefix L8A --output build/logic-interpreter-probes/batches/object0_direction_active_diag_001.json --boot-wait 5 --draw-wait 8 --case diagnostic_object0_active_direction_phase2

Observations:

  • Action 0x83 at image 0x702f is a tiny handler: it stores word [0x0139] = 0 and returns the current bytecode pointer.
  • Action 0x84 at image 0x7041 stores word [0x0139] = 1 and clears byte +0x22 on the first object entry.
  • code.engine.main_cycle uses [0x0139] before logic execution. When the word is zero, it copies first-object direction byte +0x21 to global byte [0x000f]. When the word is nonzero, it copies [0x000f] back to first object byte +0x21.
  • The same main-cycle source later writes [0x000f] back to first-object +0x21 after the logic-0 call returns. This means a logic script that sets object0 byte +0x21 after the pre-logic mirror is too late to seed [0x000f] for the next cycle by itself.
  • Several attempted permanent QEMU fixtures tried to set object0 byte +0x21 to 6, execute 0x83, wait a cycle, execute 0x84, and then read byte +0x21 through 0x57. These did not validate the intended model: object0_direction_mirror_001..004 mismatched or produced no validation marker.
  • Disposable diagnostics confirmed the phase scaffolding reached phase 2, but the observed object0 direction after the sequence was 0, not 6. Activating object0 during the seed phase did not change that result.
  • Conclusion: 0x83 should be specified from source as the selector clear for the pre-logic object0/global direction mirror. A script-level QEMU fixture is not a clean validation shape because the relevant branch point happens before logic bytecode runs, and cycle-end restoration can clobber script-written object0 direction bytes.
  • Removed the attempted reusable clear_global_0139_allows_object0_direction_to_seed_global fixture from tools/logic_interpreter_probe.py after the diagnostics showed it was testing the wrong timing point.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 172 of 176 at [x] level (170 QEMU-validated, structural 0x00, and source-backed 0x83), with 4 partial action opcodes remaining.

2026-07-04: action 0xad key-release enqueue gate

Commands:

  • rg -n "0xad|increment_global_1530|1530|0x1530|Highest-Value|\\[~\\]" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md tools/logic_opcode_evidence.py tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py
  • ndisasm -b 16 -o 0x6000 -e 0x6200 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • rizin -q -c "/x 3015" -c q build/cleanroom/AGI.decrypted.exe
  • rizin -q -c "/x 1915" -c "/x 3015" -c "/x 2f15" -c q build/cleanroom/AGI.decrypted.exe
  • rg -n "44a9|code\\.event|enqueue|event queue|keyboard|raw event|type-2|type 2|0x44a9" docs/src/symbolic_labels.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/clean_room_executable_notes.md

Observations:

  • Action 0xad at image 0x602f consists of inc byte [0x1530], then returns the current bytecode pointer.
  • Local byte-pattern searches found only the action increment and the keyboard IRQ hook test of [0x1530].
  • The keyboard IRQ hook at image 0x6036 stores the raw scan byte in [0x152f], maps scan codes 0x47..0x51 to an index by subtracting 0x47, checks enable table [0x1519 + index], and tracks press/release state in [0x1524 + index].
  • On release (scan & 0x80 set), if the latch was set, the hook clears the latch and tests byte [0x1530]. If the gate is nonzero, it calls code.input.enqueue_event (0x44a9) with (type=2, value=0).
  • On press, if no latch is set for that index, the hook clears the latch table range and increments the selected [0x1524 + index] latch.
  • Conclusion: 0xad is a source-backed input/keyboard action. It increments a nonzero gate that allows selected tracked-key releases to enqueue a type-2 zero event from the interrupt hook. A QEMU fixture for this would depend on raw scan-code press/release timing and is less appropriate than direct source evidence for the current spec target.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 173 of 176 at [x] level (170 QEMU-validated, structural 0x00, and source-backed 0x83/0xad), with 3 partial action opcodes remaining.

2026-07-04: action 0x8e event-pair capacity reset

Commands:

  • rg -n "0x8e|set_global_0141|pair_capacity|0x0141|data.event.pair_capacity|reset_pair_buffer|event buffer|resource-event|replay" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md tools/logic_opcode_evidence.py tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py
  • ndisasm -b 16 -o 0x7140 -e 0x7220 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x7060 -e 0x7130 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'

Observations:

  • Action 0x8e at image 0x716a reads one immediate byte, writes it to data.event.pair_capacity ([0x0141]), calls an update-list flush helper, calls code.event.reset_pair_buffer, calls the matching update-list rebuild helper, and returns the advanced bytecode pointer.
  • code.event.reset_pair_buffer checks data.event.pair_capacity. If the capacity is positive and no pair buffer exists, it allocates capacity * 2 bytes, stores the resulting pointer in data.event.pair_buffer_base ([0x1707]), and initializes allocator state. It then sets data.event.pair_buffer_write ([0x1709]) to the base pointer and clears data.event.pair_count ([0x0143]).
  • Existing replay-source and QEMU work already validates downstream pair-log semantics for 0xab/0xac rollback and display-mode/restore replay. The 0x8e action itself is compact enough to cover from source as the capacity and reset entry point for that same log.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 174 of 176 at [x] level (170 QEMU-validated, structural 0x00, and source-backed 0x83/0x8e/0xad), with 2 partial action opcodes remaining.

2026-07-04: action 0xaa save-description buffer copy

Commands:

  • rg -n "0xaa|copy_save_description|0x0e72|0e72|save description|description buffer|select_slot|0x2726|0x4de8|0x7d|0x7e" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md tools/logic_opcode_evidence.py tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py
  • ndisasm -b 16 -o 0x2700 -e 0x2860 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x4d80 -e 0x4e40 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'

Observations:

  • Action 0xaa at image 0x2726 reads one immediate string-slot index, computes destination 0x020d + index * 0x28, and copies up to 0x1f bytes from runtime buffer [0x0e72] through the shared bounded-copy helper.
  • Save and restore handlers test byte [0x0e72] after code.save.select_slot_or_path returns, so this buffer is populated by save/restore selector state rather than by static resource data.
  • Earlier attempted QEMU fixture save_description_copy_001 patched AGIDATA.OVL bytes at offset 0x0e72 to look, then tried to compare the copied string slot against a message string. The validation draw did not occur even though the fixture file contained the bytes. This is now explained as a fixture-shape problem: action 0xaa reads the interpreter’s runtime data segment at [0x0e72], not the fixture file’s static overlay bytes.
  • Dynamic validation remains possible but should drive the real save/restore selector path that fills the runtime description buffer. The action itself is compact and is now covered as source-backed.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs.
  • Updated PROGRESS.md: logic action opcode coverage is now 175 of 176 at [x] level (170 QEMU-validated, structural 0x00, and source-backed 0x83/0x8e/0xaa/0xad), with only 0x6e partial.

2026-07-04: action 0x6e screen-shake source pass

Commands:

  • rg -n "0x6e|shake_screen|screen_shake|shake|0x6e\\b|screen-shake|display offset|0x1379|0x112e|0x1130" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md tools/logic_opcode_evidence.py tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py
  • ndisasm -b 16 -o 0x79c0 -e 0x7b60 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • rizin -q -c "/x 7a17" -c "/x 7913" -c "/x 6513" -c "/x 7917" -c q build/cleanroom/AGI.decrypted.exe

Observations:

  • Action 0x6e reads one immediate count byte into CL and advances the bytecode pointer before doing the display work.
  • Display mode [0x1130] == 3, 2, or 4 delegates to display/overlay helper paths observed at 0x99b8, 0x9be3, and 0x9916.
  • The normal path sets byte [0x1779] to 0x70 when hardware selector [0x112e] == 0, otherwise to 0x38.
  • It writes CRT controller registers via ports 0x3d4 and 0x3d5: register 0x02 receives a byte from table 0x177a plus [0x1365]; register 0x07 receives the following table byte plus [0x1779].
  • After each register-pair write, it waits for timer word [0x0129] to change. It advances through table pairs until the register-7 value returns to the base [0x1779], then repeats for the requested count.
  • Existing QEMU case screen_shake_dispatch_smoke validates that a one-count action returns to following bytecode. A screenshot-after-return fixture cannot capture the transient register animation reliably, so the timing/display effect is source-backed.
  • Updated tools/logic_opcode_evidence.py, regenerated docs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs.
  • Updated PROGRESS.md: all 176 logic action opcodes are now covered at [x] level (170 QEMU-validated, structural 0x00, and source-backed 0x6e/0x83/0x8e/0xaa/0xad).

2026-07-04: menu navigation source-table refinement

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x93d1 -e 0x95d1 build/cleanroom/AGI.decrypted.exe
  • xxd -g 2 -s 0x16b3 -l 0x50 SQ2/AGIDATA.OVL
  • sed -n '2140,2255p' tools/logic_interpreter_probe.py
  • sed -n '1088,1178p' docs/src/logic_bytecode.md

Documented result:

  • Re-read code.menu.interact (0x93d1) and its type-2 movement dispatch table at image 0x9526. The eight little-endian target words are 0x9492, 0x94a6, 0x94b2, 0x94cb, 0x94da, 0x94e5, 0x94f6, and 0x9509.
  • Confirmed from SQ2/AGIDATA.OVL that the raw movement table rooted at 0x16b3 maps BIOS-style key words 0x4800, 0x4900, 0x4d00, 0x5100, 0x5000, 0x4f00, 0x4b00, and 0x4700 to movement values 1..8.
  • Refined the menu/navigation prose: item movement branches select previous, first, last, or next item nodes directly and do not skip disabled item nodes. The item enable word is tested only by the Enter branch before enqueueing a type-3 selection event. Heading left/right movement skips disabled headings, while root/last-heading jumps select the root or root-previous heading directly.
  • Added symbolic label table.menu.navigation_dispatch and split data.menu.heading_root, data.menu.current_heading, and data.menu.current_item so future interpreter-version comparisons do not depend on the SQ2 addresses alone.

2026-07-04: menu interaction state-machine source pass

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1140,1225p' docs/src/logic_bytecode.md
  • sed -n '5800,5865p' docs/src/clean_room_executable_notes.md
  • sed -n '244,274p' docs/src/symbolic_labels.md
  • ndisasm -b 16 -o 0x93d1 -e 0x95d1 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x911d -e 0x931d build/cleanroom/AGI.decrypted.exe
  • xxd -g 2 -s 0x16b0 -l 0x60 SQ2/AGIDATA.OVL

Documented result:

  • Re-read the setup handlers at 0x911d, 0x91cf, and 0x92ba, the enable/disable helper at 0x935f, and the modal interaction loop at 0x93d1.
  • Converted the source observations into an implementation-facing menu data model in docs/src/runtime_model.md: 18-byte circular heading nodes, 14-byte circular item nodes, global root/current pointers, finalization, and the interaction request word.
  • Documented the modal interaction lifecycle. 0xa1 only requests the menu when flag 14 is set. code.menu.interact waits through the shared event helpers, treats event type 1 as Enter/Escape, treats event type 2 as movement values 1..8, and persists the current heading/item before looping.
  • Confirmed the Enter semantics from source and existing QEMU probes: enabled items enqueue a type-3 status event with the item id, disabled items continue waiting, and Escape exits without enqueueing a selection. Dynamic arrow-key validation remains a compatibility-suite gap, but the movement semantics are source-backed from table.menu.navigation_dispatch and the AGIDATA raw-key table.

2026-07-04: picture scanner command-resume fuzz expansion

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "base_0|corner|f4|f5|interleav|random|pattern|seed_fill|draw_corner" tools/picture_fuzz.py tests/test_picture_fuzz.py tests/test_graphics_rendering.py
  • sed -n '1,260p' tools/picture_fuzz.py
  • sed -n '1,430p' tests/test_graphics_rendering.py
  • python3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzz
  • python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean
  • python3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_030_line_pair_command_resume --case base_031_corner_command_resume --case base_032_fill_command_resume --dos-prefix FR --fixture-root build/picture-fuzz/fixtures --output build/picture-fuzz/batches/command_resume_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

Documented result:

  • Added three safe curated picture fuzz cases. They exercise the common coordinate/list-reader rule that a byte above 0xef terminates the active drawing command and remains pending for the scanner.
  • Added local renderer tests for an incomplete absolute-line coordinate pair terminated by command 0xf0, a Y-first corner path terminated after one segment by command 0xf0, and a seed-fill point list terminated by command 0xf0.
  • Regenerated the corpus with 1,057 cases, of which 1,055 are safe for QEMU. The two unsafe cases remain out-of-spec guardrails for over-read behavior.
  • QEMU snapshot batch command_resume_001 matched all three new cases with 0 mismatches, promoting this scanner-resume behavior to original-engine compatibility evidence.

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,280p' tools/view_batch.py
  • sed -n '1,320p' tools/picture_carousel.py
  • sed -n '1,260p' tests/test_view_batch.py
  • sed -n '1,980p' tools/qemu_fixture.py
  • sed -n '1,620p' tests/test_qemu_fixture.py
  • python3 -B -m unittest tests.test_qemu_fixture tests.test_view_batch tests.test_view_carousel
  • python3 -B tools/view_carousel.py --case view_011_normal_mid --case view_000_group1_mirrored_mid --fixture-root build/view-carousel/smoke-fixtures --dos-dir VCARSMK --output build/view-carousel/batches/view_carousel_smoke_001.json --boot-wait 5 --first-wait 3 --delay-cycles 120 --speed-value 1 --poll-interval 0.5 --poll-timeout 20
  • python3 -B tools/view_carousel.py --fixture-root build/view-carousel/base-fixtures --dos-dir VCARBASE --output build/view-carousel/batches/view_carousel_base_001.json --boot-wait 5 --first-wait 3 --delay-cycles 120 --speed-value 1 --poll-interval 0.5 --poll-timeout 20
  • python3 -B tools/view_carousel.py --include-stress --fixture-root build/view-carousel/stress-fixtures --dos-dir VCARSTR --output build/view-carousel/batches/view_carousel_stress_001.json --boot-wait 5 --first-wait 3 --delay-cycles 120 --speed-value 1 --poll-interval 0.5 --poll-timeout 20

Documented result:

  • Added view_timed_carousel_logic_payload and build_view_timed_carousel_fixture to tools/qemu_fixture.py. The fixture packs generated LOGIC.0, selected picture resources, and selected view resources into VOL.3 and patches PICDIR, VIEWDIR, and LOGDIR.
  • Added tools/view_carousel.py, a timed polling QEMU harness for picture-plus-view cases. It keeps one original-engine process running, refreshes the picture and transient object after a cycle delay, and polls screendump output until the expected local comparison matches.
  • Added local tests for the new logic payload, packed fixture layout, runner naming/report behavior, and mocked runner flow.
  • The first sandboxed QEMU attempt failed because QEMU could not bind the local VNC socket; rerunning with the approved python3 -B tools/view_carousel.py command prefix allowed the local socket bind.
  • QEMU view_carousel_smoke_001 matched two cases, view_carousel_base_001 matched all 8 current base view cases, and view_carousel_stress_001 matched all 19 current base-plus-stress cases with 0 mismatches and 0 errors from one original-engine process.

2026-07-04: picture/view runtime contract synthesis

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '230,295p' docs/src/compatibility_testing.md
  • sed -n '1008,1024p' docs/src/compatibility_testing.md
  • sed -n '1216,1230p' docs/src/compatibility_testing.md
  • sed -n '383,398p' PROGRESS.md

Documented result:

  • Added implementation-facing picture decoder lifecycle text to docs/src/runtime_model.md, covering cache selection, fresh versus overlay decode, scanner command/data behavior, draw-state channels, seed-fill contract, and display finalization.
  • Added implementation-facing view/cel drawing contract text to docs/src/runtime_model.md, covering payload layout, row runs, bit-0x80 orientation rewrite, baseline placement, priority/control gating, pixel writes, and transient versus persistent object use.
  • Updated PROGRESS.md so picture/view implementation text is no longer listed as the main renderer gap; remaining renderer work is now broader priority/control, animation, future edge probes, and cross-version/resource parity.

2026-07-04: parser wildcard and terminator probes

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/inspect_words.py --prefix look --limit 20
  • python3 -B tools/inspect_words.py --prefix around --limit 20
  • python3 -B tools/inspect_words.py --prefix get --limit 20
  • sed -n '1240,1310p' tools/logic_interpreter_probe.py
  • python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix PW --output build/logic-interpreter-probes/batches/parser_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_word_sequence_matches_two_words --case input_word_sequence_wildcard_matches_word --case input_word_sequence_terminator_accepts_prefix

Documented result:

  • Confirmed from the local WORDS.TOK decoder that look maps to word ID 0x0002 and get maps to word ID 0x0005. The word around maps to 0x0000, so it was not used as the positive edge probe.
  • Added three tools/logic_interpreter_probe.py cases. They parse message string look get with action 0x75, then test condition 0x0e for exact two-word matching, wildcard word ID 0x0001, and terminator word ID 0x270f.
  • QEMU batch parser_edges_001 matched all three cases with 0 mismatches. The runtime model now treats the wildcard and terminator behavior as QEMU-backed, not merely source-backed.

2026-07-04: object placement spiral source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x593a -e 0x5b3a build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x56b8 -e 0x58b8 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • rg -n '0x593a|0x56b8|placement|right-edge|right edge|baseline 67|baseline_y=67|expected_baseline_y=67|place' docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools/object_overlay_probe.py tests/test_object_overlay_probe.py
  • Local simulation of the 0x593a movement sequence for view 11/group 0/frame 0 at requested placements (20, 2), (0, 80), and (154, 80).
  • python3 -B -m unittest tests.test_graphics_rendering tests.test_object_overlay_probe
  • python3 -B -m unittest tests.test_view_batch
  • Compared existing QEMU captures for top_clip_view11_priority15 and right_clip_view11_priority15 against the updated local model.

Documented result:

  • Re-read placement helper code.object.place (0x593a). It tests the initial object position with bounds helper 0x5a14, object collision helper 0x4719, and control/priority acceptance helper 0x56b8. If the position fails, it searches in a widening spiral.
  • The source movement sequence is left 1, down 1, right 2, up 2, left 3, down 3, right 4, up 4, and so on. The candidate is tested before each move.
  • Added search_object_placement() and placement_bounds_ok() to tools/agi_graphics.py for the bounds-only portion of this source model. The helper reproduces the previously QEMU-observed top-edge placement (18, 4) and right-edge placement (140, 67) for view 11/group 0/frame 0, and reproduces horizon clamping to [0x012d] + 1 when bit 0x0008 is not modeled as set.
  • Updated tools/object_overlay_probe.py so ordinary top/right expected positions are derived from the placement search instead of hard-coded in the case registry. Existing QEMU captures for both edge cases still compare with 0 mismatches.
  • Updated object-pipeline, compatibility, symbolic-label, progress-tracker, and unit-test coverage. Collision/control rejection can extend the search beyond the first bounds-acceptable candidate, so those cases remain future work.

2026-07-04: picture seed-fill span-stack source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x533b -e 0x533b build/cleanroom/AGI.decrypted.exe
  • sed -n '185,220p' docs/src/graphics_object_pipeline.md
  • sed -n '770,790p' docs/src/compatibility_testing.md
  • sed -n '968,984p' docs/src/compatibility_testing.md
  • sed -n '88,118p' docs/src/symbolic_labels.md
  • sed -n '2415,2490p' docs/src/clean_room_executable_notes.md

Rejected or limited evidence:

  • The broad disassembly command above printed far past the seed-fill helper, so only the known helper body around code.picture.seed_fill and the pre-existing focused seed-fill notes were used as evidence for this pass.

Documented result:

  • Re-read the seed-fill helper enough to make the implementation-facing contract sharper. The helper chooses exactly one expansion test channel per seed: visual low nibble first when visual drawing is active, otherwise control high nibble when control drawing is active.
  • Confirmed the early exits: no active drawing channel, selected visual value equal to the visual default target, selected control value equal to the control default target, or a seed cell that does not match the selected default target.
  • Confirmed that accepted pixels still use the normal active draw byte and odd/even masks, so both logical nibbles can change even though only one channel controls expansion.
  • Documented the SQ2 traversal class as a stack-backed horizontal span fill. It fills the current row left/right, records span state in the scratch block around 0x126c..0x1279, scans adjacent rows in a current vertical direction, pushes deferred branch spans on the CPU stack, reverses direction when needed, and terminates after popping the sentinel row state.
  • Added conservative symbolic label data.picture.seed_fill_span_scratch instead of naming each byte in the scratch block prematurely.
  • Updated compatibility notes to say the remaining seed-fill work is broadened parity coverage for barriers, odd/even masks, and multi-seed cases, not unknown traversal class.

2026-07-04: seed-fill fuzz case expansion

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "base_0|visual_fill|seed|fill|cases|safe_for_qemu" tools/picture_fuzz.py tests/test_picture_fuzz.py tests/test_graphics_rendering.py
  • sed -n '1,260p' tests/test_picture_fuzz.py
  • sed -n '1,260p' tools/picture_fuzz.py
  • sed -n '1,240p' tools/agi_graphics.py
  • sed -n '115,160p' tests/test_graphics_rendering.py
  • python3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzz
  • python3 -B tools/picture_fuzz.py generate --count 8 --seed 4097 --output build/picture-fuzz/seed-fill-cases --clean
  • python3 -B tools/picture_fuzz.py batch-qemu --snapshot --corpus build/picture-fuzz/seed-fill-cases --fixture-root build/picture-fuzz/seed-fill-fixtures --case base_021_visual_fill_full_height_barrier --case base_022_visual_fill_multi_seed_boxes --case base_023_control_fill_ignores_visual_barrier --dos-prefix SF --output build/picture-fuzz/batches/seed_fill_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • cat build/picture-fuzz/batches/seed_fill_edges_001.json
  • python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean

Rejected or limited evidence:

  • The first QEMU batch attempt failed before any interpreter behavior was observed because sandboxed QEMU could not bind 127.0.0.1:5 for VNC. The same command was rerun with approved escalation and produced the evidence result below.

Documented result:

  • Added safe curated fuzz cases:
    • base_021_visual_fill_full_height_barrier: a full-height one-pixel visual barrier blocks a visual seed fill.
    • base_022_visual_fill_multi_seed_boxes: one 0xf8 command contains two seed pairs and fills two isolated boxed regions.
    • base_023_control_fill_ignores_visual_barrier: a control-channel fill crosses a visual-only barrier because the selected expansion channel is control, while the visible barrier remains undisturbed.
  • Added local renderer assertions for all three cases. The tests check final cell values rather than only screenshot hashes, including the control-channel crossing in base_023.
  • Regenerated a small corpus; it reported 32 cases total, with 30 marked safe for QEMU. The standard 1,024-random corpus command reports 1,048 cases total and 1,046 safe cases.
  • The QEMU snapshot batch seed_fill_edges_001 matched the local renderer: 3 matches, 0 mismatches, 0 errors, with each comparison covering 26,880 logical pixels.

2026-07-04: optional view stress batch expansion

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "mirror|mirroring|transparent|cel|view" docs/src/graphics_object_pipeline.md docs/src/compatibility_testing.md PROGRESS.md tests/test_graphics_rendering.py tools/agi_graphics.py tools/view_batch.py
  • sed -n '390,620p' tools/agi_graphics.py
  • sed -n '520,620p' docs/src/graphics_object_pipeline.md
  • Local Python corpus scan over VIEWDIR frames to count frames, mirror-bit frames, transparent-color representatives, and largest cels.
  • Local Python validation that selected stress cases fit within the screen at their chosen placements.
  • python3 -B -m unittest tests.test_view_batch tests.test_graphics_rendering
  • python3 -B tools/view_batch.py --snapshot --include-stress --dos-prefix VXS --output build/view-batch/batches/view_stress_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • cat build/view-batch/batches/view_stress_001.json

Documented result:

  • Added stress_cases() to tools/view_batch.py and exposed it through optional CLI flag --include-stress. The default six-case view batch remains unchanged for quick smoke runs.
  • The stress suite adds eleven cases selected from the local SQ2 view corpus: large cels, the 129-row tall cel, transparent colors 0, 1, 2, 5, 6, 7, 8, 10, 13, 14, and 15, and a bit-0x80 transparent-10 frame.
  • The local unit tests confirm that stress cases are optional and that the stress placements fit on screen.
  • The QEMU snapshot run view_stress_001 covered 17 cases total: the six existing base cases plus the eleven stress cases. All 17 matched with 0 mismatches and 0 errors, each over 26,880 logical pixels.

2026-07-04: pattern and interleaved picture fuzz expansion

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "pattern|0x10|0x20|pattern_mode|base_02" tests/test_graphics_rendering.py tools/picture_fuzz.py docs/src/graphics_object_pipeline.md docs/src/compatibility_testing.md
  • sed -n '245,270p' docs/src/graphics_object_pipeline.md
  • sed -n '250,278p' tests/test_graphics_rendering.py
  • python3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzz
  • python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean
  • python3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_024_pattern_bypass_mask --case base_025_interleaved_line_fill_pattern --case base_026_pattern_random_bypass_sequence --dos-prefix PF --output build/picture-fuzz/batches/pattern_interleaved_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • cat build/picture-fuzz/batches/pattern_interleaved_001.json

Documented result:

  • Added safe curated picture fuzz cases:
    • base_024_pattern_bypass_mask: isolates pattern mode bit 0x10 bypassing the row/column mask test.
    • base_025_interleaved_line_fill_pattern: draws a rectangle outline, fills it, draws a line through it, and overlays a pattern plot in one valid picture stream.
    • base_026_pattern_random_bypass_sequence: uses both mode bits 0x10 and 0x20 across two pseudo-random pattern plots.
  • Added local renderer assertions for the first two cases. The mask-bypass test checks the expected 4-by-7 filled footprint for radius 3, and the interleaved test checks that later line and pattern commands overwrite earlier fill results sequentially.
  • Regenerated the standard fuzz corpus. It now reports 1,051 cases total and 1,049 safe for QEMU.
  • The QEMU snapshot batch pattern_interleaved_001 matched the local renderer: 3 matches, 0 mismatches, 0 errors, with each comparison covering 26,880 logical pixels.

2026-07-04: placement-search predicate hook clarification

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "0x4719|0x56b8|collision|control rejection|placement search|spiral|code.object.place|code.object.*collision|control-buffer" docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools/agi_graphics.py tests/test_graphics_rendering.py tools/object_overlay_probe.py tests/test_object_overlay_probe.py
  • ndisasm -b 16 -o 0x4719 -e 0x4919 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x56b8 -e 0x58b8 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • sed -n '700,866p' docs/src/graphics_object_pipeline.md

Rejected or limited evidence:

  • The first disassembly rerun used image addresses as file offsets and landed in the wrong window. The corrected commands above add the MZ header offset when skipping into the file and show the expected helper bodies.

Documented result:

  • Reconfirmed from code.object.place and its callees that every placement candidate is tested in this order: bounds/horizon helper 0x5a14, collision helper 0x4719, and control-buffer acceptance helper 0x56b8.
  • Added a docstring to search_object_placement() explaining that its optional accept predicate models the two non-bounds predicates.
  • Added a local regression test that rejects the first four otherwise-valid candidates (20,80), (19,80), (19,81), and (20,81). The helper then returns (21,81), matching the source spiral order and showing how collision/control rejection extends the search without changing movement order.
  • Added symbolic label code.object.collision_test for helper 0x4719 so both placement predicates have stable cross-version names.

2026-07-04: text/input tracker audit

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "dispatch-smoke|text window|input line|prompt|status line|close_text|clear_text|trace window|0x69|0x70|0xa3|0xa4|0xa9|text/input|Text windows" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/compatibility_testing.md tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py
  • ndisasm -b 16 -o 0x1f2b -e 0x212b build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • rg -n "0x0d1d|0x0d23|0x0d25|0x560c|close_text_window|saved rectangle|text window" docs/src/clean_room_executable_notes.md docs/src/logic_bytecode.md docs/src/runtime_model.md tools/logic_interpreter_probe.py
  • rg -n "0x79|set_input|input row|key_event_mapping|status_line_show_hide|input_line_erase|0x8a|0x6f" docs/src/logic_bytecode.md docs/src/compatibility_testing.md docs/src/runtime_model.md tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py

Documented result:

  • Audited the current text/input coverage against PROGRESS.md. The broad “promote dispatch-smoke rows” wording was stale: focused QEMU cases already cover prompt marker behavior, status/input row show-hide/clear, input refresh/erase, mapped-key and raw-key paths, text attribute mode entry/exit, and the input-width flag effects of 0xa3, 0xa4, and inactive 0xa9.
  • Re-read 0xa9 at 0x1f2b: it conditionally calls 0x560c([0x0d23], [0x0d25]) only when [0x0d1d] is nonzero, then clears [0x0d0f] and [0x0d1d].
  • Updated PROGRESS.md to name the remaining text/input gap more precisely: active saved-window restore for 0xa9, plus non-EGA text paths only if they become necessary for SQ2 behavior.

2026-07-04: sound resource format source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x50d8 -e 0x52d8 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x7f96 -e 0x8196 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • Local Python scans over SQ2/SNDDIR and the referenced VOL.* payloads.
  • python3 -B -m unittest tests.test_sound_resources

Documented result:

  • code.sound.find_loaded_resource starts at 0x50d8.
  • Action 0x62 enters code.sound.load_resource at 0x5126. On cache miss, it records resource event (3, resource), resolves the sound directory entry through 0x440d, reads the payload through the generic volume reader 0x2e32, stores the raw payload pointer at cache-record +0x04, then reads four little-endian words from the payload and stores derived payload-relative channel pointers at record offsets +0x06, +0x08, +0x0a, and +0x0c.
  • Action 0x63 stores/clears the completion flag, locates the already loaded sound record through 0x50d8, and calls code.sound.driver_start at 0x7f96.
  • code.sound.driver_start copies the four cached channel pointers into data 0x1788..0x178f, initializes the four countdown words at 0x1790..0x1797 to 1, initializes per-channel state words, and sets active-state word [0x1258] = 1.
  • The playback tick reads channel records as duration u16; 0xffff terminates a channel. Otherwise it reads a 16-bit tone/control word followed by one control byte and uses the low nibble as the observed attenuation/control value.
  • Added tools/agi_sound.py with a deterministic parser for the observed sound payload shape and tests/test_sound_resources.py to scan all present SQ2 sound resources.
  • The targeted sound test passed: 49 present sound resources; every present payload has four sorted in-bounds channel offsets with first offset 8; every channel parses to an in-payload terminator. Sound 1 has offsets (8, 15, 22, 29), channel 0 first event (duration=0x0027, tone_word=0x8037, control_byte=0x9f), and channel 0 terminator offset 13.
  • This is source-backed resource-format evidence. Audible pitch, timing, and hardware-driver output remain provisional.

2026-07-04: sound playback tick scheduling source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x7f60 -e 0x8160 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x8160 -e 0x8360 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x74c0 -e 0x76c0 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x83a0 -e 0x85a0 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x84f0 -e 0x86f0 build/cleanroom/AGI.decrypted.exe
  • Local Python scans over parsed SQ2 sound events and completion ticks.
  • python3 -B -m unittest tests.test_sound_resources

Documented result:

  • code.sound.driver_start chooses the active channel set from hardware selector [0x112e]: selector values 0 and 8 set data.sound.active_channel_byte_limit = 2 and data.sound.remaining_active_channels = 1, so only channel 0 is advanced. Other observed selector values set the byte limit to 8 and the remaining count to 4, advancing channel offsets 0, 2, 4, and 6.
  • code.sound.driver_tick starts by testing flag 9 through code.flags.test at 0x7502. If flag 9 is clear, it calls the low-level stop/completion path immediately.
  • The timer interrupt hook at 0x8521 calls code.sound.driver_tick only when data.sound.active_state is nonzero, then either acknowledges the interrupt or chains to the original timer interrupt every third hook call through byte 0x184f.
  • Every channel countdown is initialized to 1, so the first event or terminator is consumed on the first active tick. After an event is consumed, its duration word is stored as the next 16-bit countdown. A duration of zero would wrap and delay the next channel record read for 65,536 ticks.
  • The local SQ2 corpus contains 3,619 parsed sound events. The minimum duration is 1, the maximum is 688, and no present event uses duration zero.
  • Added source-backed scheduling helpers to tools/agi_sound.py: active_sound_channel_indices, schedule_sound_channel, and sound_completion_tick.
  • Expanded tests/test_sound_resources.py from four to nine tests. New checks validate the one-channel/four-channel selector rule, sound 1’s tick-40 natural termination, sound 60’s differing one-channel and four-channel completion ticks (3403 and 3404), the synthetic zero-duration wrap, and immediate first-tick completion when flag 9 is clear.
  • The targeted sound-resource tests passed: 9 tests in tests.test_sound_resources. Hardware pitch, attenuation envelopes, and port-level output remain provisional.

2026-07-04: active text-window restore source pass

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "0d1d|0D1D|0d23|0D23|0d25|0D25|560c|5590|text window|saved-window|saved window|close_text" build/cleanroom docs/src tools tests
  • ndisasm -b 16 -o 0x5500 -e 0x5700 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x1cc0 -e 0x1ec0 build/cleanroom/AGI.decrypted.exe

Documented result:

  • code.text.display_string at 0x1ce8 calls the modal message-window setup helper at 0x1d96, waits for the relevant acknowledgement/event path, and later calls code.text.close_window_state at 0x1f2b with argument zero on the normal close path.
  • code.text.display_message_window at 0x1d96 first checks word [0x0d1d]. If a saved window is already active, it calls code.text.close_window_state before building the next window. This prevents stacking multiple saved rectangles in the observed modal-message path.
  • The same opener formats/copies the current message text through helper 0x1f54, derives text-window row/column and size words from the text metrics and configuration globals, computes packed rectangle words [0x0d23] and [0x0d25], then calls helper 0x5590 with those words and attribute 0x040f.
  • Helper 0x5590 is the boxed-window draw/save helper. It delegates the actual surface save/fill/draw operations to overlay/helper calls around 0x9812. After that call returns, the opener sets [0x0d1d] = 1, prints the formatted text, refreshes text/input areas, and sets [0x0d0f] = 1.
  • code.text.close_window_state at 0x1f2b tests [0x0d1d]; when nonzero it calls helper 0x560c([0x0d23], [0x0d25]). Helper 0x560c loads those packed rectangle words and delegates to overlay restore helper 0x980c.
  • After the conditional restore, code.text.close_window_state always clears [0x0d0f] and [0x0d1d]. The existing QEMU probe validates the inactive unconditional [0x0d0f] clear; the active saved-rectangle lifecycle is now source-backed by both the producer at 0x1d96 and consumer at 0x1f2b.

2026-07-04: save-file selector and block-envelope source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x2500 -e 0x2700 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x2700 -e 0x2900 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x85e5 -e 0x87e5 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x8814 -e 0x8a14 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x8a80 -e 0x8c80 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x8794 -e 0x8994 build/cleanroom/AGI.decrypted.exe | sed -n '1,110p'
  • ndisasm -b 16 -o 0x8b9f -e 0x8d9f build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'
  • xxd -g 1 -s 0x1860 -l 0x400 SQ2/AGIDATA.OVL
  • Local Python scan over SQ2/SQ2SG.* save files.
  • python3 -B -m unittest tests.test_save_resources

Documented result:

  • Rechecked the save and restore handlers at 0x2753 and 0x2512. The save block table in earlier notes had the first write reversed. Source argument order and the local save files show that the first length-prefixed block is 0x05e1 bytes from data address 0x0002, not two bytes from 0x05e1.
  • The five save-file blocks written by code.save.write_length_prefixed_block and read by code.save.read_length_prefixed_block are:
    • 0x05e1 bytes from/to 0x0002.
    • [0x096f] bytes from/to [0x096b].
    • [0x0975] bytes from/to [0x0971].
    • [0x0141] * 2 bytes from/to [0x1707].
    • 0x1364() bytes from/to 0x0985.
  • The checked-in local SQ2 saves SQ2/SQ2SG.1 through SQ2/SQ2SG.11 all parse as a 31-byte description/header followed by five little-endian length-prefixed blocks. The first four block lengths are fixed in this local corpus: 1505, 903, 328, and 200. The fifth block is present and variable-sized (16..28 bytes observed).
  • Added tools/agi_save.py, a narrow parser for the source-backed save-file envelope. Added tests/test_save_resources.py; the targeted test run passed four tests and checks all local SQ2 save files, description extraction, truncated-block rejection, and trailing-byte rejection.
  • Refined code.save.select_slot_or_path at 0x85e5. It saves prompt-marker visibility, erases the marker, saves/restores text state, stops active sound, sets a text attribute pair, delegates path prompting, scans selectable slots, formats the selected filename into 0x1c8c, and returns zero for cancel/no selection.
  • Labeled selector subhelpers:
    • code.save.check_drive_or_path_available at 0x86a3.
    • code.save.prompt_path_if_needed at 0x8705.
    • code.save.edit_modal_text_field at 0x8794.
    • code.save.select_numbered_slot at 0x8814.
    • code.save.read_slot_summary at 0x8b9f.
  • code.save.prompt_path_if_needed displays the save or restore path prompt when [0x0e72] is empty, edits path buffer 0x1962, and validates it through the generic path validator at 0x5bdd.
  • code.save.select_numbered_slot scans up to 12 numbered save files, displays descriptions, marks the current row with glyph 0x1a, clears the old row with a space, accepts Enter, cancels Escape, and handles movement events 1 and 5 as up/down with wrap.
  • In save mode, accepting an empty-description slot calls code.save.edit_modal_text_field with prompt text at 0x1baa and fills the 31-byte header/description buffer at 0x1c6c before the save handler creates the file.

2026-07-04: heap allocation and mark/rewind source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x1300 -e 0x1500 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x1480 -e 0x1680 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x0f80 -e 0x1180 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • Source/local searches over heap globals 0x0a55, 0x0a57, 0x0a59, 0x0a5b, 0x0a5d, and 0x0a5f in the disassembly notes, docs, tools, and tests.

Documented result:

  • The interpreter heap is source-backed as a bump allocator with mark/rewind cleanup. No general free-list behavior has been observed in the allocator helpers.
  • code.heap.allocate at 0x13d6 computes available bytes as [0x0a5b] - [0x0a55]. If the requested size is larger, it formats the out-of-memory message at 0x09fd, displays it through the text helper, and calls restart/exit helper 0x02ae. No recoverable allocation-failure return was observed.
  • On successful allocation, 0x13d6 returns the old current heap pointer, advances [0x0a55] by the requested size, calls code.heap.update_free_memory_var at 0x14a0, and updates high-water pointer [0x0a5f] when the new current top exceeds the prior high-water value.
  • code.heap.current_top at 0x1430 returns [0x0a55]. code.heap.rewind_to at 0x143c stores a caller-provided pointer in [0x0a55] without refreshing the free-memory byte.
  • code.heap.save_temporary_mark at 0x144b stores [0x0a55] in [0x0a5d]. code.heap.restore_temporary_mark at 0x145a rewinds to [0x0a5d] only when that mark is nonzero, then clears the mark.
  • Startup calls code.heap.save_room_reset_mark at 0x1476 after initial object/inventory setup and logic 0 load. code.heap.reset_dynamic_state at 0x1485, used by room switch, restart, and restore paths, frees update-list nodes, clears the temporary mark, restores [0x0a55] from [0x0a59], and refreshes the free-memory byte through 0x14a0.
  • code.heap.update_free_memory_var computes [0x0a5b] - [0x0a55], stores the high byte in byte variable [0x0011], and returns the full free-byte count.
  • code.heap.show_status_action at 0x14bd formats heap diagnostics from the same globals: heap size [0x0a5b] - [0x0a57], current use [0x0a55] - [0x0a57], maximum use [0x0a5f] - [0x0a57], room/reset mark [0x0a59] - [0x0a57], and resource-event high-water [0x170f].

2026-07-04: restart, restore-failure, and shutdown cleanup source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x02ae -e 0x04ae build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'
  • ndisasm -b 16 -o 0x0240 -e 0x0440 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'
  • ndisasm -b 16 -o 0x2460 -e 0x2660 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x2500 -e 0x2700 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'
  • ndisasm -b 16 -o 0x2700 -e 0x2900 build/cleanroom/AGI.decrypted.exe | sed -n '80,260p'
  • ndisasm -b 16 -o 0x8240 -e 0x8440 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x8380 -e 0x8580 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x5a20 -e 0x5c20 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'
  • ndisasm -b 16 -o 0x0f80 -e 0x1180 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • Source/document searches for 0x80, 0x86, 0x02ae, 0x8275, restore-success/failure paths, interrupt-vector cleanup, and log-file handle cleanup.

Addressing note:

  • A first exploratory ndisasm command used -e too broadly. ndisasm -e is a file skip, not an end address. The documented slices above use the project’s existing convention: display image offset with -o IMAGE, and skip to the corresponding EXE file offset with -e IMAGE+0x200.

Documented result:

  • code.restart.confirm_restart_action at image 0x2472 is an in-engine restart, not DOS process termination. It stops sound, clears the prompt/input line, tests flag 16 to optionally skip the confirmation prompt, and only enters the reset block when the confirmation result is nonzero.
  • On accepted restart, 0x2472 erases visible input, saves the current flag 9 state, calls code.heap.reset_dynamic_state (0x1485), calls code.restart.initialize_game_tables (0x0fa5), refreshes display/list state through 0x30d6, sets flag 6, restores flag 9 if it was previously set, clears timer/event words [0x0129] and [0x012b], reloads trace logic [0x1d12] if configured, calls menu/list refresh helper 0x930e, redraws the prompt marker, and returns zero to the dispatcher.
  • On canceled restart, the reset block is skipped and 0x2472 returns the following bytecode pointer after redrawing the prompt marker.
  • code.system.confirm_exit_action at image 0x027f is the smaller confirmation-gated exit path. It stops sound, exits immediately when operand byte arg0 == 1, or displays message 0x05e3 and exits only if the display helper returns one.
  • code.system.exit_with_cleanup at image 0x02ae calls code.system.shutdown_cleanup (0x8275) and then calls code.system.dos_terminate(0) at 0x00ae. The DOS terminate wrapper uses int 21h with AH=0x4c.
  • code.system.shutdown_cleanup closes the log file if open through code.log.close_if_open (0x838c), restores saved interrupt vectors/timer state through 0x849f, then calls the BIOS video-mode wrapper 0x5a5e with mode byte [0x1807].
  • code.system.install_interrupt_hooks at 0x83ac saves original vectors and installs interpreter keyboard/timer/critical-error style hooks. The restore helper at 0x849f restores saved vectors for interrupts 0x1f, 0x05, 0x08, 0x1c, 0x09, 0x23, 0x24, and conditionally 0x10, and resets the PIT timer divisor before returning.
  • Restore action 0x7e uses the fatal exit helper for read failure: after any length-prefixed state block read fails, it closes the save file, displays message 0x0d87, and calls code.system.exit_with_cleanup. This is not modeled as a recoverable restore error.
  • Restore success has a different continuation: after all five blocks are read, it restores display adapter/mode bytes, sets hardware flag 11 for nonzero adapter kinds, calls code.restore.replay_resource_events, refreshes display and list state, clears the saved caller return pointer on the stack, and returns zero so execution resumes through the restored state.

2026-07-04: save envelope round-trip and DOS wrapper correction

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,220p' tools/agi_save.py
  • sed -n '1,240p' tests/test_save_resources.py
  • sed -n '680,760p' docs/src/compatibility_testing.md
  • sed -n '70,190p' docs/src/agi_executable.md
  • sed -n '290,370p' PROGRESS.md
  • rg -n "code\\.dos\\.|0x5cef|0x5d12|0x5d35|0x5d6b|0x5db2|0x5e01|0x5e3e|0x5e73" docs/src PROGRESS.md tools tests
  • ndisasm -b 16 -o 0x5c80 -e 0x5e80 build/cleanroom/AGI.decrypted.exe
  • ndisasm -b 16 -o 0x5ca8 -e 0x5ea8 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'
  • python3 -B -m unittest tests.test_save_resources

Documented result:

  • tools/agi_save.py now serializes the parsed save-file envelope back to bytes. The serializer requires the same source-backed structure used by the parser: a 31-byte header, exactly five blocks, matching block order, and each block’s stored length matching the number of data bytes. This preserves the interpreter’s envelope instead of inventing a higher-level save format.
  • tests/test_save_resources.py now checks that all 11 checked-in SQ2/SQ2SG.* files parse and serialize back to identical bytes. The focused save test module ran 6 tests successfully.
  • The DOS wrapper symbol rows in docs/src/symbolic_labels.md were corrected against disassembly. The previous table had several post-open wrappers mapped to later helper addresses; the corrected source-backed map is:
LabelImage offsetDOS function / behavior
code.dos.create_file0x5cadAH=0x3c; returns 0xffff on carry/error.
code.dos.open_file0x5cceAH=0x3d; returns 0xffff on carry/error.
code.dos.read_file0x5cefAH=0x3f; returns zero on carry/error, so callers check the returned byte count.
code.dos.write_file0x5d12AH=0x40; returns zero on carry/error, so callers check the returned byte count.
code.dos.delete_file0x5d35AH=0x41; returns zero on carry/error.
code.dos.close_file0x5d52AH=0x3e; callers observed so far ignore a close error.
code.dos.seek_file0x5d6bAH=0x42; returns 0xffff:0xffff in DX:AX on carry/error.
code.dos.duplicate_handle0x5d94AH=0x45; returns 0xffff on carry/error.
code.dos.get_current_directory0x5db2Writes a leading separator and calls AH=0x47 for the default drive.
code.dos.get_current_drive_letter0x5deaAH=0x19; returns lowercase a plus the zero-based current-drive number.
code.dos.find_first0x5e01Sets DTA with AH=0x1a, then calls AH=0x4e; returns 0xffff on carry/error.
code.dos.find_next0x5e26AH=0x4f; returns 0xffff on carry/error.
code.dos.probe_drive_selectable0x5e3eTries selecting a lowercase drive letter, checks whether DOS reports it as current, then restores the original drive.
code.dos.get_file_time0x5e73AH=0x57, AL=0; selector code uses the returned CX time word.
code.dos.prepare_call0x5e8dTemporarily switches DS to segment 0x0a01 and clears word [0x184d].
  • This pass strengthens save-file fixture generation but does not yet prove a dynamic original-engine save/restore round trip from a generated save file.

2026-07-04: save/restore file-error source pass

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x2500 -e 0x2700 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x26a0 -e 0x28a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x2750 -e 0x2950 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • rg -n "0x28c6|0x26b0|0x2753|0x2512|0x0e46|0x0d87|write_length|read_length|save_game_state|restore_game_state|About to save|Error in" docs/src tools tests PROGRESS.md
  • strings -a -t x SQ2/AGIDATA.OVL | rg "Can't|Error|About|save|restore|file"
  • xxd -g 1 -s 0xd20 -l 0x160 SQ2/AGIDATA.OVL
  • sed -n '270,300p' docs/src/runtime_model.md
  • sed -n '1350,1360p' docs/src/logic_bytecode.md

Documented result:

  • code.save.write_length_prefixed_block at image 0x28c6 writes the low length byte, high length byte, and payload separately. It returns 1 only when the two one-byte writes each return 1 and the payload write returns the full requested length.
  • Save action 0x7d treats create failure and write failure differently. code.dos.create_file returning 0xffff formats message 0x0df0 with path buffer 0x1962, displays it, restores modal/text state, and returns to the following bytecode. A short header write or failed length-prefixed block closes the handle, deletes filename 0x1c8c, displays message 0x0e46, restores modal/text state, and also returns to the following bytecode.
  • code.save.read_length_prefixed_block at image 0x26b0 reads the low length byte, high length byte, and payload separately. It returns 1 only when the one-byte reads each return 1 and the payload read returns the decoded length.
  • Restore action 0x7e also splits open and read failures. Open failure formats/displays message 0x0d73, restores modal/text state, and returns to the following bytecode. Any failure after the file has opened and block reads have begun closes the file, displays message 0x0d87, and calls code.system.exit_with_cleanup; this path terminates the DOS process.
  • Local strings confirm the visible prompts:
    • 0x0d73: can’t-open-file message;
    • 0x0d87: restoring error followed by an enter-to-quit prompt;
    • 0x0df0: directory full or disk write-protected;
    • 0x0e46: disk full.

2026-07-04: view-batch right/bottom placement validation

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "Picture|picture|view|renderer|Remaining|Highest-Value|fuzz|line|pattern|fill|priority|cel|mirror|clip" PROGRESS.md docs/src/graphics_object_pipeline.md docs/src/picture_resources.md docs/src/view_resources.md docs/src/compatibility_testing.md tests tools | head -n 240
  • ls docs/src
  • sed -n '1,260p' tests/test_picture_fuzz.py
  • sed -n '1,260p' tests/test_view_batch.py
  • sed -n '1,260p' tools/view_batch.py
  • sed -n '260,620p' tools/view_batch.py
  • rg -n "right_clip|bottom_clip|left_clip|top_clip|clip" tools/view_batch.py tools/object_overlay_probe.py tests/test_view_batch.py tests/test_object_overlay_probe.py docs/src/compatibility_testing.md docs/src/graphics_object_pipeline.md
  • sed -n '640,710p' tools/agi_graphics.py
  • python3 -B -m unittest tests.test_view_batch
  • Local Python probes using tools/agi_graphics.render_view_frame and tools/agi_graphics.search_object_placement to compute view 11 frame size and placement-search results.
  • python3 -B tools/view_batch.py --snapshot --dos-prefix VC --output build/view-batch/batches/clip_right_bottom_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case view_011_right_clip --case view_011_bottom_clip
  • sed -n '1,220p' build/view-batch/batches/clip_right_bottom_001.json
  • python3 -B tools/inspect_ppm.py build/view-batch/fixtures/view_011_right_clip/qemu_capture.ppm
  • sed -n '180,235p' tools/object_overlay_probe.py
  • sed -n '340,380p' tools/object_overlay_probe.py
  • python3 -B tools/view_batch.py --snapshot --dos-prefix VC --output build/view-batch/batches/clip_right_bottom_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case view_011_right_clip --case view_011_bottom_clip

Documented result:

  • The simple view-batch registry now includes right and bottom edge-placement cases for view 11/group 0/frame 0, in addition to the previous normal, cached, mirrored, left-edge, top-edge, and low-priority cases.
  • tools/view_batch.py now supports repeated --case CASE_ID filters, matching the focused-run pattern used by the other QEMU probe harnesses.
  • The first focused QEMU run, clip_right_bottom_001, mismatched the right-edge case. The mismatch was not an original-engine failure; it showed that direct view composition expected a simple right clamp while the original engine routed the transient object through code.object.place (0x593a) and its source-backed spiral placement search.
  • tools/view_batch.py now computes the expected comparison placement with search_object_placement by default, with optional expected-position and expected-priority overrides available for future cases.
  • Local placement probes for view 11/group 0/frame 0 showed:
    • request (150, 80) resolves to placement (140, 71);
    • request (20, 170) resolves to placement (23, 167).
  • The corrected focused QEMU run, clip_right_bottom_002, matched both cases with 2 matches, 0 mismatches, and 0 errors.

2026-07-04: real-picture batch harness and base QEMU parity

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "compare_picture_capture.py|qemu_fixture.py picture|picture [0-9]|real-resource|real resource|picture_N|PICDIR|all 74|74 valid|render_picture" docs/src tools tests
  • sed -n '620,730p' tools/qemu_fixture.py
  • sed -n '1,180p' tools/qemu_fixture.py
  • Local Python scan of SQ2/PICDIR to find present picture resources, picture streams that use pattern commands, and the largest valid picture payload.
  • sed -n '1,140p' tests/test_graphics_rendering.py
  • sed -n '40,110p' docs/src/compatibility_testing.md
  • Added tools/picture_batch.py and tests/test_picture_batch.py.
  • python3 -B -m unittest tests.test_picture_batch tests.test_view_batch
  • python3 -B tools/picture_batch.py --snapshot --dos-prefix PB --output build/picture-batch/batches/picture_base_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • Escalated rerun of the same tools/picture_batch.py command after the first unprivileged QEMU attempt could not bind vnc=127.0.0.1:5 from the sandbox.

Documented result:

  • tools/picture_batch.py provides a reusable QEMU validation batch for real local SQ2 picture resources. It builds picture fixtures with build_picture_fixture, can run them serially or from one QEMU snapshot, and writes a JSON report with match/mismatch/error counts.
  • The base case registry intentionally starts small: picture 1 is the first present local picture resource and includes pattern plotting, while picture 45 is the largest valid local picture payload observed in this corpus.
  • The first unprivileged batch attempt failed before the interpreter ran because QEMU could not bind its VNC socket from the sandbox. That is an execution environment failure, not compatibility evidence.
  • The escalated picture_base_001 snapshot run matched both base cases: picture_001_first_present and picture_045_largest_payload each had 0 mismatches over 26,880 logical pixels, for 2 matches, 0 mismatches, and 0 errors overall.

2026-07-04: pattern channel-mask source pass and QEMU visible parity

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '230,285p' docs/src/graphics_object_pipeline.md
  • sed -n '380,430p' tools/agi_graphics.py
  • ndisasm -b 16 -o 0x64e0 -e 0x64e0 build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'
  • ndisasm -b 16 -o 0x5200 -e 0x5200 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'
  • ndisasm -b 16 -o 0x6470 -e 0x6470 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x66a0 -e 0x66a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x54e0 -e 0x54e0 build/cleanroom/AGI.decrypted.exe | sed -n '1,110p'
  • ndisasm -b 16 -o 0x6670 -e 0x6670 build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'
  • ndisasm -b 16 -o 0x66f0 -e 0x66f0 build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'
  • ndisasm -b 16 -o 0x68b0 -e 0x68b0 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'
  • ndisasm -b 16 -o 0x5860 -e 0x5860 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'
  • sed -n '90,150p' tools/picture_fuzz.py
  • sed -n '270,330p' tests/test_graphics_rendering.py
  • python3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzz
  • python3 -B tools/picture_fuzz.py generate --count 8 --seed 4097 --output build/picture-fuzz/pattern-channel-cases --clean
  • python3 -B tools/picture_fuzz.py batch-qemu --snapshot --corpus build/picture-fuzz/pattern-channel-cases --fixture-root build/picture-fuzz/pattern-channel-fixtures --case base_027_pattern_visual_control_channels --case base_028_pattern_visual_disabled_control_only --case base_029_pattern_control_disabled_visual_only --dos-prefix PC --output build/picture-fuzz/batches/pattern_channel_masks_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean

Documented result:

  • The first pattern-source disassembly window intentionally proved a label convention detail: existing picture symbols are loaded-image offsets, while ndisasm -e consumes file offsets. In this file-offset pass, the relevant windows are shifted by 0x200; prose continues using the established symbolic loaded-image labels.
  • Source inspection confirms that pattern helper code.picture.cmd_pattern_plot and its draw helper do not implement separate channel rules. For every accepted candidate pixel, the helper writes the current X/Y pair into [0x150b] and calls the common pixel writer. The pixel writer selects [0x136d] for odd Y rows or [0x136e] for even Y rows, ORs active draw bits from [0x1369], ANDs with the selected mask, and stores the resulting byte.
  • In the full 16-color EGA target path, code.display.map_visual_color_for_adapter returns with AH == AL, so visual odd/even masks are identical. The parity branch remains part of the implementation model, but visual parity divergence is a non-EGA concern unless it is needed to explain observed SQ2 behavior.
  • Added curated safe fuzz cases:
    • base_027_pattern_visual_control_channels: both channels active; local tests assert visual and nondefault control nibbles change together.
    • base_028_pattern_visual_disabled_control_only: visual disabled, control active; local tests assert only the control nibble changes.
    • base_029_pattern_control_disabled_visual_only: control disabled, visual active; local tests assert the default control nibble is preserved.
  • A test correction during this pass clarified that the default control nibble is already 4, so the both-active case uses control class 5 to make the control-channel change observable in local cell tests.
  • The QEMU snapshot batch pattern_channel_masks_001 matched the visible EGA surface for all three cases: 3 matches, 0 mismatches, and 0 errors. Screenshots do not expose the control buffer directly, so the control-channel assertions remain source-backed plus local renderer evidence rather than screenshot evidence.
  • Regenerating the standard deterministic corpus after adding the three cases reports 1,054 total cases and 1,052 safe-for-QEMU cases.

2026-07-04: broad real-picture preset parity

Commands run from /Users/peter/ai/agi/reverse:

  • Local Python scan of all valid PICDIR payloads, counting local command bytes 0xf0..0xfa by picture and listing largest payloads, pattern-heavy pictures, fill-heavy pictures, and broad command-family mixes.
  • Added broad_cases, all_present_cases, and preset selection to tools/picture_batch.py.
  • Added preset and discovery tests to tests/test_picture_batch.py.
  • python3 -B -m unittest tests.test_picture_batch
  • python3 -B tools/picture_batch.py --preset broad --snapshot --dos-prefix PB --output build/picture-batch/batches/picture_broad_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • sed -n '1,220p' build/picture-batch/batches/picture_broad_001.json

Documented result:

  • The local corpus scan found 74 present valid picture resources. The broad preset is intentionally representative rather than exhaustive:
    • picture 1: first present local picture resource;
    • picture 6: early resource with many fills and pattern plots;
    • picture 17: all observed picture command families, including multiple pattern-mode changes;
    • picture 43: dense large picture with many fills, lines, and pattern plots;
    • picture 44: large fill-heavy picture with many control toggles;
    • picture 45: largest valid picture payload in the local corpus;
    • picture 46: pattern-heavy large picture with broad command-family mix;
    • picture 76: high pattern-count resource outside the largest-payload cluster.
  • tools/picture_batch.py --preset all can now discover all 74 present valid local picture resources for a future full-corpus QEMU run. The default preset remains the two-case base set so quick checks stay cheap.
  • The QEMU snapshot batch picture_broad_001 matched all eight broad cases: 8 matches, 0 mismatches, and 0 errors, with each comparison covering 26,880 logical pixels.

2026-07-04: packed picture fixtures and full SQ2 picture parity

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' tools/qemu_snapshot.py
  • Local Python check of picture_batch.all_present_cases(), confirming 74 valid present picture cases from the local PICDIR.
  • du -sh SQ2 build/picture-batch/fixtures
  • ls -lh build/dos622/dos622.img build/picture-batch/snapshot/picture_batch.raw build/picture-batch/snapshot/picture_batch.qcow2 2>/dev/null
  • find build/picture-batch/fixtures/picture_001_first_present -maxdepth 1 -type f -print0 | xargs -0 ls -lh
  • du -sh build/picture-batch/fixtures/picture_001_first_present build/picture-batch/fixtures/picture_046_pattern_heavy
  • mdir -i build/picture-batch/snapshot/picture_batch.raw@@32256 :: | tail -n 20
  • Added copy_minimal_picture_tree and build_packed_picture_fixture to tools/qemu_fixture.py.
  • Updated tools/picture_batch.py to use packed picture fixtures.
  • Added packed-fixture structural coverage to tests/test_qemu_fixture.py.
  • python3 -B -m unittest tests.test_qemu_fixture tests.test_picture_batch
  • Local Python packed-fixture size probe for pictures 1 and 45.
  • python3 -B tools/picture_batch.py --preset base --snapshot --fixture-root build/picture-batch/packed-fixtures --dos-prefix PP --output build/picture-batch/batches/picture_base_packed_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B tools/picture_batch.py --preset all --snapshot --fixture-root build/picture-batch/all-fixtures --dos-prefix PA --output build/picture-batch/batches/picture_all_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

Documented result:

  • A direct all-picture batch using full copied SQ2 fixture directories would put too much pressure on the 64 MB DOS snapshot image. One full picture fixture directory was about 1.7 MB because it included the original volumes and saved games, so 74 copies would exceed the image before filesystem overhead.
  • Packed picture fixtures solve that for picture-only parity checks. They copy the minimal engine/support files, write generated LOGIC.0 into VOL.3, append the tested local picture payload as the next VOL.3 record, patch LOGDIR[0] to the generated logic, and patch the selected PICDIR entry to the appended picture record. A packed picture fixture for pictures 1 or 45 is roughly 72 KB of input files.
  • The packed fixture preserves the tested original picture payload while avoiding duplicate copies of unrelated resource volumes. The original one-off qemu_fixture.py picture command still uses the historical full-tree fixture path; picture_batch.py now uses packed fixtures for batch throughput.
  • Packed base QEMU batch picture_base_packed_001 matched pictures 1 and 45 with 2 matches, 0 mismatches, and 0 errors, validating that the original engine accepts the trimmed picture fixture layout.
  • Full present-picture QEMU batch picture_all_001 matched all 74 valid local SQ2 picture resources with 74 matches, 0 mismatches, and 0 errors.
  • User feedback during this long run identified an important future throughput direction: for resource sweeps, generate a single in-engine carousel fixture that displays the next resource after input or a timed event, then drive it from QEMU with screendump plus key sends. The isolated one-process-per-case snapshot harness remains the simplest reference oracle, but carousel-style sweeps should become part of the infrastructure before comparing many games and interpreter versions.
  • If future fixture sets genuinely need more DOS disk space, the right answer is a larger formatted/bootable DOS test image or a purpose-built large fixture image. Appending bytes to build/dos622/dos622.img would not by itself help, because the FAT partition geometry inside the image would still describe the old volume.

Commands run from /Users/peter/ai/agi/reverse:

  • Added carousel bytecode helpers and build_picture_carousel_fixture to tools/qemu_fixture.py.
  • Added tools/picture_carousel.py.
  • Added tests/test_picture_carousel.py and carousel structural coverage in tests/test_qemu_fixture.py.
  • python3 -B -m unittest tests.test_qemu_fixture tests.test_picture_carousel
  • python3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_001.json --boot-wait 5 --first-wait 8 --advance-wait 2
  • python3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_002.json --boot-wait 5 --first-wait 8 --advance-wait 2
  • python3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_003.json --boot-wait 5 --first-wait 8 --advance-wait 2
  • python3 -B tools/picture_carousel.py --preset broad --fixture-root build/picture-carousel/broad-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_broad_001.json --boot-wait 5 --first-wait 8 --advance-wait 2
  • python3 -B tools/picture_carousel.py --preset broad --fixture-root build/picture-carousel/broad-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_broad_002.json --boot-wait 5 --first-wait 8 --advance-wait 8
  • Manual four-picture case file build/picture-carousel/manual_four_cases.json.
  • python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_001.json --boot-wait 5 --first-wait 8 --advance-wait 4
  • python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_002.json --boot-wait 5 --first-wait 8 --advance-wait 4
  • python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_keys_001.json --boot-wait 5 --first-wait 8 --advance-wait 4 --advance-key x,y,z
  • python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_mapped_001.json --boot-wait 5 --first-wait 8 --advance-wait 4
  • python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_fkeys_001.json --boot-wait 5 --first-wait 8 --advance-wait 4
  • python3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_mapped_fkey_001.json --boot-wait 5 --first-wait 8 --advance-wait 4

Documented result:

  • tools/picture_carousel.py builds one packed fixture containing multiple picture payloads, launches one engine process, captures the first picture, sends an advance key, waits, captures the next picture, and compares each capture to the local renderer.
  • The first raw-key implementation matched the first capture but failed the second because it cleared the wrong byte variable. The raw-key predicate caches at absolute [0x001c]; because script variables start at [0x0009], the matching script variable index is 0x13, not 0x1c. Clearing the wrong slot let a single key event advance twice and wrap back to picture 1.
  • Correcting that raw-key cache clear made the two-picture base carousel picture_carousel_base_003 pass with 2 matches and 0 mismatches.
  • Longer raw-key carousels still stalled after the third displayed picture. Changing the persistent carousel index from high variable v249 to v32 did not change that behavior.
  • The mapped-key/status-byte variant uses action 0x79 to map advance keys to unique status bytes and tests (index == n) && status[n]. Printable keys and function-key mappings both still failed broader four/eight-picture sweeps: later captures either remained on the previous picture or showed the expected picture with a visible input/message-window artifact in logical rectangle approximately x=35..124, y=67..92.
  • The current mapped function-key base smoke picture_carousel_base_mapped_fkey_001 passed with 2 matches and 0 mismatches from one engine process. This proves the general packed-carousel fixture can work, but the advance/ack strategy is not robust enough yet for broad compatibility evidence.
  • Keep tools/picture_carousel.py as infrastructure prototype and unit-tested scaffolding. Before using it for cross-game sweeps, it needs a deterministic way to advance multiple resources without parser/UI side effects and with a reliable acknowledgement that the next picture has completed drawing.

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x0150 -e 0x0350 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • ndisasm -b 16 -o 0x7f60 -e 0x8160 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'
  • Local message scan over readable SQ2 logic resources for speed, fast, and slow.
  • Added timed-carousel helpers to tools/qemu_fixture.py.
  • Added timed and polling modes to tools/picture_carousel.py.
  • python3 -B -m unittest tests.test_qemu_fixture tests.test_picture_carousel
  • python3 -B tools/picture_carousel.py --preset base --mode timed --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-base-fixtures --dos-dir PICTIME --output build/picture-carousel/batches/picture_carousel_base_timed_001.json --boot-wait 5 --first-wait 5 --advance-wait 7
  • python3 -B tools/picture_carousel.py --preset broad --mode timed --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-fixtures --dos-dir PICTIME --output build/picture-carousel/batches/picture_carousel_broad_timed_001.json --boot-wait 5 --first-wait 5 --advance-wait 7 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad.qcow2
  • python3 -B tools/picture_carousel.py --preset broad --case picture_017_full_command_mix --case picture_043_dense_large_fill_pattern --mode timed --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-17-43-fixtures --dos-dir PT1743 --output build/picture-carousel/batches/picture_carousel_17_43_timed_001.json --boot-wait 5 --first-wait 5 --advance-wait 7 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_17_43.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_17_43.qcow2
  • python3 -B tools/picture_carousel.py --preset broad --mode timed --delay-cycles 240 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-fixtures-v4 --dos-dir PICTIME --output build/picture-carousel/batches/picture_carousel_broad_timed_004.json --boot-wait 5 --first-wait 5 --advance-wait 7 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad_v4.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad_v4.qcow2
  • python3 -B tools/picture_carousel.py --preset broad --mode timed --poll --delay-cycles 240 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-poll-fixtures --dos-dir PICPOLL --output build/picture-carousel/batches/picture_carousel_broad_timed_poll_001.json --boot-wait 5 --first-wait 3 --poll-interval 1 --poll-timeout 25 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad_poll.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad_poll.qcow2
  • python3 -B tools/picture_carousel.py --preset broad --mode timed --poll --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-poll-fast-fixtures --dos-dir PICPOLL --output build/picture-carousel/batches/picture_carousel_broad_timed_poll_fast_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.5 --poll-timeout 15 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad_poll_fast.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad_poll_fast.qcow2
  • python3 -B tools/picture_carousel.py --preset broad --mode timed --poll --delay-cycles 60 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-poll-faster-fixtures --dos-dir PICPOLL --output build/picture-carousel/batches/picture_carousel_broad_timed_poll_faster_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.25 --poll-timeout 10 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad_poll_faster.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad_poll_faster.qcow2

Documented result:

  • The local SQ2 logic message scan did not find obvious script-level speed-menu text, so the speed investigation moved to the executable.
  • code.engine.main_cycle calls helper 0x7f78 near the start of each cycle. That helper reads byte DS:0x0013, which is script variable v10 because byte variables start at DS:0x0009, spins until word [0x1784] is greater than or equal to that value, then clears [0x1784]. Setting v10 lower makes the top-level cycle run faster; timed-carousel fixtures use v10 = 1 as a fast but capturable pace.
  • The timed carousel avoids parser/key-event side effects by advancing after a generated per-cycle counter rather than a keyboard event. It also sets flag 7 before generated picture loads to suppress resource-event pair recording during the artificial sweep.
  • Fixed-sleep timed captures were brittle. With delay-cycles 120, a broad run captured pictures 1,17,43,44,46,76,76,76; with delay-cycles 240, it captured 1,6,6,17,43,43,44,45. These were cadence misses: identity checks found exact matches to other broad-preset pictures.
  • Reordering each transition to update carousel state before discarding the old picture let the timed sequence advance through the whole broad list, but fixed sleeps still drifted as picture load/draw time varied.
  • Polling mode solves the cadence problem for this use case. It repeatedly captures a QEMU screendump and compares it to the expected local render until that expected picture appears, then moves to the next expected picture.
  • Timed polling run picture_carousel_broad_timed_poll_001 matched all eight broad pictures with delay-cycles 240, speed-value 1, and 0 mismatches.
  • Faster timed polling run picture_carousel_broad_timed_poll_fast_001 also matched all eight broad pictures with delay-cycles 120, speed-value 1, 0.5-second polling, and 0 mismatches. This is the current recommended one-engine broad picture sweep.
  • delay-cycles 60 was too short for reliable polling and missed all intermediate broad pictures except the final picture 76. Do not use that as a default for broad resource sweeps.

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/picture_carousel.py --preset all --mode timed --poll --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-all-poll-fixtures --dos-dir PICALL --output build/picture-carousel/batches/picture_carousel_all_timed_poll_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.5 --poll-timeout 20 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_all_poll.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_all_poll.qcow2
  • Local identity comparison of mismatched captures against all present local SQ2 picture renders.
  • magick build/picture-carousel/timed-all-poll-fixtures/carousel/qemu_picture_020.ppm build/picture-carousel/timed-all-poll-fixtures/carousel/qemu_picture_020.png
  • mdir -i build/picture-carousel/snapshot/picture_carousel_all_poll.raw@@32256 ::/PICALL
  • xxd -g 1 -s 61353 -l 32 build/picture-carousel/timed-all-poll-fixtures/carousel/VOL.3
  • xxd -g 1 -s 60 -l 9 build/picture-carousel/timed-all-poll-fixtures/carousel/PICDIR
  • Added --chunk-size support and per-case polling progress to tools/picture_carousel.py.
  • Added chunking coverage to tests/test_picture_carousel.py.
  • python3 -B -m unittest tests.test_picture_carousel
  • python3 -B tools/picture_carousel.py --preset all --mode timed --poll --chunk-size 16 --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-all-poll-chunk16-fixtures --dos-dir PICALL --output build/picture-carousel/batches/picture_carousel_all_timed_poll_chunk16_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.5 --poll-timeout 20 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_all_poll_chunk16.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_all_poll_chunk16.qcow2

Documented result:

  • A single all-present-picture timed polling carousel matched the first 19 pictures, then all later expected captures were closest to picture 19.
  • Inspecting the picture 20 capture showed the original engine’s disk prompt: Please insert disk 3 and press ENTER. The prompt was drawn over picture 19.
  • The generated fixture disk still contained VOL.3 in C:\PICALL, and that file size was 180,430 bytes. The packed picture 20 record at offset 0xefa9 began with the expected record header bytes 12 34 03 86 0e, and the PICDIR entry for picture 20 was 30 ef a9. This rules out the simple fixture-copy/header explanation for the prompt.
  • Treat this as an original-engine resource lifecycle or disk-prompt boundary for oversized generated carousel fixtures, not as renderer behavior to model in the clean-room spec.
  • The chunked all-present-picture timed polling run used chunks of 16 pictures and matched all 74 valid local SQ2 pictures with 74 matches, 0 mismatches, and 0 errors across five engine launches.
  • Chunking is the current recommended path for larger real-resource carousel sweeps: it preserves the faster timed polling workflow while avoiding the oversized single-fixture prompt boundary.

2026-07-04: dynamic save-write probe

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "save|restore|file-error|file error|0x7d|0x7e|save_game|restore_game|SAVE" tools tests docs/src PROGRESS.md
  • sed -n '740,825p' docs/src/compatibility_testing.md
  • sed -n '1,260p' tools/agi_save.py
  • sed -n '1,320p' tools/qemu_fixture.py
  • sed -n '1,320p' tools/qemu_snapshot.py
  • Added tools/save_roundtrip_probe.py.
  • Added tests/test_save_roundtrip_probe.py.
  • python3 -B -m unittest tests.test_save_roundtrip_probe
  • python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_001.json --boot-wait 5 --draw-wait 8 --post-launch-wait 2 --post-launch-after-text-wait 1 --key-delay 0.08
  • python3 -B tools/save_roundtrip_probe.py --keys $'codex probe\n' --output build/save-roundtrip/save_roundtrip_002.json --capture build/save-roundtrip/qemu_capture_002.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_002.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_002.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_002.raw --save-output build/save-roundtrip/SQ2SG_002.1 --boot-wait 5 --draw-wait 8 --post-launch-wait 5 --post-launch-after-text-wait 1 --key-delay 0.08
  • magick build/save-roundtrip/qemu_capture_002.ppm build/save-roundtrip/qemu_capture_002.png
  • mdir -i build/save-roundtrip/snapshot/save_roundtrip_after_002.raw@@32256 ::/SVRT
  • python3 -B tools/save_roundtrip_probe.py --description abc --no-submit-description --output build/save-roundtrip/save_roundtrip_debug_no_submit.json --capture build/save-roundtrip/qemu_capture_no_submit.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_no_submit.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_no_submit.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_no_submit.raw --save-output build/save-roundtrip/SQ2SG_no_submit.1 --boot-wait 5 --draw-wait 4 --path-prompt-wait 2 --description-wait 3 --key-delay 0.12
  • python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_005.json --capture build/save-roundtrip/qemu_capture_005.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_005.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_005.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_005.raw --save-output build/save-roundtrip/SQ2SG_005.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --key-delay 0.08
  • python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_006.json --capture build/save-roundtrip/qemu_capture_006.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_006.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_006.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_006.raw --save-output build/save-roundtrip/SQ2SG_006.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08
  • mdir -i build/save-roundtrip/snapshot/save_roundtrip_after_006.raw@@32256 ::/SVRT
  • mcopy -o -i build/save-roundtrip/snapshot/save_roundtrip_after_006.raw@@32256 ::/SVRT/SG.1 build/save-roundtrip/SG_006.1
  • python3 -B -c "import sys; from pathlib import Path; sys.path.insert(0, 'tools'); from agi_save import load_save; s=load_save(Path('build/save-roundtrip/SG_006.1')); print(s.description); print([b.length for b in s.blocks]); print(Path('build/save-roundtrip/SG_006.1').stat().st_size)"
  • python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_007.json --capture build/save-roundtrip/qemu_capture_007.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_007.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_007.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_007.raw --save-output build/save-roundtrip/SG_007.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08

Documented result:

  • The new probe builds a synthetic fixture that removes existing save files, displays a blank picture, loads view 11, calls action 0x7d, redraws the picture, draws view 11 at X 50 after the save action returns, and loops.
  • The first one-shot key sequence did not write a save. Its capture showed the save-description prompt with a blank input line, proving the original action had reached the save UI but the typed description had not landed in the correct editor.
  • A no-leading-Enter sequence appended codex probe to the path prompt and produced the visible message There is no directory named \SVRTcodex probe. This established that the first save UI stage is path acceptance.
  • A no-submit debug run after accepting the path showed the numbered save-slot selector. This established the second UI stage: select a slot before typing the description.
  • After adding path, slot, description, and final confirmation stages, the original engine wrote SG.1 in the synthetic C:\SVRT fixture directory. The name lacks the SQ2 prefix seen in the checked-in SQ2/SQ2SG.* files; the probe therefore exposes --save-stem for future game/interpreter runs.
  • The extracted dynamic save file parsed through tools/agi_save.py with description codex probe, block lengths 1505, 903, 328, 100, and 12, and total size 2889 bytes.
  • The fourth block length differs from the checked-in local SQ2 saves’ observed 200 byte fourth block. The source-backed writer uses [0x0141] * 2, so this dynamic fixture’s shorter object/list state implies a runtime count of 50 entries for the generated no-save scenario.
  • The post-save QEMU capture matched the expected validation screen with 0 visual mismatches, proving that the save action returned to following bytecode after the original engine wrote the file.
  • This section establishes dynamic save-write evidence. The following section records the restore probe built from the generated save bytes.

2026-07-04: dynamic restore probe from generated save

Commands run from /Users/peter/ai/agi/reverse:

  • Extended tools/save_roundtrip_probe.py with restore fixture generation and --mode restore.
  • Added restore fixture coverage to tests/test_save_roundtrip_probe.py.
  • python3 -B -m unittest tests.test_save_roundtrip_probe
  • python3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SG_007.1 --output build/save-roundtrip/restore_roundtrip_001.json --fixture build/save-roundtrip/restore-fixture --dos-dir RSVT --capture build/save-roundtrip/restore_capture_001.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_001.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_001.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --confirmation-wait 1 --key-delay 0.08
  • magick build/save-roundtrip/restore_capture_001.ppm build/save-roundtrip/restore_capture_001.png
  • python3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SG_007.1 --output build/save-roundtrip/restore_roundtrip_002.json --fixture build/save-roundtrip/restore-fixture --dos-dir RSVT --capture build/save-roundtrip/restore_capture_002.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_002.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_002.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 5 --slot-wait 1 --confirmation-wait 1 --key-delay 0.08
  • mdir -i build/save-roundtrip/snapshot/restore_roundtrip_002.raw@@32256 ::/RSVT
  • python3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SG_007.1 --save-stem SQ2SG --output build/save-roundtrip/restore_roundtrip_sq2stem_001.json --fixture build/save-roundtrip/restore-fixture-sq2stem --dos-dir RSV2 --capture build/save-roundtrip/restore_capture_sq2stem_001.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_001.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_001.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 5 --slot-wait 1 --confirmation-wait 1 --key-delay 0.08
  • Source rereads around 0x5b73, 0x8b9f, 0x85e5, and 0x2512 showed that DS:0x0002 supplies both the filename prefix in %s%s%ssg.%d and the saved-state signature checked by the slot summary reader.
  • Updated tools/save_roundtrip_probe.py so generated fixtures call 0x8f verify_game_signature with message SQ2 before save/restore, then reran: python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_010.json --capture build/save-roundtrip/qemu_capture_010.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_010.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_010.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_010.raw --save-output build/save-roundtrip/SQ2SG_010.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08
  • Updated the restore fixture oracle so success is distinguished from ordinary continuation after 0x7e: the save fixture sets a packed flag and validation variables with X=50, while the restore fixture starts with X=90 and draws from restored variables only when the saved flag is present. Reran: python3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SQ2SG_010.1 --output build/save-roundtrip/restore_roundtrip_sq2stem_006.json --fixture build/save-roundtrip/restore-fixture-signed --dos-dir RST6 --capture build/save-roundtrip/restore_capture_sq2stem_006.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_006.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_006.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 8 --path-keys $'\n\n' --slot-wait 2 --slot-keys $'\n\n' --confirmation-wait 1 --confirmation-keys $'\n\n' --key-delay 0.12

Documented result:

  • The first restore fixture logic used the same byte layout as the save fixture, but the action byte was 0x7e instead of 0x7d. That proved too weak as a restore oracle: source at 0x2512..0x26af shows a successful restore returns zero and ends the current logic stream, while cancel/open-failure paths can continue after 0x7e.
  • The first restore run copied the generated save bytes into the fixture as SG.1, matching the filename that the synthetic save action wrote. The capture stayed at the restore path prompt and mismatched the validation screen.
  • Increasing the path prompt wait did not change that result. The fixture disk did contain SG.1, so the likely failure was filename selection rather than missing copied data.
  • Copying the same generated save bytes into the restore fixture as SQ2SG.1 made the old restore selector advance, but the later X=90/X=50 oracle showed that this was still just the continuation path, not a proven restore.
  • Source explains the filename-stem behavior: action 0x8f copies a logic message into DS:0x0002 and verifies it against the embedded SQ2 string; formatter 0x5b73 then uses DS:0x0002 as the third %s in %s%s%ssg.%d; slot summary reader 0x8b9f also compares the first payload bytes of a candidate save against the same string. The early synthetic fixture skipped 0x8f, so it saved a blank-prefix SG.1 whose state block started with zeroes. The corrected fixture calls 0x8f("SQ2"), writes SQ2SG.1, and the first state block starts 53 51 32 00.
  • restore_roundtrip_sq2stem_006 is the first restore probe in this sequence that proves actual restored state: it matched the X=50 branch gated by the restored flag/variables with 0 visual mismatches. A failure/cancel return after 0x7e draws X=90 and mismatches the X=50 oracle.

2026-07-04: dynamic restore read-error UI probe

Commands run from /Users/peter/ai/agi/reverse:

  • Extended tools/save_roundtrip_probe.py with --mode restore-read-error. The generated fixture writes SQ2SG.1 as a malformed save that is still selector-visible: a 31-byte description, declared first-block length 0x05e1, and seven payload bytes 53 51 32 00 00 00 00.
  • Added fixture-shape tests to tests/test_save_roundtrip_probe.py.
  • python3 -B -m unittest tests.test_save_roundtrip_probe
  • First broad-key run, which sent redundant Enters and therefore captured DOS after the fatal dialog was dismissed: python3 -B tools/save_roundtrip_probe.py --mode restore-read-error --output build/save-roundtrip/restore_read_error_001.json --fixture build/save-roundtrip/restore-read-error-fixture --dos-dir RERR --capture build/save-roundtrip/restore_read_error_001.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_read_error_001.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_read_error_001.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 8 --path-keys $'\n\n' --slot-wait 2 --slot-keys $'\n\n' --confirmation-wait 1 --confirmation-keys $'\n' --key-delay 0.12
  • Prompt-timing captures: restore_read_error_prompt_001, restore_read_error_slot_001, restore_read_error_after_slot_001, and restore_read_error_exact_001.
  • Stable capture using exactly one Enter per prompt: python3 -B tools/save_roundtrip_probe.py --mode restore-read-error --output build/save-roundtrip/restore_read_error_002.json --fixture build/save-roundtrip/restore-read-error-fixture --dos-dir RERR --capture build/save-roundtrip/restore_read_error_002.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_read_error_002.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_read_error_002.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 8 --path-keys $'\n' --slot-wait 2 --slot-keys $'\n' --confirmation-wait 1 --confirmation-keys $'\n' --key-delay 0.12
  • python3 -B tools/inspect_ppm.py build/save-roundtrip/restore_read_error_002.ppm
  • magick build/save-roundtrip/restore_read_error_002.ppm build/save-roundtrip/restore_read_error_002.png

Documented result:

  • The malformed SQ2SG.1 is 40 bytes long and intentionally cannot parse as a complete save envelope. It is still valid enough for the selector summary path: after the 31-byte description, code.save.read_slot_summary skips the first length prefix and reads the seven available payload bytes, which match the SQ2 signature prefix established by 0x8f.
  • The timing captures show the expected UI sequence: restore directory prompt, selector row with description codex probe, confirmation dialog naming \rerr\sq2sg.1, then the read-error dialog.
  • The stable final capture remains on the read-error dialog after an 8-second wait, proving that the dialog waits for an Enter rather than immediately exiting. Its text is Error in restoring game. Press ENTER to quit.
  • restore_read_error_002.ppm has geometry 640x400, RGB SHA-256 556971f26fc34deb32497a9d10c08eedeb28f6bdb0957cd7676a8ef26830849c, 3 unique colors, and non-background bounding box (0, 136, 639, 399). Sending extra Enters after confirmation dismisses this fatal dialog and leaves the process back at DOS, explaining the first failed capture.

2026-07-04: heap startup initialization source pass

Commands run from /Users/peter/ai/agi/reverse:

  • grep -n "heap initialization\|heap init\|0x0a55\|0x0a57\|0x1476\|0x1485\|0x13d6" docs/src/clean_room_executable_notes.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/agi_executable.md
  • ndisasm -b 16 -o 0x13a0 -e 0x13a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
  • rg -n "0a55|0a57|0a5b|0a5f|0a59|heap\\.base|heap\\.limit|current_top" build docs/src tools tests
  • Local byte-pattern scan over build/cleanroom/AGI.decrypted.exe for writes to 0x0a55, 0x0a57, 0x0a5b, and 0x0a5f.
  • ndisasm -b 16 -o 0x1600 -e 0x1600 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x43d0 -e 0x43d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'
  • xxd -g 1 -s 0x1620 -l 0x90 build/cleanroom/AGI.decrypted.exe
  • xxd -g 1 -s 0x4400 -l 0x60 build/cleanroom/AGI.decrypted.exe

Documented result:

  • The existing heap helper labels remain correct for allocator behavior: [0x0a55] is the current top, [0x0a57] is the base, [0x0a59] is the room/reset mark, [0x0a5b] is the limit, [0x0a5d] is the temporary mark, and [0x0a5f] is the high-water pointer. The helper cluster around image 0x1600 includes the direct rewind, temporary mark, room/reset mark, reset, free-memory-byte update, and heap-status display paths already documented in earlier notes.
  • Startup memory setup around image 0x43ea is the missing initialization source. It computes memory sizing globals, resizes/probes the resident block with DOS AH=4a, requests a runtime memory block with DOS AH=48h, and on success converts the returned segment into a DS-relative byte offset by subtracting 0x0a01 and shifting left four bits.
  • That converted offset is stored into both [0x0a55] and [0x0a57], so the heap current pointer initially equals the heap base. The limit is then computed from word [0x112c] as ([0x112c] << 4) + [0x0a57] and stored into [0x0a5b].
  • If either DOS allocation in this startup routine fails, the path displays a startup memory error and terminates through DOS rather than entering the interpreter with a partial heap.

2026-07-04: sound hardware-output source pass

Commands run from /Users/peter/ai/agi/reverse:

  • grep -n "sound\|tone\|attenuation\|speaker\|driver\|0x80f3\|0x1790\|0x1806" docs/src/clean_room_executable_notes.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/logic_bytecode.md docs/src/compatibility_testing.md
  • ndisasm -b 16 -o 0x80e0 -e 0x80e0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x8150 -e 0x8150 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • ndisasm -b 16 -o 0x7f80 -e 0x7f80 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'
  • ndisasm -b 16 -o 0x82c0 -e 0x82c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'
  • Added pc_speaker_divisor() and pc_speaker_event_enabled() to tools/agi_sound.py.
  • python3 -B -m unittest tests.test_sound_resources

Documented result:

  • The already documented channel scheduling remains the portable gameplay contract: active channel set, countdown/event timing, per-channel attenuation nibble, and completion flag. The hardware-output pass adds only the source-backed driver-interface details.
  • On hardware selectors 0 and 8, code.sound.driver_write_tone uses the PIT/PC-speaker path. If the current attenuation nibble is 0x0f, it clears bits 0 and 1 of port 0x61. Otherwise it computes a divisor from the 16-bit tone word as 12 * (((tone_word & 0x3f) << 4) + ((tone_word >> 8) & 0x0f)), writes mode byte 0xb6 to port 0x43, writes the low and high divisor bytes to port 0x42, and sets bits 0 and 1 of port 0x61.
  • On stop, the selector 0/8 path calls the same tone helper with a silence control byte, while other selector paths write bytes 0x9f, 0xbf, 0xdf, and 0xff to port 0xc0.
  • For non-0/8 selectors, code.sound.driver_write_tone writes encoded tone/control bytes to port 0xc0: it writes the high tone byte, and writes the low tone byte unless the high byte’s top three bits are all set. Selector 2 first applies the small control-byte adjustment at helper 0x8345.
  • code.sound.driver_write_attenuation maintains the low-nibble attenuation value, applies a per-channel envelope/delta table when active, adjusts selector 2 attenuation values below 8 upward by 2, combines the low nibble with a stored high-nibble channel mask, and writes the result to port 0xc0.
  • The new local test locks down the source formula for sound 1’s first event: tone word 0x8037 produces PC-speaker divisor 10560, and its control byte 0x9f has attenuation nibble 0x0f, so it is silent on the PC-speaker gate.

2026-07-04: sound attenuation envelope source model

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x8362)) count=360 2>/dev/null | ndisasm -b 16 -o 0x8162 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x82f3)) count=140 2>/dev/null | ndisasm -b 16 -o 0x80f3 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x8196)) count=180 2>/dev/null | ndisasm -b 16 -o 0x7f96 -
  • xxd -g 1 -s 0x17b8 -l 72 SQ2/AGIDATA.OVL
  • python3 -B -m unittest tests.test_sound_resources

Documented result:

  • code.sound.driver_start initializes each channel’s envelope table pointer at [0x17b0..0x17b7] to 0x17b8, each envelope index at [0x17a0..0x17a7] to 0xffff, and the per-channel active words to 0xffff.
  • On event reads, code.sound.driver_tick resets envelope index +0x17a0 to zero for channels with BX != 6 before reading a new duration/tone/control record. Channel 3 (BX == 6) keeps its current envelope index across event reads in the observed source.
  • code.sound.driver_write_attenuation (0x8162) reads the base attenuation byte from [BX+0x17a8]. If it is 0x0f, the helper skips envelope and selector-2 adjustment and writes the silent low nibble with the channel mask.
  • If the base attenuation is not 0x0f and envelope index [BX+0x17a0] is not 0xffff, the helper reads one byte from table pointer [BX+0x17b0] at that index. Byte 0x80 disables the envelope and copies previous envelope value [BX+0x17a9] into the base attenuation byte. Other bytes are applied as signed-ish deltas from the base attenuation, not cumulative deltas from the previous envelope value; negative underflow clamps to zero and positive overflow clamps to 0x0f. The clamped value is stored in [BX+0x17a9].
  • After envelope processing, the helper adds runtime byte [0x0020] and clamps to 0x0f. Hardware selector 2 then raises non-silent attenuation values below 8 by 2. Finally it ORs the low nibble with the high channel mask from [BX+0x17fc] and writes the result to port 0xc0.
  • The default table at 0x17b8 begins fe fd fe ff 00 00 01 01 ... and terminates with 0x80. The observed channel masks are 0x90, 0xb0, 0xd0, and 0xf0.
  • Added SoundAttenuationState, SoundAttenuationOutput, default_attenuation_envelope(), sound_channel_output_mask(), and sound_attenuation_output() to tools/agi_sound.py. Local tests cover the source table bytes, channel masks, selector-2 adjustment, delta clamps, and 0x80 terminator behavior.

2026-07-04: source-backed opcode dynamic-probe audit

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1080,1112p' docs/src/compatibility_testing.md
  • sed -n '1268,1298p' docs/src/compatibility_testing.md
  • rg -n "0x6e|0x83|0x8e|0xaa|0xad|source-backed" docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/compatibility_testing.md PROGRESS.md
  • sed -n '387,397p' PROGRESS.md

Documented result:

  • The five non-QEMU-validated action rows are intentionally source-backed for the current full-EGA spec target rather than unfinished core semantics.
  • 0x6e (shake_screen_like) is source-backed for its CRT/display-register timing loop. The existing dispatch smoke proves bytecode continuation, but a screenshot is not a useful portable oracle for the transient hardware effect.
  • 0x83 (clear_global_0139) is source-backed at the main-cycle mirror point. Logic script writes occur after the pre-logic mirror and can be overwritten by the next restore path, so a bytecode-only QEMU fixture would mostly prove the harness timing rather than the interpreter contract.
  • 0x8e (set_global_0141_and_refresh) resets the event-pair capacity state. The downstream save/restore replay behavior is already QEMU-backed through 0xab/0xac; probing the raw reset directly would require a narrow internal state hook.
  • 0xaa (copy_save_description_to_string_slot) copies from runtime data segment buffer 0x0e72; the earlier failed static AGIDATA.OVL patch did not populate that runtime buffer. A representative dynamic probe would need to drive the save/restore selector that fills it.
  • 0xad (increment_global_1530) is source-backed in the keyboard IRQ release gate. A direct QEMU fixture would depend on raw scan-code release timing rather than a stable high-level gameplay observable.
  • Updated PROGRESS.md and docs/src/compatibility_testing.md so these rows are no longer treated as high-value dynamic-probe work unless a future harness can drive their runtime state directly.

2026-07-04: update-list draw-order source model

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "draw ordering|draw order|update-list|update list|dirty|persistent|0x6a54|0x6a8e|0x16ff|0x1703|0x57cf|0x9db6|0x9e35" docs/src/graphics_object_pipeline.md docs/src/runtime_model.md docs/src/symbolic_labels.md tools tests
  • Initial exploratory ndisasm windows around 0x69f0, 0x5700, and 0x9d80; the first executable windows were discarded because ndisasm -e was again easy to misread as an end address rather than a file skip.
  • Corrected source slices with the EXE header offset included: ndisasm -b 16 -o 0x6a20 -e 0x6c20 build/cleanroom/AGI.decrypted.exe, ndisasm -b 16 -o 0x5728 -e 0x5928 build/cleanroom/AGI.decrypted.exe, ndisasm -b 16 -o 0x0358 -e 0x0558 build/cleanroom/AGI.decrypted.exe, ndisasm -b 16 -o 0x042f -e 0x062f build/cleanroom/AGI.decrypted.exe, ndisasm -b 16 -o 0x045e -e 0x065e build/cleanroom/AGI.decrypted.exe, and ndisasm -b 16 -o 0x4cbb -e 0x4ebb build/cleanroom/AGI.decrypted.exe.
  • rg -n "0x1322|1322|127a|priority_table|data\\.priority|0x124a" docs/src/symbolic_labels.md docs/src/graphics_object_pipeline.md docs/src/runtime_model.md tools
  • xxd -g 1 -s 0x1240 -l 0x20 SQ2/AGIDATA.OVL
  • xxd -g 1 -s 0x1320 -l 0x20 SQ2/AGIDATA.OVL
  • Local byte-pattern scan over build/cleanroom/AGI.decrypted.exe for writes to word 0x124a.
  • Added update-list ordering helpers to tools/agi_graphics.py.
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • Source confirms the two root wrappers already documented: 0x6a8e rebuilds and draws root 0x1703, then root 0x16ff; 0x6aab refreshes root 0x1703, then root 0x16ff.
  • Shared builder 0x0358(root, callback) scans the 43-byte object table in memory order. Accepted records are stored with a draw key. The key is object baseline field +0x05 unless flag bit 0x0004 is set, in which case it is 0x4cbb(object[+0x24]).
  • The builder then selects the smallest remaining key on each pass. It uses a signed comparison against an initial 0x00ff best key and preserves the first object-table entry for equal keys. Consumed keys are overwritten with 0x00ff.
  • Helper 0x042f inserts newly allocated 16-byte render nodes at the head of the root list; the first inserted node remains the root tail. Helper 0x045e draws from tail toward previous pointers, saving a backing rectangle through IBM_OBJS.OVL:0x9db0 and then drawing through IBM_OBJS.OVL:0x9db6. Combining these paths means objects draw in ascending key order within a root, while equal-key objects draw in object-table order and later entries can cover earlier entries.
  • Helper 0x4cbb(value) in SQ2’s normal mode scans the priority table from one-past index 0xa8 downward and returns the first index whose byte is less than value; value == 0 returns 0xffff. The local AGIDATA byte at 0x127a + 0xa8 is zero, and a local byte-pattern scan found only the 0x4d10 helper clearing word [0x124a], not a write that enables the alternate direct formula branch.
  • Added ObjectDrawCandidate, priority_value_to_sort_y, object_update_root, object_update_sort_key, and object_update_draw_order to the local renderer helpers. The focused graphics test module now includes source-model tests for root order, stable equal-key order, and the SQ2 sentinel behavior in the fixed-priority reverse mapping.

2026-07-04: dirty-rectangle union source model

Commands run from /Users/peter/ai/agi/reverse:

  • ndisasm -b 16 -o 0x5762 -e 0x5962 build/cleanroom/AGI.decrypted.exe (the command was useful for the first routine body but also confirmed again that ndisasm -e is a skip offset, not an end offset, so the terminal output continued past 0x57ce).
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • Helper 0x5762(object) returns without display work when word [0x1216] is zero.
  • Otherwise it loads the current frame pointer from object word +0x10, the saved frame pointer from object word +0x12, stores the current frame pointer back into +0x12, and computes one display rectangle covering both the current and saved object footprints.
  • The vertical calculation treats object Y fields as baselines. The current top is object[+0x05] - current_frame[+0x01] + 1; the saved top is object[+0x18] - saved_frame[+0x01] + 1.
  • The horizontal calculation uses left X plus frame width. The rectangle passed to overlay entry 0x980c is left = min(current_left, saved_left), bottom = max(current_bottom, saved_bottom), width = max(current_right, saved_right) - left, and height = bottom - min(current_top, saved_top) + 1.
  • Added DirtyRect and dirty_rect_union() to tools/agi_graphics.py, with focused tests for identical footprints and old/current footprints on opposite sides of the union.

2026-07-04: control-acceptance source model

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x58b8)) count=220 2>/dev/null | ndisasm -b 16 -o 0x56b8 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x4919)) count=260 2>/dev/null | ndisasm -b 16 -o 0x4719 -
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • code.object.control_acceptance (0x56b8) derives object byte +0x24 from the baseline priority table unless object flag bit 0x0004 is set, computes the logical-buffer offset for the object coordinate, and scans one row using the current frame width.
  • Object priority/control byte +0x24 == 0x0f bypasses the buffer scan and returns accepted.
  • High nibble 0x00 rejects immediately. High nibble 0x10 rejects unless object flag bit 0x0002 is set. High nibble 0x20 leaves final class state (flag3=true, flag0=false). High nibble 0x30 leaves final class state (flag3=false, flag0=true). Other nonzero high nibbles leave final class state (flag3=false, flag0=false).
  • The source resets class state for each scanned cell, so the final scanned class state controls the post-scan gates. This corrects the earlier wording that implied class 0x20 was latched once encountered anywhere.
  • After a complete scan, object flag bit 0x0100 rejects states whose flag0 component is false, and object flag bit 0x0800 rejects states whose flag0 component is true. When object byte +0x02 is zero, the final class state is also written to global flags 3 and 0.
  • Added ControlAcceptance and control_acceptance_scan() to tools/agi_graphics.py, with focused local tests for class rejection, priority-15 bypass, final-class ordering, and event-byte-zero global flag values.
  • A neighboring slice of code.object.collision_test (0x4719) confirmed the previously documented object-object skip bit 0x0200, object-table scan, grouping-byte skip, horizontal overlap test, and current/saved Y crossing test.

2026-07-04: view header reserved bytes

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x3bf7)) count=360 2>/dev/null | ndisasm -b 16 -o 0x39f7 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x3e1b)) count=180 2>/dev/null | ndisasm -b 16 -o 0x3c1b -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x60db)) count=180 2>/dev/null | ndisasm -b 16 -o 0x5edb -
  • python3 -B tools/inspect_view.py | head -n 40
  • python3 -B -c "import sys; sys.path.insert(0,'tools'); from agi_graphics import iter_valid_resources; from collections import Counter; c=Counter(payload[:2] for _,payload in iter_valid_resources('VIEWDIR')); print(c); print(sorted((k.hex(),v) for k,v in c.items()))"
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • code.view.load_resource (0x39f7) loads/caches a view payload, then calls display-mode helper 0x591f and the update-list rebuild path. It does not interpret payload bytes +0x00 or +0x01.
  • code.object.bind_view (0x3ae7) copies the cached payload pointer into the object record and reads payload byte +0x02 as the group count.
  • code.object.select_group_table (0x3c1b) reads group offsets from payload + 0x05 + selected_group * 2, then reads the selected group count from the group pointer.
  • The preview/display helper at 0x5edb binds the view and uses the existing object/frame selection path; previous source notes also identified its consumer of the preview text offset at payload bytes +0x03..+0x04.
  • A local census found all 203 valid SQ2 view resources have first two payload bytes 01 01.
  • The current clean-room model therefore treats view payload bytes +0x00 and +0x01 as reserved header bytes for SQ2: preserved in the format model, but unused by the observed loader, binder, group/frame selector, and preview paths. Future interpreter-version comparisons should flag any divergent use.

2026-07-04: direct corner-path unit coverage

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "0xF4|0xf4|corner|draw_corner|base_.*corner|F5|0xf5" tools tests docs/src/compatibility_testing.md docs/src/graphics_object_pipeline.md
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • The picture fuzz corpus and prior QEMU batches already include synthetic corner-path streams, but the focused graphics unit tests only made the command-resume behavior explicit.
  • Added direct local regression tests for 0xf4 (draw_corner_path_y_first) and 0xf5 (draw_corner_path_x_first) point sets. These tests do not add a new original-engine observation; they make the existing source-modeled rare handlers harder to accidentally regress while picture renderer work continues.

2026-07-04: WORDS.TOK decoder tests

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/inspect_words.py --prefix look --limit 10
  • python3 -B tools/inspect_words.py --prefix get --limit 10
  • python3 -B tools/inspect_words.py --prefix anyword --limit 10
  • python3 -B tools/inspect_words.py --limit 5
  • python3 -B -m unittest tests.test_words
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • The local WORDS.TOK decoder reads 26 big-endian letter offsets followed by prefix-compressed entries. The local SQ2 file has 1,099 decoded entries and a zero offset for the x bucket.
  • Known IDs used by parser probes are now locked down in local tests: anyword has ID 0x0001, look has ID 0x0002, and get has ID 0x0005.
  • Prefix-compressed phrase reconstruction is covered by tests for phrases such as look across, look down, and get inside.
  • This complements the original-engine parser_edges_001 QEMU batch, which validates matching look get, wildcard ID 0x0001, and terminator ID 0x270f through condition 0x0e.

2026-07-04: save path validation plan

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5ddd)) count=360 2>/dev/null | ndisasm -b 16 -o 0x5bdd -
  • xxd -g 1 -s 0x135f -l 16 SQ2/AGIDATA.OVL
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5fea)) count=120 2>/dev/null | ndisasm -b 16 -o 0x5dea -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x603e)) count=120 2>/dev/null | ndisasm -b 16 -o 0x5e3e -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x51ea)) count=120 2>/dev/null | ndisasm -b 16 -o 0x4fea -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5fb2)) count=90 2>/dev/null | ndisasm -b 16 -o 0x5db2 -
  • python3 -B -m unittest tests.test_save_resources

Documented result:

  • code.dos.validate_path (0x5bdd) skips leading spaces before measuring the path. If the resulting string is empty it calls code.dos.get_current_directory (0x5db2) to fill the same buffer.
  • If the last character is slash or backslash and the path length is greater than one, the validator strips that trailing separator in place before selecting a validation path. The separator table at AGIDATA.OVL:0x135f contains backslash, slash, and a terminator.
  • If the effective path has a drive prefix (text[1] == ':'), the drive letter is lowercased by helper 0x4fea and stored in byte [0x1363]; otherwise helper 0x5dea reads the current DOS drive letter into [0x1363].
  • A single slash/backslash path returns success immediately. A two-character drive path such as A: calls helper 0x5e3e, which switches to the requested drive, checks whether DOS accepted it, and switches back. Other paths call DOS find-first helper 0x5e01 with attribute 0x10.
  • Added SavePathValidationPlan and save_path_validation_plan() to tools/agi_save.py, with tests for leading spaces, empty-current-directory fallback, root acceptance, trailing separator stripping, drive-only paths, and normal directory lookup classification. The helper models source-level string handling and DOS-check selection; it does not claim to know whether a given path exists in a future DOS environment.

2026-07-04: resource cache record layout polish

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x3b79)) count=130 2>/dev/null | ndisasm -b 16 -o 0x3979 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x4be8)) count=130 2>/dev/null | ndisasm -b 16 -o 0x49e8 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x4c5e)) count=130 2>/dev/null | ndisasm -b 16 -o 0x4a5e -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x52d8)) count=90 2>/dev/null | ndisasm -b 16 -o 0x50d8 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5326)) count=240 2>/dev/null | ndisasm -b 16 -o 0x5126 -

Documented result:

  • View cache lookup 0x3979 walks the list rooted at [0x0ffa], compares record byte +0x02 against the requested view number, and leaves the link slot for insertion in [0x1000]. View loader 0x39f7 allocates 5 bytes on a miss, links the record through that slot, stores the view number at +0x02, and stores the payload pointer at +0x03.
  • Picture cache lookup 0x49e8 walks the list rooted at the static first record [0x120e], compares record byte +0x02, and leaves the insertion slot in [0x1214]. Picture loader 0x4a3b uses the static first record when that insertion slot is zero; otherwise it allocates 5 bytes, links it from the previous record, stores the picture number at +0x02, and stores the payload pointer at +0x03.
  • Sound cache lookup 0x50d8 walks the list rooted at static record [0x125a], compares record word +0x02, and leaves the insertion slot in [0x1268]. Sound loader 0x5126 uses the static first record for the first sound, otherwise allocates 14 bytes. It stores the sound number word at +0x02, the payload pointer at +0x04, and derives four channel stream pointers at +0x06, +0x08, +0x0a, and +0x0c from the first four little-endian payload offsets.
  • Existing logic loader notes already pinned down the 10-byte logic cache record: next pointer at +0x00, logic number byte at +0x02, message count at +0x03, bytecode pointer at +0x04, current instruction pointer at +0x06, and message-offset-table pointer at +0x08.

2026-07-04: picture raw-operand scanner edge

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x6675)) count=80 2>/dev/null | ndisasm -b 16 -o 0x6475 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x6694)) count=260 2>/dev/null | ndisasm -b 16 -o 0x6494 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x6803)) count=260 2>/dev/null | ndisasm -b 16 -o 0x6603 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x68ab)) count=260 2>/dev/null | ndisasm -b 16 -o 0x66ab -
  • python3 -B -m unittest tests.test_graphics_rendering
  • python3 -B -m unittest tests.test_picture_fuzz
  • python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean
  • python3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_033_raw_visual_operand --case base_034_raw_control_operand --case base_035_raw_pattern_mode_operand --dos-prefix RO --fixture-root build/picture-fuzz/fixtures --output build/picture-fuzz/batches/raw_operand_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

Documented result:

  • The top-level picture scanner at 0x6475 dispatches command bytes 0xf0..0xfa, then resumes at 0x647a using the AL byte left by the handler. This makes handler-local byte consumption significant.
  • Handlers 0x6494 (0xf0 set visual), 0x64c7 (0xf2 set control), and 0x6524 (0xf9 set pattern mode) each use a raw lodsb operand read and then preload the next byte. They do not reject operands >= 0xf0.
  • Coordinate/list readers 0x66c1, 0x66d4, and 0x66b8 do reject bytes above 0xef, returning carry with the command-looking byte in AL for the scanner to process next. This is the source distinction between raw one-byte operands and coordinate/list data.
  • Updated PictureRenderer with read_raw_byte() and changed 0xf0, 0xf2, and 0xf9 to use it. Added local graphics tests showing that command-looking bytes after those opcodes are operands, not commands.
  • Added three safe curated fuzz cases: base_033_raw_visual_operand, base_034_raw_control_operand, and base_035_raw_pattern_mode_operand. Regenerating the deterministic corpus produced 1,060 cases, of which 1,058 are marked safe for QEMU.
  • Original-engine snapshot batch raw_operand_001 matched all three new cases with 0 mismatches and 0 errors. This confirms the source-modeled scanner edge on the visible EGA surface.

2026-07-04: picture relative-line underflow edge

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x685e)) count=180 2>/dev/null | ndisasm -b 16 -o 0x665e -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x68e1)) count=260 2>/dev/null | ndisasm -b 16 -o 0x66e1 -
  • python3 -B -m unittest tests.test_graphics_rendering
  • python3 -B -m unittest tests.test_picture_fuzz
  • python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean
  • python3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_036_relative_x_underflow_wraps --case base_037_relative_y_underflow_wraps --dos-prefix RU --fixture-root build/picture-fuzz/fixtures --output build/picture-fuzz/batches/relative_underflow_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
  • python3 -B -m unittest tests.test_compatibility_suite
  • python3 -B tools/compatibility_suite.py --dry-run --include-qemu-smoke
  • python3 -B tools/compatibility_suite.py --include-qemu-smoke --report build/compatibility-suite/qemu_smoke_002.json

Documented result:

  • Handler 0x665e reads the initial coordinate pair through 0x66b8, plots it, then consumes relative bytes while they are <= 0xef.
  • For X, bits 0x70 supply a magnitude and bit 0x80 chooses subtraction. The handler adds or subtracts that magnitude in BH, an 8-bit coordinate register, then clamps only if BH > 0x9f. Subtracting from zero therefore underflows to a high unsigned byte and is clamped to 0x9f, not to zero.
  • For Y, bits 0x07 supply a magnitude and bit 0x08 chooses subtraction. The same byte-register behavior applies, followed by a high-side clamp to 0xa7.
  • Updated PictureRenderer.draw_relative_lines() to model this 8-bit wrap-and-high-clamp behavior. Added local graphics tests for X underflow from (0,10) to the right edge and Y underflow from (10,0) to the bottom edge.
  • Added safe curated fuzz cases base_036_relative_x_underflow_wraps and base_037_relative_y_underflow_wraps. Regenerating the deterministic corpus produced 1,062 cases, of which 1,060 are marked safe for QEMU.
  • Original-engine snapshot batch relative_underflow_001 matched both new cases with 0 mismatches and 0 errors.
  • Promoted the new pair into the QEMU smoke layer as picture_fuzz_relative_underflow_qemu. The updated smoke report qemu_smoke_002.json passed after running 235 local tests, mdBook, opcode-evidence check, parser QEMU probes, command-resume probes, raw-operand probes, and the new relative-underflow probes.

2026-07-04: parser normalization and output-slot model

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1b58)) count=180 2>/dev/null | ndisasm -b 16 -o 0x1958 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1aac)) count=260 2>/dev/null | ndisasm -b 16 -o 0x18ac -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1b9d)) count=260 2>/dev/null | ndisasm -b 16 -o 0x199d -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1c6b)) count=360 2>/dev/null | ndisasm -b 16 -o 0x1a6b -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1dc7)) count=130 2>/dev/null | ndisasm -b 16 -o 0x1bc7 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1de4)) count=100 2>/dev/null | ndisasm -b 16 -o 0x1be4 -
  • xxd -g 1 -s 0x0940 -l 48 SQ2/AGIDATA.OVL
  • xxd -g 1 -s 0x0c70 -l 80 SQ2/AGIDATA.OVL
  • python3 -B -m unittest tests.test_words

Documented result:

  • Re-read action 0x75 at 0x1958, parser helper 0x18ac, normalization helper 0x199d, dictionary lookup helper 0x1a6b, unknown-token helper 0x1bc7, and dictionary-entry advance helper 0x1be4.
  • Added symbolic labels for the parser action/helper routines and data labels for separator bytes 0x0c67, ignored bytes 0x0c75, parsed IDs 0x0c7b, parsed word pointers 0x0c8f, dictionary base pointer [0x0ca5], normalized parse buffer 0x0ca7, and current parse pointer [0x0cd1].
  • Added local source-model helpers to tools/inspect_words.py: parser_separator_bytes(), parser_ignored_bytes(), normalize_parser_text(), and parse_words().
  • Local tests now validate the SQ2 separator table ( ,.?!();:[]{}), ignored punctuation table (apostrophe, backtick, hyphen, double quote), separator collapse, ignored-punctuation removal, case-insensitive lookup, zero-ID word filtering, unknown-word reporting, and the ten-output-word limit.
  • The output-slot model corrects an easy-to-miss detail: ignored zero-ID dictionary words such as the, a, and i do not increment the parser’s output index. An unknown word after ignored terms therefore records parsed_nonzero_word_count + 1; for example a phrase shaped like the <unknown> look reports output slot 1, not raw token ordinal 2.

2026-07-04: heap formula test model

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x15d6)) count=260 2>/dev/null | ndisasm -b 16 -o 0x13d6 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x16bd)) count=240 2>/dev/null | ndisasm -b 16 -o 0x14bd -
  • rg -n "bump|heap|0x13d6|0x14bd|memory" docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md
  • python3 -B -m unittest tests.test_heap

Documented result:

  • Rechecked the allocator helper code.heap.allocate (0x13d6) and the heap-status action code.heap.show_status_action (0x14bd) against the existing heap notes and symbolic labels.
  • Added tools/agi_heap.py as a local source-model helper for the bump heap: allocation returns the old current pointer, advances the current pointer, recomputes the free-byte count and byte variable 8 page value, and updates the high-water pointer only when the new current pointer exceeds the prior high-water value.
  • The helper also models fatal allocation overflow, one-shot temporary-mark restore, dynamic reset to the room/reset mark, and the five heap-status numbers printed by action 0x87.
  • Added tests/test_heap.py; the focused suite passed 7 tests. This makes the allocator/status formulas executable compatibility evidence while leaving the visibly rendered out-of-memory path as optional future coverage.

2026-07-04: bit-0x80 view mirror edge tests

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5a7d)) count=420 2>/dev/null | ndisasm -b 16 -o 0x587d -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5bac)) count=260 2>/dev/null | ndisasm -b 16 -o 0x59ac -
  • sed -n '2668,2734p' docs/src/clean_room_executable_notes.md
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • Re-read code.object.rewrite_frame_orientation (0x587d) and the nearby placement helpers before expanding local coverage for the view/cel mirror model.
  • The source loop skips explicit leading transparent runs until it sees the first nontransparent run. If the row terminator is reached first, it writes an empty rebuilt row.
  • For rows with visible data, the helper emits the original implicit trailing transparent width before reversing the counted run bytes. Widths larger than 15 are emitted as multiple transparent run bytes because a run length is only four bits.
  • Added focused local tests for all-transparent rows, implicit transparent padding, long transparent chunking, and reversal from the first visible run. The graphics suite passed 57 tests after correcting one arithmetic mistake in the expected transparent width.

2026-07-04: object-overlay priority gate edge tests

Commands run from /Users/peter/ai/agi/reverse:

  • rg --files SQ2 build/cleanroom | rg "IBM|OBJ|OVL|AGI.decrypted"
  • ls -l SQ2 build/cleanroom
  • sed -n '700,770p' docs/src/clean_room_executable_notes.md
  • sed -n '2520,2550p' docs/src/clean_room_executable_notes.md
  • ndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVL | sed -n '1,260p'
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • Confirmed again that the draw entry is in SQ2/IBM_OBJS.OVL, not the base executable image: entry 0x9db6 jumps to 0x9e35.
  • The draw loop extracts each run byte as color/high nibble plus count/low nibble. If the run color is the transparent nibble from frame control byte +0x02, it advances by the run length without writing.
  • For nontransparent pixels, destination high nibbles above 0x20 are compared directly with the object’s shifted priority/control nibble. The ja branch rejects only strictly greater values, so equal priority writes are allowed.
  • Destination high nibbles <= 0x20 enter the downward scan at 0x9ec6. If the scan finds a high nibble above 0x20, that value is compared with the same inclusive rule; if the scan reaches the lower limit first, CH remains zero and even priority 0 passes the local gate.
  • Rejection at 0x9ee5 increments the destination pointer and continues the run loop, so a blocked pixel does not suppress later pixels in the same run.
  • Added local tests for equal scanned priority, no-hit priority 0, and per-pixel run continuation. The graphics suite passed 60 tests.

2026-07-04: input-word sequence matcher edge

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x0b5c)) count=220 2>/dev/null | ndisasm -b 16 -o 0x095c -
  • sed -n '336,420p' docs/src/logic_bytecode.md
  • sed -n '56,104p' docs/src/runtime_model.md
  • sed -n '5950,5985p' docs/src/clean_room_executable_notes.md
  • python3 -B -m unittest tests.test_words
  • python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_words
  • python3 -B tools/logic_interpreter_probe.py --dos-prefix PU --output build/logic-interpreter-probes/batches/parser_unknown_terminator_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_word_sequence_terminator_matches_unknown_word
  • python3 -B tools/logic_opcode_evidence.py

Documented result:

  • Re-read condition handler code.logic.condition_input_word_sequence (0x095c). The handler first rejects when data.words.parsed_count_or_error_position is zero, flag 4 is already set, or flag 2 is clear. Otherwise it walks the variable-length operand word IDs against data.words.parsed_ids.
  • Operand word ID 0x0001 is a one-word wildcard. Operand word ID 0x270f immediately forces success, skips any remaining operand words, and sets flag 4 through the normal success path.
  • A non-terminator operand after all parsed words have been consumed fails. A too-short operand list also fails because the parsed count remains nonzero when the operand loop ends.
  • Added InputWordSequenceResult and input_word_sequence_matches() to tools/inspect_words.py, with local tests for exact match, wildcard, terminator skip, extra/non-terminator failure, too-short failure, flag gates, and the unknown-token terminator edge.
  • The unknown-token edge is now dynamically confirmed: parsing flarble stores a nonzero parser count/error-position and sets flag 2; condition 0x0e with only word ID 0x270f matched in QEMU batch parser_unknown_terminator_001 with 1 match, 0 mismatches, and 0 errors.

2026-07-04: compatibility suite manifest runner

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "compatibility suite|runner|manifest|broad suite|run_.*suite|snapshot|preset broad|parser_edges|raw_operand" tools tests docs/src/compatibility_testing.md PROGRESS.md AGENTS.md
  • ls tools tests
  • sed -n '1,180p' docs/src/compatibility_testing.md
  • sed -n '380,398p' PROGRESS.md
  • python3 -B -m unittest tests.test_compatibility_suite
  • python3 -B tools/compatibility_suite.py --dry-run
  • python3 -B tools/compatibility_suite.py --dry-run --include-qemu-smoke
  • python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_001.json

Documented result:

  • Added tools/compatibility_suite.py, a local-by-default manifest/runner for the current compatibility layers. The default selection runs the full local unit suite, mdbook build docs, and python3 -B tools/logic_opcode_evidence.py --check.
  • QEMU checks are explicit opt-ins. The smoke layer includes parser edge batches and targeted picture-fuzz scanner/raw-operand batches. The broad layer includes the representative picture timed carousel and the current view/object stress carousel.
  • Added tests/test_compatibility_suite.py to lock down manifest contents, default layer selection, explicit QEMU layer selection, unknown-name rejection, stop-on-first-failure behavior, and report writing without running real subprocesses.
  • The focused runner tests passed. A real default runner invocation wrote build/compatibility-suite/local_001.json after the local unit suite passed with 230 tests, the mdBook built, and opcode evidence checked cleanly.

2026-07-04: sound tone-output boundary model

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x821c)) count=220 2>/dev/null | ndisasm -b 16 -o 0x801c -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x82f3)) count=260 2>/dev/null | ndisasm -b 16 -o 0x80f3 -
  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x8362)) count=180 2>/dev/null | ndisasm -b 16 -o 0x8162 -
  • python3 -B -m unittest tests.test_sound_resources

Documented result:

  • Re-read code.sound.driver_tick (0x801c), tone output helper code.sound.driver_write_tone (0x80f3), the selector-2 internal control adjustment helper at 0x8145, and the attenuation helper at 0x8162.
  • Added SoundToneOutput, sound_tone_output(), and sound_stop_silence_output() to tools/agi_sound.py.
  • For selectors 0 and 8, the model reports whether the PC-speaker gate is enabled and, when enabled, the source PIT divisor. A silent attenuation nibble produces disabled gate state and no divisor.
  • For other selectors, the model reports the port-0xc0 tone bytes: high tone byte first, low tone byte only when the high byte’s top three bits are not all set. The stop-core path writes 0x9f, 0xbf, 0xdf, and 0xff.
  • Local sound tests now cover these tone/silence outputs in addition to the prior resource parser, schedule, PC-speaker divisor, attenuation envelope, and completion tests. tests.test_sound_resources passed with 16 tests.

2026-07-04: cross-version workflow chapter

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,220p' docs/src/SUMMARY.md
  • sed -n '1,120p' docs/src/symbolic_labels.md
  • rg -n "cross-version|cross version|symbolic label|other games|future interpreter" docs/src AGENTS.md PROGRESS.md

Documented result:

  • Added docs/src/cross_version_workflow.md and linked it from the mdBook summary.
  • The chapter defines the evidence package to collect for each future interpreter/game pair, the structural anchors to use when mapping existing symbolic labels to moved routines, the recommended source-first pass order, compatibility suite tiers, and delta-recording rules.
  • The workflow explicitly avoids claiming that another interpreter has already been compared. It moves the cross-version tracker from an empty placeholder to a documented process that is blocked only on future local inputs.

2026-07-04: compatibility suite QEMU smoke layer

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/compatibility_suite.py --include-qemu-smoke --report build/compatibility-suite/qemu_smoke_001.json

Documented result:

  • A first sandboxed attempt reached the QEMU smoke layer but failed when QEMU tried to bind the local VNC socket with Operation not permitted. This was a sandbox permission failure, not an interpreter or fixture mismatch.
  • The rerun with local VNC/socket access passed and wrote build/compatibility-suite/qemu_smoke_001.json.
  • The selected command set included the local unit suite, mdbook build docs, tools/logic_opcode_evidence.py --check, parser-edge QEMU probes, unknown-word terminator parser QEMU probe, picture command-resume fuzz QEMU probes, and raw-operand picture fuzz QEMU probes.
  • Every selected command returned zero. The original-engine probe reports were: parser_edges_suite.json with 3 matches, parser_unknown_terminator_suite.json with 1 match, command_resume_suite.json with 3 matches, and raw_operand_suite.json with 3 matches.

2026-07-04: compatibility suite QEMU broad layer

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/compatibility_suite.py --include-qemu-broad --report build/compatibility-suite/qemu_broad_002.json

Documented result:

  • The broad suite selection includes the local checks, all QEMU smoke checks, and the broad QEMU resource sweeps.
  • The smoke checks repeated the same clean results as the dedicated smoke run: parser edge probes matched 3/3, the unknown-word terminator probe matched 1/1, command-resume picture fuzz probes matched 3/3, raw-operand picture fuzz probes matched 3/3, and relative-line underflow probes matched 2/2.
  • The suite-level broad picture carousel build/picture-carousel/batches/picture_carousel_broad_suite.json matched all 8 broad real-picture cases from one engine process.
  • The suite-level view/object stress carousel build/view-carousel/batches/view_carousel_stress_suite.json matched all 19 current base-plus-stress cases from one engine process.
  • build/compatibility-suite/qemu_broad_002.json records return code 0 for all selected commands.

2026-07-04: object control-acceptance branch tests

Commands run from /Users/peter/ai/agi/reverse:

  • dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x58b8)) count=360 2>/dev/null | ndisasm -b 16 -o 0x56b8 -
  • python3 -B -m unittest tests.test_graphics_rendering

Documented result:

  • Re-read code.object.control_acceptance at 0x56b8. The high-nibble scanner resets its class-state bytes for each scanned cell. Classes 0x10, 0x20, and 0x30 have explicit branches; other nonzero high nibbles fall through after setting the final state to (flag3=false, flag0=false).
  • Added local tests for that fall-through branch: it accepts with no rejection bits, rejects when bit 0x0100 is set, and accepts when bit 0x0800 is set.
  • Added a local test for priority/control byte 0x0f, which bypasses scanning and clears both reported event flags when object byte +0x02 is zero.
  • tests.test_graphics_rendering passed with 64 tests.

2026-07-07: Gold Rush AGI v3 resource compression first pass

Context:

  • The user identified games/GR as a local Gold Rush data set using AGI interpreter version 3 and asked for a source-first comparison with the SQ2 version 2 interpreter.
  • The known QEMU BIOS text-rendering problem was set aside as an on-screen text caveat. Dynamic checks, if needed later, should use the FreeDOS image path rather than the old MS-DOS image.

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • rg --files
  • ls -la games && ls -la games/GR && ls -la games/SQ2
  • file games/GR/AGI games/SQ2/AGI
  • xxd -g 1 -l 64 games/GR/AGI
  • xxd -g 1 -l 64 games/GR/GRDIR
  • python3 -B tools/decrypt_agi.py --game-dir games/SQ2 --output build/cleanroom/SQ2_AGI.decrypted.exe
  • dd if=games/GR/AGI bs=1 skip=$((0x30a0+0x200)) count=760 2>/dev/null | ndisasm -b 16 -o 0x30a0 -
  • dd if=games/GR/AGI bs=1 skip=$((0x44d0+0x200)) count=520 2>/dev/null | ndisasm -b 16 -o 0x44d0 -
  • dd if=games/GR/AGI bs=1 skip=$((0x07d0+0x200)) count=720 2>/dev/null | ndisasm -b 16 -o 0x07d0 -
  • dd if=games/GR/AGI bs=1 skip=$((0x9a40+0x200)) count=420 2>/dev/null | ndisasm -b 16 -o 0x9a40 -
  • dd if=games/GR/AGI bs=1 skip=$((0x33c0+0x200)) count=520 2>/dev/null | ndisasm -b 16 -o 0x33c0 -
  • dd if=games/GR/AGI bs=1 skip=$((0x0200+0x200)) count=520 2>/dev/null | ndisasm -b 16 -o 0x0200 -
  • xxd -g 1 -s 0xf50 -l 128 games/GR/AGIDATA.OVL
  • strings -a -t d games/GR/AGIDATA.OVL
  • strings -a -t d games/SQ2/AGIDATA.OVL
  • python3 -B tools/agi_resources.py --game-dir games/GR --summary --kind logic --number 0
  • python3 -B tools/agi_resources.py --game-dir games/GR --kind picture --number 1
  • python3 -B tools/agi_resources.py --game-dir games/GR --kind view --number 0
  • python3 -B tools/agi_resources.py --game-dir games/SQ2 --summary --kind logic --number 0
  • python3 -B tools/disassemble_logic.py --game-dir games/GR --stats
  • python3 -B -m unittest tests/test_agi_resources.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_logic_doc_coverage.py tests/test_sound_resources.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests

Documented result:

  • games/GR/AGI is already an MZ executable. games/SQ2/AGI remains an encrypted/data file and was decrypted to build/cleanroom/SQ2_AGI.decrypted.exe for comparison.
  • GR AGIDATA.OVL contains Version 3.002.149; SQ2 AGIDATA.OVL contains Version 2.936.
  • GR uses a combined GRDIR. Its first four little-endian words are section offsets: logic 0x0008, picture 0x02e7, view 0x05c6, and sound 0x08c6. The resulting section counts are 245 logic entries, 245 picture entries, 256 view entries, and 51 sound entries.
  • The GR directory loader at image 0x44de first formats a combined directory name using the runtime prefix and "%sdir", then falls back to separate logdir, picdir, viewdir, and snddir files if the combined open fails.
  • The v3 absent-entry helper at image 0x4599 rejects only exact ff ff ff. This differs from SQ2 v2’s high-nibble 0xf rejection.
  • GR image 0x33c2 opens sixteen volume handles using "%svol.%d". This explains observed local directory entries pointing at GRVOL.9 through GRVOL.12.
  • The v3 generic record reader at image 0x30d0 reads a 7-byte header: 12 34, a metadata/volume byte, an expanded little-endian length, and a stored little-endian length.
  • Metadata bit 0x80 selects the picture-nibble transform at image 0x9a5b. Otherwise equal expanded/stored lengths are read directly, and unequal lengths use the dictionary decompressor at image 0x07f4.
  • The dictionary decompressor uses 9-bit initial codes, reset code 0x100, end code 0x101, and grows to 10 and 11 bits. The picture-nibble transform expands packed color/control nibbles after picture commands 0xf0 and 0xf2 back into ordinary byte operands.
  • Added tools/agi_resources.py with split v2 and combined v3 container detection, v3 dictionary expansion, v3 picture-nibble expansion, and a small CLI summary/inspection mode.
  • Added tests/test_agi_resources.py. The new focused tests passed.
  • Extended tools/disassemble_logic.py so logic payload loading uses tools/agi_resources.py; v3 combined games select action table base 0x0440 and condition table base 0x0762.
  • The decoded GR logic census had no parse errors. The v3 action dispatcher accepts action slots through 0xb5, but this Gold Rush data set uses action opcodes only through 0xa9. The condition dispatcher compares predicate bytes with 0x26; a later static comparison pass corrected this note by showing that only entries 0x00..0x12 are structured table records in the observed AGIDATA.OVL, while bytes above that overlap string/data.
  • Focused regression checks passed: tests/test_agi_resources.py, plus tests/test_logic_doc_coverage.py and tests/test_sound_resources.py with AGI_GAME_DIR=games/SQ2.
  • A full local unit-suite run with AGI_GAME_DIR=games/SQ2 currently fails in older fixture builders because the private game files are read-only and copied fixtures try to overwrite files such as LOGDIR and VOL.3 without changing destination permissions. This appears unrelated to the new GR resource parser and should be handled separately if full-suite green status is needed before the next commit.

2026-07-07: GR / SQ2 static opcode, object, view, and picture comparison

Context:

  • The user asked for a source-only pass through every GR logic opcode compared with SQ2, plus the same style of comparison for object, view, and picture implementation code. Observable behavior checks were intentionally deferred.
  • All evidence in this pass comes from local game directories, the local SQ2 decrypted executable, local disassembly with ndisasm, and local parsing tools.

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n '1,220p' PROGRESS.md
  • sed -n '1,260p' docs/src/clean_room_executable_notes.md
  • tail -n 80 docs/src/progress_log.md
  • sed -n '1,220p' docs/src/symbolic_labels.md
  • ndisasm -b 16 -o 0x0000 -e 0x200 games/GR/AGI > build/gr-sq2-static/gr_agi_image.ndisasm
  • ndisasm -b 16 -o 0x0000 -e 0x200 build/cleanroom/SQ2_AGI.decrypted.exe > build/gr-sq2-static/sq2_agi_image.ndisasm
  • python3 -B tools/compare_gr_sq2_static.py --sq2-game-dir games/SQ2 --gr-game-dir games/GR --sq2-exe build/cleanroom/SQ2_AGI.decrypted.exe --gr-exe games/GR/AGI --output build/gr-sq2-static/opcode_static_report.md
  • python3 -B tools/compare_gr_sq2_static.py --help
  • python3 -B -m py_compile tools/compare_gr_sq2_static.py
  • python3 -B -m py_compile tools/compare_gr_sq2_static.py tools/disassemble_logic.py
  • AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py --stats
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/disassemble_logic.py --stats
  • mdbook build docs
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests
  • python3 -B tools/compatibility_suite.py
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py
  • xxd -g 1 -s 0x0762 -l 192 games/GR/AGIDATA.OVL
  • Local one-off Python census using tools.agi_resources to read all present SQ2 and GR picture/view resources and count transform types.
  • Focused ndisasm/sed reads over GR and SQ2 image ranges around action dispatch, condition dispatch, picture command dispatch, view selectors, object update lists, frame timers, motion helpers, and display refresh helpers.
  • xxd -g 1 -l 16 games/SQ2/OBJECT
  • xxd -g 1 -l 16 games/GR/OBJECT
  • xxd -g 1 -s 315 -l 16 games/SQ2/OBJECT
  • xxd -g 1 -s 1798 -l 16 games/GR/OBJECT

Generated local artifacts:

  • tools/compare_gr_sq2_static.py: deterministic comparison helper requiring explicit SQ2 and GR game/executable paths.
  • build/gr-sq2-static/gr_agi_image.ndisasm: full GR loaded-image disassembly.
  • build/gr-sq2-static/sq2_agi_image.ndisasm: full SQ2 loaded-image disassembly.
  • build/gr-sq2-static/opcode_static_report.md: generated static comparison report.

Documented result:

  • Shared action opcode table entries 0x00..0xaf have identical argument counts and operand metadata in SQ2 and GR.
  • Normalized handler-entry snippets match for 159 shared action opcodes and differ for 17 shared actions: 0x12, 0x6f, 0x73, 0x76, 0x77, 0x78, 0x79, 0x7c, 0x7d, 0x80, 0x84, 0x89, 0x8a, 0xa3, 0xa4, 0xa9, and 0xad.
  • The changed shared action snippets cluster around room switching, input/text/window handling, save/restart/inventory UI paths, key-map capacity, and two GR state bytes. Important static observations:
    • GR action 0x79 raises the key-map loop limit from 0x27 to 0x31.
    • GR actions 0xa3 and 0xa4 route to the generic no-op/return handler rather than setting/clearing SQ2’s input-width word.
    • GR action 0xad sets byte [0x0405] to 1; GR-only action 0xb5 sets the same byte to 0. SQ2 action 0xad increments byte [0x1530].
    • GR action 0x84 preserves object0 byte +0x22 when it is already 4; SQ2 clears that field unconditionally.
  • GR-only action slots 0xb0..0xb5 are present in the v3 action table. 0xb0, 0xb2, 0xb3, and 0xb4 route to the generic no-op/return handler after operand consumption. 0xb1 stores its one operand in word [0x0403]; local cross-references show later code tests [0x0403] before a menu/popup-like path. 0xb5 clears byte [0x0405].
  • Shared condition table entries 0x00..0x12 have identical parser contracts and no normalized handler-entry differences.
  • GR condition dispatch code compares predicate bytes with 0x26, but the bytes after the first 19 four-byte entries are not a confirmed handler table. Forced decoding of AGIDATA.OVL:0x07ae.. yields punctuation/filename bytes such as .,;:'!-, words.tok, and object, followed by zeros. The disassembler now treats only 0x00..0x12 as the structured condition table for the observed GR input.
  • The local GR logic census still has no parse errors after that conservative condition-table correction. Observed GR scripts use condition opcodes only through 0x0e.
  • Resource-reader implementation is the major known container difference: code.resource.load_all_directories and code.resource.read_volume_payload_once are different as expected because GR uses combined GRDIR, prefixed GRVOL.N, 7-byte headers, dictionary expansion, and picture-nibble expansion.
  • View runtime slices match as relocated skeletons after the v3 resource reader produces an expanded payload: view load/cache, object-view binding, group table selection, frame selection, and view discard all compared cleanly.
  • Picture runtime slices mostly match as relocated skeletons: load/cache, prepare/overlay/discard, command scan, all eleven picture command handlers 0xf0..0xfa, coordinate reads, line drawing, pixel write, seed fill, and pattern plotting. Differences are display-mode refresh paths: code.picture.decode_no_clear, code.display.fill_buffer_word, and code.display.full_refresh omit SQ2’s display-mode-2 overlay refresh branch in GR.
  • Object runtime slices mostly match as relocated skeletons: update-list sorting/insertion/draw/refresh, collision test, control acceptance, dirty-rectangle update, placement, active/inactive list rebuild/flush/refresh, and membership toggles. GR packages rectangle save/restore/draw routines in the main executable image (0x5b67, 0x5ba6, 0x5be3) while SQ2 uses object-overlay entry points in IBM_OBJS.OVL.
  • Static object/motion differences that still need semantic naming:
    • GR frame-timer update adds an extra helper-gated branch before using the four-plus-group direction table.
    • GR motion dispatch accepts one additional object mode selector (cmp ax,0x3 instead of SQ2’s cmp ax,0x2) before falling through to the same boundary-check tail.
    • Straight-line ndisasm differences inside code.object.advance_frame_by_mode and code.motion.rectangle_boundary_check are embedded jump-table bytes, not enough by themselves to claim behavioral differences; the surrounding branch bodies were manually inspected as relocated skeletons.
  • Local resource data census:
    • SQ2 pictures: 75 directory-present entries, 74 decoded direct payloads, one bad v2 header at picture 147.
    • SQ2 views: 203 present direct payloads.
    • GR pictures: 186 present payloads, all using the picture-nibble transform.
    • GR views: 247 present payloads, all using the dictionary transform.
  • OBJECT file bytes differ in length and content (games/SQ2/OBJECT length 331, games/GR/OBJECT length 1814), but the interpreter-side object-table initialization skeleton is the same: it reads/decrypts the file, computes inventory/object table roots, uses a 43-byte object-record stride, clears and initializes the object table, and seeds the same prompt/direction defaults after address relocation.
  • Verification: mdbook build docs passed. AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 251 tests. Running python3 -B tools/compatibility_suite.py without AGI_GAME_DIR failed at import time because the project now requires an explicit game directory; rerunning as AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py passed the same 251 tests and mdBook build.

2026-07-07: Version ledger and read-only fixture copy fix

Context:

  • The user asked for a Versions documentation chapter to track differences between observed interpreter versions.
  • The user also clarified that generated test/original-engine fixtures should never modify private files under games/; fixture builders should copy the selected game into build/ and modify that generated copy.

Commands run from /Users/peter/ai/agi/reverse:

  • rg -n "fixture|LOGDIR|VOL\\.3|games/|AGI_GAME_DIR|copytree|copy2|chmod|write_bytes|open\\(" tests tools docs/src PROGRESS.md AGENTS.md
  • sed -n '1,260p' tools/qemu_fixture.py
  • sed -n '620,980p' tools/qemu_fixture.py
  • sed -n '1,520p' tests/test_qemu_fixture.py
  • rg -n "shutil\\.copy|copytree|copy2|LOGDIR\\)\\.write|VOL\\.3\\)\\.write|PICDIR\\)\\.write|VIEWDIR\\)\\.write|AGIDATA\\.OVL\\)\\.write|destination / \\\"LOGDIR\\\"|fixture / \\\"LOGDIR\\\"|games/" tools tests
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_qemu_fixture.py
  • mdbook build docs
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests

Documented result:

  • Added docs/src/versions.md and linked it from docs/src/SUMMARY.md. The chapter currently records SQ2 / AGI 2.936 and Gold Rush / AGI 3.002.149 differences: executable form, resource container, compression/transform paths, dispatch-table range, and generated-fixture readiness.
  • Updated tools/qemu_fixture.py so generated fixture copies are writable even when the selected game input is read-only. The shared copy primitive now cleans generated destinations, preserves .ppm capture files, copies source files, and adds owner read/write permission on the copy before patching.
  • Added a fixture-destination guard: paths under repository games/ are rejected before any generated directory is created or patched. Destinations that are the selected game directory, a child of it, or a parent of it are rejected for the same reason.
  • Added focused tests proving that a read-only copied LOGDIR can be patched in the fixture copy, that a destination under games/ is rejected, and that a destination parent of the selected game is rejected.
  • Updated AGENTS.md to state the immutable-games/ rule explicitly.
  • Verification passed: mdbook build docs, the focused tests/test_qemu_fixture.py slice, and the full local unit suite with AGI_GAME_DIR=games/SQ2 (251 tests).

2026-07-07: Gold Rush v3 extra action opcode source pass

Context:

  • The user asked to figure out the extra opcodes. This pass focuses on the Gold Rush / AGI v3 action slots beyond the SQ2 action table, 0xb0..0xb5, using local disassembly and local decoded resources.
  • No external AGI documentation or source was consulted.
  • No targeted QEMU fixture was run for this pass. The current generated original-engine fixture writers are still v2/SQ2-container-oriented, while the relevant GR effects were directly visible in the opcode handlers and their local consumers. Observable v3 fixture tests remain useful later.

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • rg -n "0xb0|0xb1|0xb2|0xb3|0xb4|0xb5|0x0403|0x0405|GR-only|extra action|extra opcodes|v3-only" PROGRESS.md docs/src tools tests
  • sed -n '1,260p' tools/compare_gr_sq2_static.py
  • sed -n '1,240p' tools/agi_resources.py
  • rg -n "0403|0405|970B|9724|63A8|63B0|000097|000063A|000064" build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '11140,11320p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '17280,17480p' build/gr-sq2-static/gr_agi_image.ndisasm
  • Local Python using tools.compare_gr_sq2_static.load_table to print GR action-table entries 0xb0..0xb5.
  • sed -n '6120,6260p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '17030,17240p' build/gr-sq2-static/gr_agi_image.ndisasm
  • rg -n "1b67|1b71|1b73|1b75|1b79|1b77|1b7b|1b7d|1b7f|1b81|1b83|1b85|1b87|1b88|03f9|03fd|0403|0405" build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '16680,17040p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '9240,9320p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '1,240p' tests/test_logic_doc_coverage.py
  • rg -n "0x403|0x405|\\[0x403\\]|\\[0x405\\]" build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '11180,11340p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '17030,17230p' build/gr-sq2-static/gr_agi_image.ndisasm

Observed GR action-table entries:

OpcodeHandlerArgsMetadataSource-backed interpretation
0xb00x528600x00Reserved/no-op slot. Handler 0x5286 only returns the bytecode pointer passed to it.
0xb10x970b10x00Reads one immediate byte, zero-extends it, and stores it in word [0x0403].
0xb20x528600x00Reserved/no-op slot.
0xb30x528640x00Reserved/no-op slot after four table-declared fixed operands.
0xb40x528620xc0Reserved/no-op slot after two table-declared variable operands.
0xb50x63b000x00Stores zero in byte [0x0405].

Detailed observations:

  • Generic handler 0x5286 saves/restores registers, loads AX from [bp+0x8], and returns. It has no state writes and no operand reads.
  • Handler 0x970b (0xb1) reads the byte at the incoming bytecode pointer, increments the pointer, zero-extends the byte, stores the word at [0x0403], and returns the incremented pointer.
  • GR menu interaction routine 0x9724 begins with cmp word [0x403],0 and returns without drawing/waiting if the word is zero. The same routine otherwise draws the menu structure rooted at [0x1b71], waits for input, navigates enabled menu nodes, and enqueues type-3 item events through 0x46f4.
  • Existing shared menu actions build and request the menu separately: 0x9c builds menu headings in the linked structure rooted at [0x1b71], 0x9d adds menu items, 0x9e finalizes the structure, 0x9f/0xa0 enable/disable items, and 0xa1 sets request word [0x1b67] when flag 0x0e is set. The main cycle path calls 0x9724 only when [0x1b67] is nonzero, so 0xb1 is a separate interaction gate rather than a menu-build opcode.
  • GR shared action 0xad at 0x63a8 stores one in byte [0x0405]. GR-only action 0xb5 at 0x63b0 stores zero in the same byte.
  • The GR keyboard interrupt hook at 0x63b8 tests [0x0405] on selected tracked-key release paths. When the byte is nonzero, it calls event enqueue helper 0x46f4 with type 2 and value 0.

Documentation/tooling updates from this pass:

  • Added v3-specific action names in tools/disassemble_logic.py without changing the SQ2 ACTION_NAMES catalog: set_menu_interaction_gate for 0xb1, clear_key_release_event_gate for 0xb5, and reserved/no-op names for 0xb0, 0xb2, 0xb3, and 0xb4.
  • Updated tools/compare_gr_sq2_static.py notes so the generated static report records the local consumers of [0x0403] and [0x0405].
  • Updated PROGRESS.md, docs/src/versions.md, docs/src/logic_bytecode.md, and docs/src/symbolic_labels.md with the v3-only opcode interpretations and new symbolic data labels data.menu.interaction_gate_0403 and data.input.key_release_enqueue_gate_0405.

Verification after the documentation/tooling updates:

  • Regenerated build/gr-sq2-static/opcode_static_report.md with python3 -B tools/compare_gr_sq2_static.py --sq2-game-dir games/SQ2 --gr-game-dir games/GR --sq2-exe build/cleanroom/SQ2_AGI.decrypted.exe --gr-exe games/GR/AGI --output build/gr-sq2-static/opcode_static_report.md.
  • AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py --stats parsed the local GR logic resources with no errors.
  • python3 -B -m py_compile tools/disassemble_logic.py tools/compare_gr_sq2_static.py passed.
  • mdbook build docs passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 251 tests.
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py passed the same 251 tests and mdBook build.
  • git diff --check passed.

2026-07-09: Gold Rush / SQ2 shared action delta source pass

Context:

  • The user asked to update PROGRESS.md with the planned GR/SQ2 comparison items, then continue.
  • This pass used local disassembly first. No external AGI documentation/source and no QEMU confirmation were used.
  • Inputs were the already generated build/gr-sq2-static/*_agi_image.ndisasm files plus exact-offset disassembly from local executable bytes where linear ndisasm swallowed handler-entry bytes as neighboring inline data.

Commands run from /Users/peter/ai/agi/reverse:

  • git status --short
  • sed -n '1,260p' PROGRESS.md
  • sed -n '1,260p' docs/src/versions.md
  • sed -n '1,260p' docs/src/symbolic_labels.md
  • sed -n '1,220p' docs/src/progress_log.md
  • sed -n '1,260p' tools/compare_gr_sq2_static.py
  • tail -n 180 docs/src/clean_room_executable_notes.md
  • rg -n "0x6f|0x73|0x76|0x77|0x78|0x79|0x7c|0x7d|0x80|0x84|0x89|0x8a|0xa3|0xa4|0xa9|0xad|0xb1|0xb5|Gold Rush|GR / SQ2|v3" docs/src/logic_bytecode.md docs/src/versions.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md PROGRESS.md
  • Local Python using tools.compare_gr_sq2_static.load_table to print the SQ2/GR action-table entries for the changed shared action set.
  • sed -n '8548,8628p' build/gr-sq2-static/sq2_agi_image.ndisasm
  • sed -n '8768,8855p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '10900,11040p' build/gr-sq2-static/sq2_agi_image.ndisasm
  • sed -n '11272,11445p' build/gr-sq2-static/gr_agi_image.ndisasm
  • Exact-offset local Python disassembly for handlers 0x175c, 0x19d4, 0x31d8, 0x351e, 0x2753, 0x29e5, 0x2472, 0x26e0, 0x7041, and 0x73b9.
  • AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py --limit 256 | rg "switch_room_like|0x12|\\broom"
  • Exact-offset local Python disassembly for GR helpers 0x0062, 0x07bc, 0x169b, 0x11b3, 0x341c, 0x9648, 0x3b00, and 0x3ab0.
  • rg -n "0dc1|DC1|\\[0xdc1\\]|03f9|0dc3|0dc5" build/gr-sq2-static/gr_agi_image.ndisasm docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md
  • Exact-offset local Python disassembly for SQ2/GR code.object.frame_timer_update, code.motion.pre_mode_and_boundary_update, code.motion.dispatch_mode_step, and code.motion.rectangle_boundary_check.
  • Local Python byte dump of the SQ2 and GR motion-mode dispatch tables at image offsets 0x06ad and 0x06bd.

Input/text cluster observations:

  • GR action-table entries match SQ2 parser contracts for actions 0x6f, 0x73, 0x76, 0x77, 0x78, 0x89, 0x8a, 0xa3, 0xa4, and 0xa9, but the handler bodies remove SQ2’s display-mode-2/input-width branches.
  • SQ2 0x6f at image 0x78f0 stores input-line bounds and computes display offset [0x1379] with an alternate display-mode-2 branch. GR 0x6f at image 0x7c24 stores relocated globals and computes display offset [0x11b1] = arg0 << 3 unconditionally.
  • SQ2 string/number prompt actions 0x73 and 0x76 have alternate paths for display mode [0x1130] == 2 when input-width word [0x0d0f] == 0. GR prompt handlers 0x0e92 and 0x756b use the normal prompt/editor path only.
  • SQ2 0x77, 0x78, 0x89, and 0x8a test display mode and input-width state before clearing/redrawing/refreshing/erasing. GR handlers 0x3b0c, 0x3b2e, 0x3a48, and 0x3a29 use the normal relocated input buffers and visible row helpers without those special branches.
  • SQ2 actions 0xa3 and 0xa4 set/clear word [0x0d0f]; GR maps both table entries to the generic no-op/return handler 0x5286.
  • SQ2 action 0xa9 at 0x1f2b restores active saved-window state and clears both [0x0d0f] and [0x0d1d]. GR action 0xa9 at 0x21a2 restores the relocated active saved-window rectangle and clears only active word [0x0b24].

Event/key/menu observations:

  • SQ2 action 0x79 at image 0x4c3d reads a two-byte key word and one mapped value, then scans up to 0x27 four-byte slots rooted at [0x0145] for the first empty key word. GR action 0x79 at 0x4e98 is the same shape but scans up to 0x31 slots.
  • SQ2 action 0xad at 0x602f increments byte [0x1530]. The SQ2 keyboard IRQ hook tests [0x1530] != 0 before enqueueing (type=2, value=0) on selected tracked-key release paths.
  • GR action 0xad at 0x63a8 sets byte [0x0405] = 1; GR-only action 0xb5 at 0x63b0 clears the same byte. GR keyboard IRQ hook 0x63b8 tests [0x0405] before the selected key-release enqueue.
  • GR-only action 0xb1 at 0x970b stores its immediate operand in word [0x0403]. GR menu interaction routine 0x9724 returns immediately while [0x0403] == 0, so this is a separate interaction gate after menu request state has been set.

Room/inventory/save/restart/object-state observations:

  • SQ2 action 0x12 at image 0x175c reads the immediate room byte and calls room-switch helper 0x1792. GR action 0x12 at 0x19d4 calls helper 0x0062 first: bytes below 0x7e or above 0x80 pass through unchanged, while bytes 0x7e, 0x7f, and 0x80 return 0x49.
  • Decoded local GR scripts contain switch_room_like(#126), switch_room_like(#127), and switch_room_like(#128), so the GR remap is live behavior for this interpreter/game pair, not dead code.
  • SQ2 action 0x7c at 0x31d8 enters the carried-item selector through the established text/input save-restore path. GR action 0x7c at 0x351e follows the relocated skeleton but clears word [0x0dc1] before return. The selector helper sets [0x0dc1] = 1 while handling the flag-13 interactive input path, so the current label is a temporary selector/input gate.
  • SQ2 save action 0x7d at 0x2753 writes the known five-block envelope. GR save action 0x7d at 0x29e5 writes the relocated five-block envelope but calls helper 0x07bc over the object/inventory chunk before and after the save writes. Helper 0x07bc XORs a caller-supplied byte range with repeating key bytes at data address DS:0x072c until the key byte is zero.
  • SQ2 restart action 0x80 at 0x2472 redraws the prompt marker at the end of the accepted path. GR restart action 0x80 at 0x26e0 first records the prompt-marker visible word through helper 0x3b00, erases the marker, and redraws it when restart was accepted or, after a canceled restart, only if the marker had been visible before entry.
  • SQ2 action 0x84 at 0x7041 sets [0x0139] = 1 and clears object 0 byte +0x22 unconditionally. GR action 0x84 at 0x73b9 sets [0x0139] = 1 but skips the clear when object 0 byte +0x22 is already 4.

Object/motion observations:

  • SQ2 code.object.frame_timer_update at 0x0563 uses the direction-to-loop table for object views with group count byte +0x0b >= 4 when bit 0x2000 is clear. GR 0x055c keeps the two/three-group table path, but the four-plus path is split: exactly four groups uses the direction table without the new flag gate, and more than four groups uses the direction table only when flag 0x14 is set. A later targeted QEMU probe in these notes corrected the earlier no-auto-select shorthand for the exactly-four case.
  • SQ2 code.motion.dispatch_mode_step at 0x067a accepts modes 1..3 after decrementing object byte +0x22. GR 0x068a accepts modes 1..4. The GR jump table at 0x06bd maps mode 1 to random motion, mode 2 to approach-first-object, and both modes 3 and 4 to the target-direction helper.
  • The surrounding GR pre-mode/boundary and rectangle-boundary helpers remain relocated skeletons of the SQ2 logic after accounting for embedded jump-table bytes in linear disassembly.

Documentation/tooling updates from this pass:

  • Updated PROGRESS.md with the requested ordered comparison queue, then replaced the queue with source-pass status and the remaining v3 fixture work.
  • Refined tools/compare_gr_sq2_static.py changed-action notes so regenerated reports describe the now-source-backed deltas.
  • Updated docs/src/versions.md, docs/src/logic_bytecode.md, docs/src/runtime_model.md, and docs/src/symbolic_labels.md with the source-backed GR/SQ2 differences and new GR address associations.

v3 Direct Logic Fixture Writer

Goal: turn the source-backed Gold Rush / AGI v3 deltas into testable behavior without modifying private inputs under games/.

Commands and local reads:

  • sed -n '1,1240p' tools/qemu_fixture.py
  • sed -n '1,760p' tools/agi_resources.py
  • sed -n '1,620p' tests/test_qemu_fixture.py
  • sed -n '1,90p' tools/project_paths.py
  • rg -n "copy_game_tree|build_.*fixture|patch_dir_entry|SQ2|AGI_GAME_DIR|--game-dir|detect_layout|v3|GRDIR|GRVOL" tools tests docs/src PROGRESS.md
  • Local Python detect_layout(games/GR) / read_directory_entries(...) inspection.
  • AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py --stats
  • Local Python count of GR combined-directory slots and present entries.
  • python3 -B -m py_compile tools/qemu_fixture.py tools/agi_resources.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_qemu_fixture.py tests/test_agi_resources.py

Observations:

  • tools/qemu_fixture.py previously imported SQ2 from tools/disassemble_logic.py, which made fixture construction depend on the selected game at module import time and hid an SQ2-oriented source path inside copy helpers.
  • The copy path now accepts game_dir=... explicitly or uses AGI_GAME_DIR through project_paths.game_dir() only when the caller omits a source. Fixture destinations are still rejected under games/ and when they would overwrite the selected source game.
  • Existing v2 fixture builders retain the split-directory VOL.3 packing behavior. The compatibility alias copy_sq2_tree() remains for older probes, but it no longer imports or references a global SQ2 path.
  • Added v3_volume_record(...) for the observed direct/uncompressed v3 header: 12 34 metadata expanded_len stored_len payload. The writer uses equal expanded and stored lengths and keeps the metadata low nibble aligned with the patched directory volume.
  • Added patch_combined_dir_entry(...) for v3 combined directories. It uses the section offsets and section ends recovered by detect_layout() and patches the selected resource entry at section_offset + resource_no * 3.
  • Added build_v3_logic_fixture(...). It copies the selected v3 game into the generated destination, detects the copied combined layout, appends a direct record to the existing prefixed volume for the selected logic resource, and patches that logic entry in the combined directory.
  • Added python3 -B tools/qemu_fixture.py v3-logic payload.bin --game-dir ... as a reusable CLI wrapper for the direct logic fixture path.
  • The test fixture uses a tiny synthetic GRDIR/GRVOL.1 layout, not private game files, to prove the direct-record append and directory patch can be read back through read_volume_record(...).
  • The current local GR combined-directory counts are: logic 245 slots / 182 present, picture 245 slots / 186 present, view 256 slots / 247 present, and sound 51 slots / 44 present.

Status:

  • Basic v3 logic fixture writing is implemented and covered by focused unit tests.
  • Generated v3 picture/view packing is intentionally still absent. Add it only when a targeted behavior probe needs generated picture or view payloads rather than original game resources.
  • Verification after the CLI/docs update: python3 -B -m py_compile tools/qemu_fixture.py tools/agi_resources.py, AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests (254 tests), mdbook build docs, AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py, and git diff --check all passed.

Gold Rush v3 Room-Remap Behavior Probe

Goal: convert the source-backed GR action 0x12 room-target remap into an original-engine compatibility check.

Commands and local reads:

  • AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py 73
  • AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py 0
  • python3 -B tools/setup_freedos_image.py --force
  • python3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --output build/gr-v3-behavior/room_remap_build_001.json
  • python3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --run-qemu --output build/gr-v3-behavior/room_remap_qemu_001.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/gr_v3_behavior_probe.py --probe direct-draw --game-dir games/GR --picture 1 --fixture-root build/gr-v3-behavior/direct-draw-fixtures --output build/gr-v3-behavior/direct_draw_pic001_qemu_001.json --run-qemu --boot-wait 5 --draw-wait 8
  • python3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --picture 1 --run-qemu --output build/gr-v3-behavior/room_remap_dispatch_qemu_pic001_001.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --picture 1 --run-qemu --output build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/inspect_ppm.py on the generated QEMU captures.

Probe construction:

  • A direct draw sanity fixture patches GR logic 0 to picture_logic_payload(1). QEMU capture build/gr-v3-behavior/direct-draw-fixtures/direct_draw/qemu_capture.ppm was nonblank (14 unique colors), proving that direct v3 logic replacement executes under the original GR interpreter.
  • The first room-remap fixture pair only patched logic 0 to switch rooms and logic 0x49 to draw a marker picture. It produced equal but all-black captures. This was not accepted as evidence.
  • The failure exposed an important harness requirement: replacing logic 0 removes the original global dispatch tail. GR logic 0 normally reaches call_logic_var(v0) near bytecode offset 0x0ca0, so a custom logic 0 that switches rooms must also continue dispatching the current room.
  • The corrected switch_room_payload() fires the switch once behind guard variable v250, then executes call_logic_var(v0) and end on each cycle. Logic 0x49 in both fixtures is patched to draw picture 1.

QEMU result:

  • Report: build/gr-v3-behavior/room_remap_dispatch_qemu_pic001_001.json.
  • Direct target fixture: logic 0 uses switch_room_like(#0x49).
  • Alias target fixtures: logic 0 uses switch_room_like(#0x7e), switch_room_like(#0x7f), or switch_room_like(#0x80).
  • All fixtures patch logic 0x49 to the same picture-display payload.
  • The expanded QEMU report is build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json; all alias captures match direct target 0x49.
  • The four QEMU captures are byte-identical: 45518c409f738a1fb2f4233db202f64d2e0e94011a9559e8ace0d952362814ab.
  • inspect_ppm reports all four captures as 640x400, 14 unique colors, and non-background bounding box (0, 0, 639, 399).

Conclusion:

  • The source-backed GR helper code.room.remap_reserved_room_target is now dynamically validated for 0x7e, 0x7f, and 0x80 all mapping to 0x49.
  • Final verification for this pass: python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py, AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests (257 tests), mdbook build docs, AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py, and git diff --check all passed.

Gold Rush v3 Key-Map Capacity Behavior Probe

Goal: convert the source-backed GR action 0x79 key-map capacity delta into an observable original-engine check.

Commands and local reads:

  • rg -n "00004E9[0-9A-F]|00004EA[0-9A-F]|00004EB[0-9A-F]|00004EC[0-9A-F]|00004ED[0-9A-F]|00004EE[0-9A-F]|00004EF[0-9A-F]" build/gr-sq2-static/gr_agi_image.ndisasm
  • rg -n "000063A[0-9A-F]|000063B[0-9A-F]|000063C[0-9A-F]|000063D[0-9A-F]|000063E[0-9A-F]" build/gr-sq2-static/gr_agi_image.ndisasm
  • rg -n "0000970[0-9A-F]|0000971[0-9A-F]|0000972[0-9A-F]|0000973[0-9A-F]|0000974[0-9A-F]|0000975[0-9A-F]|0000976[0-9A-F]" build/gr-sq2-static/gr_agi_image.ndisasm
  • python3 -B tools/gr_v3_behavior_probe.py --probe key-map-capacity --game-dir games/GR --picture 1 --fixture-root build/gr-v3-behavior/key-map-capacity-fixtures --dos-prefix GRK --run-qemu --output build/gr-v3-behavior/key_map_capacity_qemu_pic001_002.json --boot-wait 5 --draw-wait 8
  • python3 -B tools/inspect_ppm.py on each generated capture.

Source observations:

  • GR action 0x79 at image 0x4e98 reads two operand bytes into a little-endian key/event word, reads one mapped status value, then scans slots rooted at [0x0145].
  • The GR loop compares DI with 0x31, so it can fill slots 0..48. The SQ2 source-backed comparison showed the same handler shape but a loop bound of 0x27, so SQ2 fills slots 0..38.
  • GR action 0xad at image 0x63a8 stores byte [0x0405] = 1, GR-only action 0xb5 at image 0x63b0 stores [0x0405] = 0, and the GR keyboard IRQ hook at 0x63b8 tests [0x0405] before enqueueing a type-2 zero event on the selected scan-code release path.
  • GR-only action 0xb1 at image 0x970b stores its immediate operand into word [0x0403]; code.menu.interact at 0x9724 returns immediately while that word is zero.

Probe construction:

  • The new key_map_capacity_payload() emits 48 dummy 0x79 mappings, then emits 0x79('x', 0, 7) as the 49th mapping. The generated payload has 49 occurrences of opcode 0x79, and the target mapping appears after 48 earlier mapping opcodes.
  • The positive fixture patches copied GR logic 0 with that payload, sends typed key x through the QEMU monitor, and draws original GR picture 1 only when status byte 7 is observed.
  • The direct fixture patches logic 0 to draw picture 1 immediately.
  • The no-key control uses the same slot-48 mapping payload but sends no key.

QEMU result:

  • Report: build/gr-v3-behavior/key_map_capacity_qemu_pic001_002.json.
  • Expected matches: slot_48_key_map should match direct_picture; slot_48_no_key should not.
  • The report matches those expectations exactly.
  • Direct and keyed captures are byte-identical PPM files with SHA-256 45518c409f738a1fb2f4233db202f64d2e0e94011a9559e8ace0d952362814ab.
  • inspect_ppm reports direct and keyed captures as 640x400, 14 unique colors, and non-background bounding box (0, 0, 639, 399).
  • The no-key capture has one unique color and no non-background bounding box.

Conclusion:

  • The source-backed GR key-map loop bound of 0x31 is now dynamically validated for the final slot, showing that a mapping beyond SQ2’s 0x27 slot count is observable through the original GR interpreter’s event/status path.
  • The [0x0405] key-release gate and [0x0403] menu-interaction gate remain source-backed; add raw scan-code/menu timing probes only if the final spec needs observable confirmation for those gate paths.
  • Final verification for this pass: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests (259 tests), mdbook build docs, python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py, git diff --check, and AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py all passed.

Gold Rush v3 Save Object/Inventory XOR Model

Goal: turn the source-backed GR save transform into implementation-ready helper code and tests.

Commands and local reads:

  • sed -n '1,260p' tools/save_roundtrip_probe.py
  • sed -n '1,240p' tools/agi_save.py
  • python3 -B tools/disassemble_logic.py --game-dir games/GR --limit 256 | rg "verify_game_signature|save_game_state|restore_game_state|copy_save_description|0x8f|0x7d|0x7e|0xaa"
  • Exact-offset local Python/ndisasm reads for GR image offsets 0x29e5, 0x2aba, 0x2b5b, 0x2b7c, 0x07bc, and 0x2792.
  • Local Python byte read of the sequence starting at numeric image offset 0x072c; this was later recognized as a segment-confusion error, because the helper uses DI = 0x072c as a data-segment address.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_save_resources.py
  • python3 -B -m py_compile tools/agi_save.py

Source observations:

  • GR save action 0x7d at image 0x29e5 begins by computing [0x07d6] + [0x07da], then calls helper 0x07bc(start=[0x07d6], end=[0x07d6]+[0x07da]).
  • The same action calls 0x07bc over the same range again at 0x2b61 before returning, after the selector/file I/O cleanup path.
  • The write sequence calls length-prefixed writer 0x2b7c for five blocks. The third call writes start [0x07d6] with length [0x07da], so the XORed range is exactly the third saved state block.
  • Helper 0x07bc initializes DI = 0x072c, XORs each byte in the caller range with byte [DI], increments both pointers, and when byte [DI] is zero it resets DI to 0x072c.
  • Rechecking the addressing against games/GR/AGIDATA.OVL shows that data-segment address 0x072c contains the zero-terminated ASCII text Avis Durgan. The earlier 59-byte byte sequence came from reading main-code bytes at the same numeric offset and is not the save transform key.

Implementation/test updates:

  • Added GR_V3_OBJECT_INVENTORY_XOR_KEY, xor_with_repeating_key(...), and gr_v3_object_inventory_save_xor(...) to tools/agi_save.py.
  • Added tests proving the GR transform uses the exact Avis Durgan key, wraps after byte 10, matches the original-engine save prefix known vector, round trips, and rejects an empty generic XOR key.

Conclusion:

  • The GR v3 save transform is now source-backed and executable in the local save helper model. The current model describes the on-disk third block as the Avis Durgan XOR-transformed form of the runtime object/inventory block, with the second in-memory pass restoring runtime bytes before action return.
  • A later QEMU save-file extraction probe promoted the source-backed transform to original-engine evidence; see the next section. The promoted fixture uses a blank save prefix and does not resolve the GR verifier/save-prefix path.
  • Final verification for this pass: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests (262 tests), mdbook build docs, python3 -B -m py_compile tools/agi_save.py tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py, git diff --check, and AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py all passed.

Gold Rush v3 Save Extraction Probe

Goal: confirm the source-mapped GR save XOR transform against a save file written by the original interpreter.

Commands and local reads:

  • sed -n reads of PROGRESS.md, tools/gr_v3_behavior_probe.py, tools/save_roundtrip_probe.py, tools/qemu_fixture.py, tools/agi_save.py, and the relevant tests.
  • Initial local Python parsing used the wrong message offset base and led to the provisional, later-corrected hypothesis that GR message text might be plain in resource bytes. A follow-up pass below corrected this: GR logic message text is encrypted in the same observed message-text region.
  • strings -a -t x games/GR/AGIDATA.OVL and xxd -s 0x0700 -l 0x260 -g 1 games/GR/AGIDATA.OVL confirmed the same message XOR key text exists in GR data at offset 0x072c, while GR logic message resources observed in this pass are already readable.
  • ndisasm reads around GR image offsets 0x108c, 0x245e, 0x5035, and 0x5ede, plus byte reads around file offset 0x60d7, confirmed the local 0x8f verifier/copy shape and embedded verifier string bytes.
  • Local script scan found GR’s original 0x8f use in logic 101 at bytecode offset 0x0004, with message number 3. The raw encrypted bytes for that message are 35 35 61; after decrypting from the message text-region start, the message is GR\0.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_qemu_fixture
  • python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tests/test_gr_v3_behavior_probe.py tests/test_qemu_fixture.py
  • Initial exploratory QEMU run with a synthetic 0x8f("GR") fixture exited before saving; mdir found no save file, and the capture showed the later typed description at DOS. This result was not promoted as save behavior.
  • Promoted QEMU run:
python3 -B tools/gr_v3_behavior_probe.py --probe save-xor-extract --game-dir games/GR --fixture-root build/gr-v3-behavior/save-xor-fixtures --dos-prefix GRS --run-qemu --output build/gr-v3-behavior/save_xor_extract_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/save_xor_extract.raw --snapshot-qcow build/gr-v3-behavior/snapshot/save_xor_extract.qcow2 --post-run-raw build/gr-v3-behavior/snapshot/save_xor_extract_after.raw --save-output build/gr-v3-behavior/SG_001.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08

Implementation/test updates:

  • tools/qemu_fixture.py now lets generated logic resources opt out of SQ2-style message text encryption with encrypt_messages=False; the default encrypted behavior remains correct for observed SQ2 and GR logic resources.
  • tools/gr_v3_behavior_probe.py now has --probe save-xor-extract. The promoted fixture omits 0x8f verify_game_signature, so it writes a blank-prefix SG.1 save and keeps the test focused on action 0x7d.
  • tests/test_qemu_fixture.py covers encrypted-default and plain-message logic-resource construction.
  • tests/test_gr_v3_behavior_probe.py covers the GR save extraction payload, the optional verifier-message form, and stale save removal in copied fixtures.
  • tools/compatibility_suite.py now has an opt-in qemu-v3 layer containing the GR save-XOR extraction probe; it is intentionally separate from the SQ2-oriented smoke/broad layers because it depends on private games/GR.

QEMU result:

  • Report: build/gr-v3-behavior/save_xor_extract_qemu_001.json.
  • Suite-level report: build/compatibility-suite/qemu_v3_save_001.json, whose named gr_save_xor_extract_qemu command returned zero and wrote build/gr-v3-behavior/save_xor_extract_suite.json.
  • Extracted save: build/gr-v3-behavior/SG_001.1.
  • Description: codex gr probe.
  • Block lengths: 1028, 989, 1811, 100, and 12.
  • First block begins with a blank signature prefix, as expected for a fixture that does not call 0x8f.
  • The third block’s on-disk prefix is c87769f82158e57363fb6f5dd6686f91457dca6606ac4011.
  • After gr_v3_object_inventory_save_xor(), the third block prefix is 8901008b011c9001049a011ca0011cb10108b80167c20167.
  • Applying the same XOR helper a second time restores the emitted third-block bytes. The report marks all checks passed.

Conclusion:

  • GR action 0x7d is now both source-backed and original-engine validated for the v3 save envelope and third-block XOR transform.
  • This first promoted QEMU evidence deliberately avoids GR’s verifier/save-prefix path; the following correction section covers that path.

Gold Rush v3 Signed Save Extraction Correction

Goal: correct the GR message-encoding hypothesis and promote the 0x8f("GR") save-prefix path to original-engine evidence.

Commands and local reads:

  • python3 -B tools/disassemble_logic.py --game-dir games/GR 101
  • Exact-offset ndisasm reads around GR image offsets 0x108c, 0x245e, 0x5035, and 0x5ec2..0x5eff.
  • Local Python parse of GR logic 101’s message area using table-base-relative offsets and decryption from the start of the message text region.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_qemu_fixture tests.test_compatibility_suite
  • python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/compatibility_suite.py tests/test_gr_v3_behavior_probe.py tests/test_compatibility_suite.py
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --dry-run --include-qemu-v3
  • Signed QEMU run:
python3 -B tools/gr_v3_behavior_probe.py --probe save-xor-extract --verify-signature --game-dir games/GR --fixture-root build/gr-v3-behavior/save-xor-signed-fixtures --dos-prefix GRS --run-qemu --output build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/save_xor_extract_signed.raw --snapshot-qcow build/gr-v3-behavior/snapshot/save_xor_extract_signed.qcow2 --post-run-raw build/gr-v3-behavior/snapshot/save_xor_extract_signed_after.raw --save-output build/gr-v3-behavior/GRSG_001.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08

Source observations:

  • GR action 0x8f at image 0x108c reads the immediate message number, resolves it through message helper 0x245e, copies from that message pointer into DS:0x0002 with bounded copy helper 0x5035, and then calls verifier helper 0x5ede.
  • Helper 0x5ec2 copies the embedded GR\0 string from code offset 0x5ed7 into data buffer 0x0f88. Helper 0x5ede compares DS:0x0002 against the embedded code string and calls the shared exit helper on the first mismatch.
  • GR logic 101 uses 0x8f(#3) near bytecode offset 0x0004. Its message table has encrypted text bytes 6f 76 4c 14 16 7d 75 35 35 61; decrypting the text region yields messages .\0, %g69\0, and GR\0. The earlier synthetic signed fixture failed because it stored GR\0 in plain text, so the loader decrypted it into the wrong runtime bytes before the verifier compared it.

Implementation/test updates:

  • gr_save_extract_payload(verify_signature=True) now uses the normal encrypted-message default instead of encrypt_messages=False.
  • tools/gr_v3_behavior_probe.py --probe save-xor-extract --verify-signature now expects GRSG.N, checks that the first save-state block starts with GR\0, and reports signature_prefix: "GR".
  • tools/compatibility_suite.py adds named command gr_signed_save_xor_extract_qemu in the opt-in qemu-v3 layer.
  • tests/test_gr_v3_behavior_probe.py now asserts that the verifier message is encrypted in the fixture payload and decrypts to GR\0.

QEMU result:

  • Report: build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json.
  • Extracted save: build/gr-v3-behavior/GRSG_001.1.
  • Expected save file inside DOS: GRSG.1.
  • Description: codex gr probe.
  • Block lengths: 1028, 989, 1811, 100, and 12.
  • First block prefix bytes: 47 52 00 00 00 00 00 00.
  • Third-block encoded prefix and SHA-256 match the blank-prefix run: c87769f82158e57363fb6f5dd6686f91457dca6606ac4011 and 00c9fc2f1cc1ff71f2779804f993dea7389227c486a016556c45a9a0fb63f6a8.
  • Third-block decoded prefix and SHA-256 also match the blank-prefix run: 8901008b011c9001049a011ca0011cb10108b80167c20167 and 5a833f40a62fc2e367e60600592d8033219586797a3e0a1b3a142accb64bc237.

Conclusion:

  • GR’s 0x8f verifier/save-prefix path is now source-backed and original-engine validated for save creation. The correct generated fixture shape uses encrypted logic-message text, just like observed GR logic 101.
  • A follow-up pass below validates the restore side of the same signed save path.

Gold Rush v3 Signed Restore Round Trip

Goal: validate the source-mapped GR restore path for a signature-prefixed GRSG.1 save without treating malformed save data as part of the behavioral model.

Commands and local reads:

  • git status --short
  • rg -n "Highest-Value|signed|restore|Gold Rush|v3" PROGRESS.md docs/src/clean_room_executable_notes.md docs/src/runtime_model.md docs/src/versions.md docs/src/compatibility_testing.md docs/src/symbolic_labels.md docs/src/progress_log.md
  • sed -n reads of tools/gr_v3_behavior_probe.py, tools/save_roundtrip_probe.py, tools/qemu_fixture.py, tools/qemu_snapshot.py, tools/compatibility_suite.py, and the focused tests.
  • rizin -q -a x86 -b 16 -c "pd 145 @ 0x2994" games/GR/AGI
  • rizin -q -a x86 -b 16 -c "pd 75 @ 0x2b44" games/GR/AGI
  • rizin -q -a x86 -b 16 -c "pd 35 @ 0x2ac8" games/GR/AGI
  • rizin -q -a x86 -b 16 -c "pd 35 @ 0x09be" games/GR/AGI
  • A prior exploratory rizin read without explicit -a x86 -b 16 decoded the bytes as the host architecture. That output was discarded and not used as evidence.
  • python3 -B -m py_compile tools/gr_v3_behavior_probe.py tests/test_gr_v3_behavior_probe.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe
  • python3 -B tools/gr_v3_behavior_probe.py --probe signed-restore-roundtrip --game-dir games/GR --fixture-root build/gr-v3-behavior/signed-restore-dryrun-fixtures --dos-prefix GRT --output build/gr-v3-behavior/signed_restore_roundtrip_dryrun_001.json
  • Direct QEMU run:
python3 -B tools/gr_v3_behavior_probe.py --probe signed-restore-roundtrip --game-dir games/GR --fixture-root build/gr-v3-behavior/signed-restore-qemu-fixtures --dos-prefix GRT --run-qemu --output build/gr-v3-behavior/signed_restore_roundtrip_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/signed_restore_roundtrip_001.raw --snapshot-qcow build/gr-v3-behavior/snapshot/signed_restore_roundtrip_001.qcow2 --post-run-raw build/gr-v3-behavior/snapshot/signed_restore_roundtrip_after_001.raw --save-output build/gr-v3-behavior/GRSG_restore_001.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suite
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --dry-run --include-qemu-v3
  • The first suite-wrapper run of gr_signed_restore_roundtrip_qemu failed before boot because sandboxed QEMU could not bind VNC (Failed to bind socket: Operation not permitted). The same named command passed after rerunning with escalation for local VNC binding:
AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_signed_restore_roundtrip_qemu --report build/compatibility-suite/qemu_v3_signed_restore_001.json

Source observations:

  • GR restore action 0x7e is at image 0x2792, which appears at raw file offset 0x2994 in the local MZ image. The prologue sets [0x0438] = 1, saves the caller continuation in [bp-0xce], temporarily stores 0x40 in [0x0b1c], and calls selector helper 0x8aeb with mode/message byte 0x72.
  • The success path opens the selected file through 0x625c, reads the 31-byte description with 0x62f9, then calls the length-prefixed read helper at image 0x2942/raw 0x2b44 for five blocks.
  • The five restore destinations mirror the GR save writer: first block into 0x0002, second into [0x07d0], third into [0x07d6], fourth into [0x153e], and fifth into 0x07ea.
  • After the third block has been loaded and all five reads have succeeded, raw 0x2ad1..0x2ae0 computes [0x07d6] + [0x07da] and calls image 0x07bc / raw 0x09be, the same repeating-key XOR helper used by the save writer. This makes restore decode the on-disk object/inventory block back into its runtime representation.
  • The restore success path then restores display bytes from [0x0f4f] and [0x0f51], updates the hardware-mode flag byte [0x001f], calls display and resource refresh helpers, clears [bp-0xce] to zero, refreshes menu/list state, and returns that zero continuation. This matches the existing model: successful restore restarts execution through restored state rather than continuing after opcode 0x7e.

Implementation/test updates:

  • tools/gr_v3_behavior_probe.py now has --probe signed-restore-roundtrip.
  • The probe builds a save-producing fixture whose logic calls 0x8f("GR"), sets a restored-marker flag and marker variables, and invokes 0x7d. The generated GRSG.1 is extracted from the QEMU disk image.
  • A restore fixture copies that generated save into its generated game directory, starts with a deliberately different marker X coordinate, calls 0x8f("GR"), and invokes 0x7e.
  • The restore fixture begins each cycle with an if flag branch. Only a successful restore brings back the saved flag and saved X coordinate, causing the next cycle to draw the saved-state marker. If restore fails/cancels and continues after 0x7e, the fixture draws the unrestored marker instead.
  • Direct comparison fixtures draw the expected saved-state marker and the unrestored-control marker without using save/restore UI.
  • tests/test_gr_v3_behavior_probe.py now covers the signed restore save payload, restore payload, generated fixture copy of GRSG.1, and direct comparison fixtures.
  • tools/compatibility_suite.py adds named qemu-v3 command gr_signed_restore_roundtrip_qemu, with a manifest test in tests/test_compatibility_suite.py.

QEMU result:

  • Direct report: build/gr-v3-behavior/signed_restore_roundtrip_qemu_001.json.
  • Suite report: build/compatibility-suite/qemu_v3_signed_restore_001.json.
  • Underlying suite probe report: build/gr-v3-behavior/signed_restore_roundtrip_suite.json.
  • The save-generation phase wrote and extracted build/gr-v3-behavior/GRSG_restore_suite.1.
  • Save description: codex gr probe.
  • Block lengths: 1028, 989, 1811, 100, and 12.
  • First block prefix bytes: 47 52 00 00 00 00 00 00.
  • Third block encoded SHA-256: 00c9fc2f1cc1ff71f2779804f993dea7389227c486a016556c45a9a0fb63f6a8.
  • Third block decoded SHA-256: 5a833f40a62fc2e367e60600592d8033219586797a3e0a1b3a142accb64bc237.
  • Capture comparison hashes from the suite probe:
    • restored: b16282219c5608e75e5b22a1fe3fe016f3ebeed52fa20b0b301260f02a3f713c
    • expected direct: b16282219c5608e75e5b22a1fe3fe016f3ebeed52fa20b0b301260f02a3f713c
    • unrestored control: 160a4ed1bab5ec6eb901ae2c5e3198a081000c0261cf6ad89eec4033e88861b4
  • Checks all passed: save_generation_passed, restored_matches_expected_direct, restored_differs_unrestored_control, and expected_direct_differs_unrestored_control.

Conclusion:

  • GR action 0x7e is now source-backed and original-engine validated for a valid, signature-prefixed GRSG.1 restore. The v3 save/restore model should apply the repeating Avis Durgan XOR transform to the object/inventory block on both write and read sides, with successful restore returning through restored state rather than continuing after the restore opcode.
  • Malformed save behavior remains intentionally out of scope for the compatibility spec because invalid files can drive the original interpreter into garbage-memory/exploit-like behavior.

Gold Rush v3 Restart Prompt-Marker Truth Table

Goal: correct and model the GR-specific prompt-marker redraw branch in action 0x80.

Commands and local reads:

  • rg -n "GR restart|prompt-marker visible|redraws? (the )?marker|only if.*visible|restart preserves prompt|0x0403|0x0405|0x26e0" docs/src PROGRESS.md tools tests
  • Exact-offset local Python/ndisasm reads for GR image offsets 0x26e0, 0x3b00, 0x3ab0, and 0x3ad9.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_restart_model.py
  • python3 -B -m py_compile tools/agi_restart.py

Source observations:

  • GR action 0x80 at image 0x26e0 calls helper 0x3b00 and stores the current prompt-marker visible word from [0x0dc3] in a local before erasing the marker through 0x3ad9.
  • After confirmation/reset work, the branch at 0x276f calls code.input.show_prompt_marker (0x3ab0) when the confirmation result is nonzero, or when the confirmation result is zero and the saved visible word is nonzero.
  • Therefore the redraw predicate is:
restart_was_accepted OR prompt_marker_was_visible_before_entry

Implementation/test updates:

  • Added tools/agi_restart.py with gr_v3_restart_redraws_prompt_marker(accepted, marker_was_visible).
  • Added tests/test_restart_model.py covering all four truth-table rows.

Conclusion:

  • The earlier shorthand “redraws only if it had been visible” was too narrow. The source-backed GR v3 model is: accepted restart redraws the marker; canceled restart redraws only when the marker had been visible on entry. A QEMU probe remains optional because this is a text/prompt-marker effect and the disassembly branch is direct.
  • Final verification for this pass: AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests (263 tests), mdbook build docs, python3 -B -m py_compile tools/agi_restart.py tools/agi_save.py tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py tools/compare_gr_sq2_static.py, git diff --check, and AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py all passed.

Gold Rush v3 Instrumented Motion Mode 4 Probe

Goal: validate the GR-specific motion dispatcher branch for object mode 4 without pretending ordinary bytecode can set that internal state directly.

Commands and local reads:

  • sed -n '560,640p' PROGRESS.md
  • sed -n '1,620p' tools/gr_v3_behavior_probe.py
  • sed -n '320,900p' tools/qemu_fixture.py
  • sed -n '2700,2945p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '12880,13150p' build/gr-sq2-static/gr_agi_image.ndisasm
  • Local Python scan of direct near-call targets in games/GR/AGI for image offsets 0x1975, 0x1888, 0x18cf, and 0x1909.
  • Local Python scan of present GR picture/view resources through tools/agi_resources.py.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe
  • python3 -B tools/gr_v3_behavior_probe.py --probe motion-mode-4 --game-dir games/GR --fixture-root build/gr-v3-behavior/motion-mode-4-fixtures --dos-prefix GRM --run-qemu --output build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.json --snapshot-raw build/gr-v3-behavior/snapshot/motion_mode_4.raw --snapshot-qcow build/gr-v3-behavior/snapshot/motion_mode_4.qcow2 --boot-wait 5 --draw-wait 8

Source observations:

  • GR code.motion.dispatch_mode_step at image 0x068a reads object byte +0x22, decrements it, accepts values through 3, and uses the embedded jump table at 0x06bd. The fourth slot dispatches to the same helper at 0x1888 used by mode 3.
  • SQ2 dispatcher 0x067a accepts only decremented values through 2, so SQ2 modes above 3 skip autonomous-mode dispatch.
  • GR action 0x51 at image 0x705c normally stores byte 3 into object field +0x22, then seeds target X/Y and completion state and calls helper 0x1888 once. Action 0x52 follows the same mode-3 shape with variable operands.
  • The helper-shaped code at image 0x1975 writes object 0 byte +0x22 = 4, target X/Y bytes +0x27/+0x28, and saved step byte +0x29 when [0x0139] is nonzero. A direct near-call scan found no ordinary call to 0x1975 in the local GR main image, and no bytecode action table entry points at it. Its natural entry path remains unresolved/source-only.
  • The first attempt to patch action 0x51 used the loaded-image offset as a file offset and failed with context bytes 8b f8 a1 d0 instead of c6 45 22 03. The patch helper now translates loaded-image offsets through the MZ header size when the copied interpreter begins with MZ.

Implementation/test updates:

  • Added --probe motion-mode-4 to tools/gr_v3_behavior_probe.py.
  • The probe builds three copied GR fixtures under build/: stationary object, unmodified action-0x51 mode-3 movement, and an instrumented copy where byte 0x03 at loaded-image offset 0x707f is patched to 0x04.
  • Added tests covering the generated motion payload, the expected patch context, rejection of unexpected interpreter bytes, and fixture construction.

QEMU result:

  • Report build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.json passed.
  • The instrumented mode-4 capture matched the unmodified mode-3 movement capture from (20,80) to (50,80) on GR picture 1/view 0.
  • The stationary control did not match the moving capture.

Conclusion:

  • The GR v3 internal dispatcher branch for object motion mode 4 is now instrumented-QEMU-validated: once mode 4 exists in object byte +0x22, it follows the same visible target-direction path as mode 3.
  • This is not evidence that ordinary logic bytecode can create mode 4; the natural seeding path is still source-only and should remain separate in the implementation spec.

Gold Rush v3 Frame-Selection Gate Probe

Goal: resolve and validate the GR-specific branch inside code.object.frame_timer_update (0x055c) for automatic direction-based group selection on views with exactly four groups versus more than four groups.

Commands and local reads:

  • sed -n '560,700p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '600,690p' build/gr-sq2-static/sq2_agi_image.ndisasm
  • Local Python census of GR view resources through tools/agi_resources.py, using payload byte +0x02 as the group count and group offsets at +0x05.
  • Local decoded-frame comparison of candidate GR views 33, 39, 177, and other four-plus-group resources.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe
  • python3 -B tools/gr_v3_behavior_probe.py --probe frame-selection-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/frame-selection-fixtures --dos-prefix GRF --run-qemu --output build/gr-v3-behavior/frame_selection_gate_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/frame_selection_gate.raw --snapshot-qcow build/gr-v3-behavior/snapshot/frame_selection_gate.qcow2 --boot-wait 5 --draw-wait 8

Source observations:

  • SQ2 code.object.frame_timer_update at image 0x0563 uses the two/three group table when object byte +0x0b is 2 or 3. When +0x0b >= 4, it indexes the four-plus group table at data 0x08e7.
  • GR code.object.frame_timer_update at image 0x055c keeps the two/three group path, then differs at image 0x05ac:
    • If +0x0b == 4, it jumps directly to the four-plus table path at 0x05c6.
    • Otherwise it tests flag 0x14 via helper 0x7818.
    • If flag 0x14 is clear, it skips selection and leaves the target as sentinel 4.
    • If flag 0x14 is set, it still requires +0x0b > 4 before using the four-plus table.
  • Therefore the earlier shorthand was backwards for exactly-four-loop views: exactly four groups are not excluded; they bypass the new flag gate and use the same four-plus table as SQ2. Only group counts greater than four are gated on flag 0x14.
  • Local GR resources include suitable stock views, so no synthetic v3 view packing was needed:
    • View 177 has exactly four groups, with visibly distinct group 0 and group 1 frames.
    • View 39 has more than four groups, with visibly distinct group 0 and group 1 frames.

Implementation/test updates:

  • Added --probe frame-selection-gate to tools/gr_v3_behavior_probe.py.
  • The probe builds copied GR fixtures under build/ for group-0/group-1 controls, exact-four flag-clear/flag-set cases, and more-than-four flag-clear/flag-set cases. It uses ordinary logic bytecode only; the GR interpreter is not patched.
  • Added focused tests covering the generated gate payload, control payload, and fixture case list.

QEMU result:

  • Report build/gr-v3-behavior/frame_selection_gate_qemu_001.json passed.
  • Exact-four view 177 selected group 1 for direction 6 both with flag 0x14 clear and with flag 0x14 set; both captures matched the group-1 control and did not match the group-0 control.
  • More-than-four view 39 remained on group 0 while flag 0x14 was clear; after flag 0x14 was set, it selected group 1. The group-0 and group-1 controls were distinct.

Conclusion:

  • GR / AGI v3 automatic direction group selection should be modeled as: two/three groups use the two/three table; exactly four groups use the four-plus table; more than four groups use the four-plus table only when flag 0x14 is set. Sentinel target 4 still means “do not change group.”

2026-07-10: GR v3 restart prompt-marker QEMU confirmation

Goal: confirm the source-mapped GR action 0x80 canceled-restart prompt-marker branch with an original-engine fixture.

Commands and local reads:

  • sed -n '619,700p' PROGRESS.md
  • sed -n '8960,9035p' docs/src/clean_room_executable_notes.md
  • sed -n '1,260p' tools/agi_restart.py
  • sed -n '1,260p' tests/test_restart_model.py
  • sed -n '1,360p' tools/gr_v3_behavior_probe.py
  • sed -n '820,1320p' tools/gr_v3_behavior_probe.py
  • rg -n "restart|prompt|input_line|0x77|0x78|confirm_restart|show_prompt" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src/runtime_model.md docs/src/logic_bytecode.md
  • python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/compatibility_suite.py tests/test_gr_v3_behavior_probe.py tests/test_compatibility_suite.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suite tests.test_restart_model
  • python3 -B tools/gr_v3_behavior_probe.py --probe restart-prompt-marker --game-dir games/GR --fixture-root build/gr-v3-behavior/restart-prompt-dryrun-fixtures --dos-prefix GRP --output build/gr-v3-behavior/restart_prompt_marker_dryrun_001.json
  • python3 -B tools/gr_v3_behavior_probe.py --probe restart-prompt-marker --game-dir games/GR --fixture-root build/gr-v3-behavior/restart-prompt-qemu-fixtures --dos-prefix GRP --run-qemu --output build/gr-v3-behavior/restart_prompt_marker_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/restart_prompt_marker_001.raw --snapshot-qcow build/gr-v3-behavior/snapshot/restart_prompt_marker_001.qcow2 --boot-wait 5 --draw-wait 8
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_restart_prompt_marker_qemu --report build/compatibility-suite/qemu_v3_restart_prompt_001.json

Source-first model:

  • The earlier source pass showed GR action 0x80 recording the current prompt-marker visible word before erasing it.
  • After confirmation, accepted restart always redraws the marker; canceled restart redraws the marker only when it was visible on entry.
  • The local helper gr_v3_restart_redraws_prompt_marker() keeps this truth table pinned for implementation use.

Fixture implementation:

  • Added --probe restart-prompt-marker to tools/gr_v3_behavior_probe.py.
  • The probe builds four copied GR fixtures under build/: hidden control, visible control, hidden then Escape-canceled restart, and visible then Escape-canceled restart.
  • The fixture logic uses ordinary bytecode only: picture load/show, prompt marker message setup through 0x6c, input row setup through 0x6f, hidden or visible marker setup through 0x77/0x78, and restart confirmation through 0x80.
  • The QEMU report compares both whole captures and the foreground-pixel count in logical prompt rectangle (0,40)..(39,47).
  • The v3 compatibility suite now has named command gr_restart_prompt_marker_qemu.

QEMU result:

  • Direct report build/gr-v3-behavior/restart_prompt_marker_qemu_001.json passed.
  • Suite report build/compatibility-suite/qemu_v3_restart_prompt_001.json passed after rerunning with VNC socket permission; the first unprivileged attempt failed before QEMU saved the DOS snapshot with Failed to bind socket: Operation not permitted.
  • Hidden control and hidden canceled restart had 0 prompt-row foreground pixels and identical capture hash 82d824134a00e40ae092e86b396b4b712e1d8ad48e7ad181d484ffbe8fa79f28.
  • Visible control and visible canceled restart had 8 prompt-row foreground pixels and identical capture hash cd7a1c8f5bf5eee32a6e818fb1c49d274db9175de9cbc7ed3f95a721df0e5a96.

Conclusion:

  • The original GR interpreter confirms the canceled-restart half of the source-backed truth table: Escape-canceled restart restores the prompt marker only when it was visible before action 0x80 erased it. The accepted-restart half remains source-backed because the reset path immediately restarts engine state, making a clean visual oracle less useful than the direct branch.

2026-07-10: GR v3 menu interaction gate QEMU confirmation

Goal: confirm the source-mapped GR-only action 0xb1 set_menu_interaction_gate with an original-engine fixture.

Commands and local reads:

  • git status --short
  • rg -n "menu-gate|menu gate|0xb1|Highest-Value|raw key-release|GR / SQ2|Menus|Compatibility|restart prompt|set_menu_interaction|code.menu.interact|0403" PROGRESS.md docs/src/versions.md docs/src/runtime_model.md docs/src/logic_bytecode.md docs/src/compatibility_testing.md docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md docs/src/progress_log.md AGENTS.md
  • sed -n '1,220p' PROGRESS.md
  • tail -n 80 docs/src/progress_log.md
  • sed -n '40,180p' docs/src/versions.md
  • sed -n '470,530p' docs/src/runtime_model.md
  • sed -n '110,140p' docs/src/logic_bytecode.md
  • sed -n '1488,1504p' docs/src/logic_bytecode.md
  • sed -n '1080,1135p' docs/src/compatibility_testing.md
  • sed -n '150,190p' AGENTS.md
  • tail -n 120 docs/src/clean_room_executable_notes.md
  • sed -n '500,640p' PROGRESS.md
  • sed -n '250,285p' docs/src/symbolic_labels.md
  • sed -n '428,545p' docs/src/symbolic_labels.md
  • sed -n '540,610p' PROGRESS.md
  • rg -n "282|test" PROGRESS.md docs/src/compatibility_testing.md
  • sed -n '1,260p' tools/gr_v3_behavior_probe.py
  • sed -n '494,516p' docs/src/runtime_model.md
  • sed -n '976,990p' docs/src/compatibility_testing.md
  • sed -n '70,90p' docs/src/compatibility_testing.md
  • sed -n '1478,1530p' docs/src/compatibility_testing.md
  • sed -n '380,438p' tools/compatibility_suite.py
  • rg -n "0403|1b67|970B|9724|038D|38d|000097|000038" build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '17600,17760p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '4250,4320p' build/gr-sq2-static/gr_agi_image.ndisasm
  • python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/compatibility_suite.py tests/test_gr_v3_behavior_probe.py tests/test_compatibility_suite.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suite
  • python3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-dryrun-fixtures --dos-prefix GRG --output build/gr-v3-behavior/menu_gate_dryrun_001.json
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --dry-run --include-qemu-v3
  • python3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-qemu-fixtures --dos-prefix GRG --run-qemu --output build/gr-v3-behavior/menu_gate_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/menu_gate_001.raw --snapshot-qcow build/gr-v3-behavior/snapshot/menu_gate_001.qcow2 --boot-wait 5 --draw-wait 8
  • python3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-qemu-fixtures-2 --dos-prefix GRG --run-qemu --output build/gr-v3-behavior/menu_gate_qemu_002.json --snapshot-raw build/gr-v3-behavior/snapshot/menu_gate_002.raw --snapshot-qcow build/gr-v3-behavior/snapshot/menu_gate_002.qcow2 --boot-wait 5 --draw-wait 8
  • python3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-qemu-fixtures-3 --dos-prefix GRG --run-qemu --output build/gr-v3-behavior/menu_gate_qemu_003.json --snapshot-raw build/gr-v3-behavior/snapshot/menu_gate_003.raw --snapshot-qcow build/gr-v3-behavior/snapshot/menu_gate_003.qcow2 --boot-wait 5 --draw-wait 8
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_menu_gate_qemu --report build/compatibility-suite/qemu_v3_menu_gate_001.json

Source-first model:

  • GR-only action handler 0x970b reads one immediate byte, zero-extends it, and stores the word at [0x0403].
  • GR action 0xa1 at 0x96eb still tests flag 14 and writes word [0x1b67] = 1 as the ordinary menu request.
  • The GR main-cycle path around image 0x38dd notices [0x1b67] != 0 and calls code.menu.interact at image 0x9724.
  • code.menu.interact first compares word [0x0403] with zero. If it is zero, it returns immediately; if nonzero, it proceeds into the existing draw/wait modal menu path. Therefore 0xb1 is an interaction gate, not part of menu construction.

Fixture implementation:

  • Added --probe menu-gate to tools/gr_v3_behavior_probe.py.
  • The final probe builds three copied GR fixtures under build/: menu_gate_blocked_control, menu_gate_enabled_request, and menu_gate_disabled_request.
  • The blocked control draws a visible object marker at the blocked location.
  • The request fixtures build a one-heading, one-item menu, finalize it, set flag 14, execute 0xb1(1) or 0xb1(0), execute 0xa1, and then end the logic stream so the top-level engine cycle can service [0x1b67].
  • The promoted oracle does not depend on pressing Enter inside the menu. It checks only the gate: 0xb1(0) should match the blocked control, while 0xb1(1) should differ by entering the modal menu path.

Important correction:

  • An earlier fixture ended with a self-loop. That kept logic 0 executing inside the interpreter and prevented the top-level main cycle from reaching the [0x1b67] check. Both enabled and disabled cases therefore matched the blocked control even though the generated 0xb1 bytes were correct.
  • Replacing the self-loop with the structural end action allowed the original engine’s main cycle to process the request. The Enter-driven accepted-marker oracle still was not stable, so the final evidence uses a no-key modal-gate oracle.

QEMU result:

  • Direct report build/gr-v3-behavior/menu_gate_qemu_003.json passed.
  • menu_gate_disabled_request matched menu_gate_blocked_control.
  • menu_gate_enabled_request differed from both the blocked control and the disabled request.
  • Capture SHA-256 values:
    • blocked control and disabled request: 160a4ed1bab5ec6eb901ae2c5e3198a081000c0261cf6ad89eec4033e88861b4
    • enabled request: e463cb17d86267bda970277df82d51c6b51dc743327f51c856a25de65399155b
  • The named suite command build/compatibility-suite/qemu_v3_menu_gate_001.json passed after rerunning with VNC socket permission; the first unprivileged attempt failed before QEMU launched with Failed to bind socket: Operation not permitted.

Conclusion:

  • The original GR interpreter confirms the disassembly model for action 0xb1: zero blocks a requested menu before the modal menu draw/wait path, and nonzero permits the existing menu interaction path to run after 0xa1.

2026-07-10: SQ2/GR tracked key-release IRQ source model

Goal: turn the remaining source-backed raw key-release gate into an implementation-facing state model without relying on QEMU keyboard-release timing.

Commands and local reads:

  • git status --short
  • sed -n '621,640p' PROGRESS.md
  • rg -n "key-release|key release|0xad|0xb5|0405|1530|release_event|tracked-key|type-2 zero|raw key" PROGRESS.md docs/src tools tests build/gr-sq2-static/gr_agi_image.ndisasm build/gr-sq2-static/sq2_agi_image.ndisasm
  • rg -n "map_key|raw_key|status_byte|key_release|release|sendkey|post_launch_key_names|post_launch_keys|key_names" tools/logic_interpreter_probe.py tools/gr_v3_behavior_probe.py tests/test_logic_interpreter_probe.py tests/test_gr_v3_behavior_probe.py
  • sed -n '10870,11140p' build/gr-sq2-static/sq2_agi_image.ndisasm
  • sed -n '11220,11480p' build/gr-sq2-static/gr_agi_image.ndisasm
  • sed -n '188,240p' docs/src/symbolic_labels.md
  • sed -n '470,505p' docs/src/logic_bytecode.md
  • sed -n '1285,1305p' docs/src/compatibility_testing.md
  • sed -n '5677,5708p' docs/src/clean_room_executable_notes.md
  • rg -n "Restart|Save|heap|motion|keyboard|input|model|source-modeled|class .*Model|def .*model|gr_v3" tools tests | head -n 200
  • ls tools | sort
  • ls tests | sort
  • sed -n '1,220p' tools/agi_restart.py
  • sed -n '1,220p' tests/test_restart_model.py
  • python3 -B -m py_compile tools/agi_input.py tests/test_input_model.py
  • python3 -B -m unittest tests.test_input_model

Source observations:

  • SQ2 action 0xad at image 0x602f is inc byte [0x1530], so the gate is an unsigned byte and wraps from 0xff to zero.
  • GR action 0xad at image 0x63a8 stores byte [0x0405] = 1.
  • GR-only action 0xb5 at image 0x63b0 stores byte [0x0405] = 0.
  • The SQ2 keyboard IRQ hook at image 0x6036 and the GR hook at image 0x63b8 have the same tracked-key latch shape after relocation:
    • read the raw scan byte from port 0x60;
    • mask off bit 0x80 and accept only scan codes 0x47..0x51;
    • require the corresponding enable-table byte to be nonzero;
    • on keydown, if the selected latch was clear, clear all tracked latches and set only the selected latch;
    • on duplicate keydown, do not enqueue a script event;
    • on key release, clear the selected latch only if it had been set;
    • enqueue event (type=2, value=0) only when that release path cleared a latch and the version-specific gate byte is nonzero.

Implementation/test updates:

  • Added tools/agi_input.py with a portable KeyReleaseIrqState model, SQ2/GR gate-writer helpers, and process_tracked_key_irq_scan().
  • Added tests/test_input_model.py to cover:
    • SQ2 0xad enabling a later release event;
    • SQ2 byte wraparound from 0xff to zero;
    • GR 0xad/0xb5 set/clear behavior;
    • keydown clearing other tracked latches;
    • disabled and out-of-range scan bytes producing no event;
    • model validation for table lengths and gate byte range.

Conclusion:

  • The tracked release-key behavior is now source-modeled at a portable state level. A direct QEMU fixture remains optional only if the final target needs raw hardware IRQ timing evidence; it is not necessary for the valid-data AGI semantics currently being specified.

2026-07-10: v3 generated picture/view fixture packing

Goal: remove the remaining fixture-writer gap for targeted Gold Rush / AGI v3 graphics probes without modifying private local inputs under games/.

Commands and local reads:

  • git status --short
  • sed -n '621,640p' PROGRESS.md
  • rg -n "does not yet pack|direct-record logic|v3.*picture/view|picture-nibble|v3-synthetic|v3 fixture" AGENTS.md PROGRESS.md docs/src tests tools
  • sed -n '220,340p' docs/src/resource_files.md
  • sed -n '1,220p' docs/src/versions.md
  • sed -n '660,900p' tools/qemu_fixture.py
  • sed -n '900,980p' tools/qemu_fixture.py
  • sed -n '1260,1365p' tools/qemu_fixture.py
  • rg -n "def encode_picture_nibbles|encode_picture_nibbles" tools/agi_resources.py tests/test_agi_resources.py tests/test_qemu_fixture.py
  • sed -n '265,345p' tools/agi_resources.py
  • python3 -B -m py_compile tools/agi_resources.py tools/qemu_fixture.py tests/test_agi_resources.py tests/test_qemu_fixture.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_agi_resources tests.test_qemu_fixture

Source/model observations:

  • The v3 generic reader accepts direct records when the expanded and stored lengths in the 7-byte record header are equal. That path is already present in original GR resources and is suitable for controlled generated logic/view fixtures.
  • The v3 picture path is selected by metadata bit 0x80; the low nibble still names the volume and must match the directory entry volume. The stored payload is a nibble stream that expands to ordinary picture bytes.
  • The inverse picture-nibble writer packs ordinary bytes as two nibbles, except the operand following picture commands 0xf0 and 0xf2, which is stored as one nibble. The generated expanded stream must end at 0xff.

Implementation/test updates:

  • Added encode_picture_nibbles() to tools/agi_resources.py as the inverse of the observed GR picture-nibble expansion for valid expanded picture streams.
  • Added v3 fixture helpers in tools/qemu_fixture.py: v3_picture_volume_record(), generic patch_v3_resource(), patch_v3_picture_resource(), patch_v3_view_resource(), build_v3_synthetic_picture_fixture(), and build_v3_synthetic_picture_view_fixture().
  • Added CLI commands v3-synthetic-picture and v3-synthetic-picture-view.
  • Added local read-back tests for the picture-nibble record wrapper, copied v3 picture patching, copied v3 view patching, and full synthetic picture/view fixture construction.

Conclusion:

  • Targeted v3 picture/view probes can now be generated under build/ without patching games/. The project still does not implement a v3 dictionary compressor; generated view payloads use the original interpreter’s direct-record path, while original compressed local resources continue to be decoded through the observed reader model.

2026-07-10: v3 synthetic picture/view fixture QEMU probe

Goal: confirm the generated GR v3 picture/view fixture writer against the original interpreter after adding local read-back tests.

Commands and local reads:

  • git status --short
  • sed -n '1,180p' PROGRESS.md
  • sed -n '180,360p' PROGRESS.md
  • sed -n '620,660p' PROGRESS.md
  • sed -n '1,260p' tools/gr_v3_behavior_probe.py
  • sed -n '260,620p' tools/gr_v3_behavior_probe.py
  • sed -n '620,980p' tools/gr_v3_behavior_probe.py
  • sed -n '980,1380p' tools/gr_v3_behavior_probe.py
  • sed -n '1380,1760p' tools/gr_v3_behavior_probe.py
  • sed -n '1760,2140p' tools/gr_v3_behavior_probe.py
  • sed -n '1,260p' tests/test_gr_v3_behavior_probe.py
  • sed -n '260,620p' tests/test_gr_v3_behavior_probe.py
  • sed -n '1,220p' tests/test_compatibility_suite.py
  • sed -n '1,340p' tools/compatibility_suite.py
  • sed -n '430,510p' tests/test_qemu_fixture.py
  • python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/compatibility_suite.py tests/test_gr_v3_behavior_probe.py tests/test_compatibility_suite.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suite
  • python3 -B tools/gr_v3_behavior_probe.py --probe synthetic-picture-view --game-dir games/GR --fixture-root build/gr-v3-behavior/synthetic-picture-view-fixtures --dos-prefix GSP --output build/gr-v3-behavior/synthetic_picture_view_001.json
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_synthetic_picture_view_qemu --report build/compatibility-suite/qemu_v3_synthetic_picture_view_001.json
  • Repeated the same compatibility-suite command with elevated execution after the first QEMU attempt failed before launch with Failed to bind socket: Operation not permitted.

Fixture design:

  • The blank control patches logic 0 to loop without drawing.
  • The picture-only fixture patches logic 0 to show generated picture 0 and stores picture 0 as a v3 picture-nibble record in GRVOL.1.
  • The picture/view fixture uses the same generated picture plus a direct v3 view 0 record. The logic draws the picture, loads view 0, and uses action 0x7a to place group 0 frame 0 at (20,80) with priority 15.
  • The generated picture payload is f0 01 f8 50 50 ff: select visual color 1, seed fill at (80,80), then terminate.
  • The generated view payload is a one-loop, one-frame, 4x4 opaque run-length encoded cel with color 4 and transparent color 0: 00 00 01 00 00 07 00 01 03 00 04 04 00 44 00 44 00 44 00 44 00.

Implementation/test updates:

  • Added --probe synthetic-picture-view to tools/gr_v3_behavior_probe.py.
  • Added build_gr_synthetic_picture_view_fixtures() and a QEMU reducer that compares blank, picture-only, and picture-plus-view captures.
  • Added local tests that read the generated v3 records back through tools/agi_resources.py and confirm the expected picture-nibble/direct transforms.
  • Added compatibility-suite command gr_synthetic_picture_view_qemu.

QEMU result:

  • build/gr-v3-behavior/synthetic_picture_view_suite.json passed.
  • The blank control had one unique color.
  • The picture-only capture had two unique colors and differed from blank by 215,040 pixels.
  • The picture-plus-view capture had three unique colors and differed from picture-only by 128 pixels.
  • Capture hashes:
    • blank control: f3ee47648d6ba080ffab59f9c5cc84d66a44ee6de07c5fa3edbe222e95021062
    • picture only: e9b3a51fc2fe85e39ba7c88c726e4835d8586d6cfa50bea45727e08e71a424a4
    • picture plus view: a1680189d4c06001263bbcec3edcea29b9323ca584e2a533918693d5cc60113a

Conclusion:

  • The original GR interpreter accepts the generated picture-nibble picture record and direct view record for this controlled fixture. This promotes the v3 fixture writer as reusable compatibility infrastructure while preserving the source-backed renderer/model distinction.

2026-07-10: sound/audio subsystem specification pass

Goal: consolidate the already source-backed sound findings into a dedicated implementation-facing mdBook chapter.

Commands and local reads:

  • git status --short
  • sed -n '1,220p' PROGRESS.md
  • sed -n '420,660p' PROGRESS.md
  • tail -n 60 docs/src/progress_log.md
  • rg -n "Sound|sound|audio|tone|PC-speaker|speaker|duration|waveform|Remaining|0x62|0x63|0x64" PROGRESS.md docs/src tools tests
  • rg --files docs/src tools tests | rg "sound|audio"
  • sed -n '1,220p' docs/src/SUMMARY.md
  • sed -n '135,205p' docs/src/runtime_model.md
  • sed -n '300,340p' docs/src/symbolic_labels.md
  • sed -n '1,260p' tests/test_sound_resources.py
  • sed -n '1,260p' tools/agi_sound.py
  • sed -n '260,360p' tools/agi_sound.py
  • rg -n "Sound/audio|Sound|sound|audio|Highest-Value|Final spec|Remaining" PROGRESS.md
  • tail -n 80 docs/src/progress_log.md
  • tail -n 80 docs/src/clean_room_executable_notes.md
  • sed -n '100,220p' docs/src/current_status.md
  • sed -n '520,552p' PROGRESS.md
  • sed -n '630,652p' PROGRESS.md
  • sed -n '1,80p' docs/src/resource_files.md
  • sed -n '1,80p' docs/src/graphics_object_pipeline.md
  • python3 -B -m unittest tests.test_sound_resources
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_sound_resources
  • mdbook build docs
  • git diff --check
  • git status --short
  • find tests tools -type d -name __pycache__ -print

Observations consolidated:

  • Sound payloads begin with four little-endian channel offsets, followed by duration/tone/control channel streams terminated by duration 0xffff.
  • The current SQ2 corpus has 49 present sound resources, all with four sorted in-bounds channel offsets and terminating streams.
  • 0x62, 0x63, and 0x64 expose sound loading, start-with-completion-flag, and stop/clear behavior to logic bytecode.
  • Driver start initializes channel countdowns to 1, so the first channel record is consumed on the first active sound tick.
  • Selectors 0 and 8 advance only channel 0; other observed selectors advance all four channels.
  • Flag 9 is a playback gate tested at the start of the tick path. Clearing it causes immediate stop/completion on tick 1.
  • The PC-speaker path computes a divisor from the event tone word and treats attenuation nibble 0x0f as silence.
  • The non-PC path emits tone bytes and channel/attenuation bytes to port 0xc0; the stop path emits 0x9f 0xbf 0xdf 0xff.
  • The attenuation envelope table uses signed deltas from the event base attenuation and terminates with sentinel 0x80.

Documentation updates:

  • Added docs/src/sound_and_audio.md.
  • Added the new chapter to docs/src/SUMMARY.md.
  • Updated PROGRESS.md and docs/src/current_status.md to reference the dedicated sound/audio chapter.

Validation:

  • The first focused sound test command failed with the expected explicit-game guard: game directory required; pass --game-dir PATH or set AGI_GAME_DIR.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_sound_resources passed 16 tests.
  • mdbook build docs passed.
  • git diff --check passed.
  • No Python __pycache__ directories were present under tests/ or tools/ after the focused test run.

Conclusion:

  • The sound/audio subsystem now has a chapter-level contract for resource parsing, playback scheduling, completion flags, and hardware-driver output boundaries. Analog waveform synthesis remains explicitly outside the current interpreter compatibility target.

2026-07-10: read-only multi-game census tooling

Goal: create a reproducible source-first inventory step for the additional local game directories without modifying private inputs under games/.

Commands and local reads:

  • sed -n '640,670p' PROGRESS.md
  • sed -n '1,260p' docs/src/versions.md
  • sed -n '1,220p' docs/src/cross_version_workflow.md
  • rg -n "v3|GR|Gold|compression|picture-nibble|direct view|loader error|static delta|behavioral" docs/src PROGRESS.md tools tests
  • rg -n "v3|GR|combined|dictionary|nibble|prefixed|expanded|stored|compression|Version|SQ2|sound" docs/src/resource_files.md
  • sed -n '80,240p' docs/src/resource_files.md
  • sed -n '228,390p' docs/src/resource_files.md
  • sed -n '1,260p' tools/agi_resources.py
  • sed -n '260,560p' tools/agi_resources.py
  • sed -n '1,180p' tests/test_agi_resources.py
  • find games -maxdepth 2 -type f
  • find games -maxdepth 1 -type d
  • git ls-files tools
  • sed -n '1,220p' tools/project_paths.py
  • sed -n '1,220p' tests/test_compatibility_suite.py
  • sed -n '1,220p' tests/test_qemu_fixture.py
  • sed -n '1,120p' .gitignore
  • python3 -B -m py_compile tools/game_census.py tests/test_game_census.py
  • python3 -B -m unittest tests.test_game_census
  • python3 -B tools/game_census.py --games-root games --format json --output build/cross-version/game_census.json
  • python3 -B tools/game_census.py --games-root games --format markdown --output build/cross-version/game_census.md
  • sed -n '1,80p' build/cross-version/game_census.md
  • python3 -m json.tool build/cross-version/game_census.json
  • mdbook build docs
  • git diff --check
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests
  • find tests tools -type d -name __pycache__ -print
  • rm -rf tests/__pycache__ tools/__pycache__
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_game_census_dispatch_001.json
  • python3 -m json.tool build/compatibility-suite/local_game_census_dispatch_001.json

Implementation:

  • Added tools/game_census.py.
  • The tool requires one or more explicit --game-dir paths or an explicit --games-root path.
  • It detects v2 split and v3 combined layouts through tools/agi_resources.py.
  • It extracts local Version ... strings from known interpreter data files.
  • It counts entries, present entries, volumes, readable record transforms, and stored/expanded byte totals per resource family.
  • It records per-record header/expansion errors instead of aborting the whole inventory.
  • Added synthetic tests in tests/test_game_census.py for split layout, combined layout, version extraction, deduplication, and Markdown formatting.

Validation:

  • python3 -B -m py_compile tools/game_census.py tests/test_game_census.py passed.
  • python3 -B -m unittest tests.test_game_census passed 5 tests.
  • python3 -B -m unittest tests.test_game_census tests.test_agi_resources passed 13 tests.
  • mdbook build docs passed.
  • git diff --check passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 304 tests.
  • Removed the generated tests/__pycache__ and tools/__pycache__ directories after the full test run.

Current private-input census:

  • v2 split layouts: KQ1 Version 2.917, KQ2 Version 2.411, KQ3 Version 2.936, LSL1 Version 2.440, PQ1 Version 2.917, and SQ2 Version 2.936.
  • v3 combined layouts: KQ4D Version 3.002.102 with DMDIR/DMVOL.N, and GR Version 3.002.149 with GRDIR/GRVOL.N.
  • GR has no record errors under the current v3 reader.
  • SQ2 still reports the two known out-of-range end entries from LOGDIR/PICDIR.
  • KQ1 has four sound entries that fail the generic v2 header check.
  • KQ4D has multiple suspect sound-section entries that fail the generic v3 header check. These should be source-inspected before any behavioral rule is added.

Conclusion:

  • The project now has a repeatable first-pass inventory for future cross-version comparison. Record errors are planning evidence for later disassembly, not part of the clean-room behavioral model for valid resources.

2026-07-10: KQ4D dispatch-table detection and sound references

Goal: follow up on the KQ4D census errors by fixing v3 logic disassembly for non-GR table bases and checking whether decoded KQ4D scripts reference the suspect sound-section entries.

Commands and local reads:

  • ls -l games/KQ4D/DMDIR games/KQ4D/DMVOL.* games/KQ4D/AGI
  • xxd -g1 -l 96 games/KQ4D/DMDIR
  • xxd -g1 -s 0x2d5 -l 256 games/KQ4D/DMDIR
  • file games/KQ4D/AGI games/GR/AGI games/SQ2/AGI
  • Python local decode of KQ4D DMDIR sound entries, printing present-like indices, raw triples, target volume/offsets, and target header bytes.
  • python3 -B tools/disassemble_logic.py --help without a game directory, which failed with the expected explicit-game guard.
  • AGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --help
  • AGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --stats before the fix, showing garbage table data from the old GR-specific v3 table bases.
  • Python local scans comparing SQ2/GR/KQ4D AGIDATA table bytes and searching for action/condition argc/meta signatures.
  • rg -n "dispatch_table_layout|load_table|ACTION_NAMES|COND_NAMES|disassemble_logic" tests tools docs/src
  • sed -n '430,530p' tools/disassemble_logic.py
  • rg -n "0x0440|0x0762|0x061D|0x08FD|dispatch table|AGIDATA dispatch" docs/src tests tools
  • AGI_GAME_DIR=games/SQ2 python3 -B -m py_compile tools/disassemble_logic.py tests/test_disassemble_logic_tables.py
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_disassemble_logic_tables
  • AGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --stats
  • AGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --limit 80 | rg -n "load_sound|start_sound|stop_sound|sound"
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_disassemble_logic_tables tests.test_game_census tests.test_agi_resources
  • mdbook build docs
  • git diff --check
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests
  • find tests tools -type d -name __pycache__ -print
  • rm -rf tests/__pycache__ tools/__pycache__

Observations:

  • KQ4D DMDIR has header offsets 0x0008, 0x00f8, 0x01e5, and 0x02d5.
  • The KQ4D sound section contains clean v3 records at sound indices 70..79.
  • Later present-looking sound-section triples often point into compressed data rather than to 12 34 volume record headers.
  • The old tools/disassemble_logic.py v3 path hard-coded GR table bases 0x0440 and 0x0762, so KQ4D stats were decoded with the wrong operand table.
  • The first 16 action-table argc/meta pairs form an exact signature at:
    • SQ2 AGIDATA.OVL:0x061d
    • GR AGIDATA.OVL:0x0440
    • KQ4D AGIDATA.OVL:0x0620
  • The 19 structured condition-table argc/meta pairs form an exact signature at:
    • SQ2 AGIDATA.OVL:0x08fd
    • GR AGIDATA.OVL:0x0762
    • KQ4D AGIDATA.OVL:0x0942
  • KQ4D has the same v3 action-table shape through opcode 0xb5; the extra slots at 0xb0..0xb5 have the same operand metadata shape as GR.
  • After table detection, KQ4D stats are coherent. Decoded KQ4D scripts use load_sound, start_sound_with_flag, and stop_sound_or_clear_sound_state, but only with sound resource numbers 70..79.

Implementation:

  • Added signature-based dispatch table detection to tools/disassemble_logic.py.
  • Added tests/test_disassemble_logic_tables.py covering SQ2, GR, and KQ4D table bases.
  • Updated resource, bytecode, version, symbolic-label, and progress docs to treat v3 action/condition table bases as build-specific associations.

Validation:

  • AGI_GAME_DIR=games/SQ2 python3 -B -m py_compile tools/disassemble_logic.py tests/test_disassemble_logic_tables.py tools/game_census.py tests/test_game_census.py passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_disassemble_logic_tables tests.test_game_census tests.test_agi_resources passed 16 tests.
  • mdbook build docs passed.
  • git diff --check passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 307 tests.
  • Removed the generated tests/__pycache__ and tools/__pycache__ directories after the full test run.
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_game_census_dispatch_001.json passed. The report records zero return codes for local unit tests, mdBook build, and the opcode-evidence freshness check.

Conclusion:

  • KQ4D can now be used as a v3 logic-disassembly input. Its current scripts only reference the clean sound records, so later bad KQ4D sound-section triples remain out-of-model planning evidence until source inspection proves a valid script path can observe them.

2026-07-10: separate clean-room specification book

The project deliverable was clarified as a human-readable specification of externally observable AGI behavior, not a replacement engine. The existing docs/ mdBook remains the reverse-engineering evidence record, including disassembly and DOS implementation details. A separate spec/ mdBook now serves as the clean-room interface for an independent implementation team.

The specification starts with an explicit behavioral boundary and conformance model. It excludes original addresses, registers, instruction sequences, overlay organization, memory layout, and inferred source structure unless an item itself has an externally observable effect. Evidence must first be recorded here, then deliberately restated in spec/ as a portable behavioral contract.

The default compatibility manifest now builds both mdBooks. This keeps the evidence record and the clean-room deliverable independently renderable as the project evolves.

Initial validation found that the installed mdBook version does not accept the newer book.multilingual configuration key. The unsupported key was removed; it did not affect the specification content or directory structure.

Validation:

  • mdbook build spec passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_spec_split_001.json passed all 307 local tests, built both mdBooks, and passed the opcode-evidence freshness check.
  • git diff --check passed.

2026-07-10: first behavioral-specification promotion pass

The first substantive clean-room specification pass promoted evidence into four implementation-independent chapters:

  • version profiles for the currently promoted 2.936 and 3.002.149 behavior;
  • split-v2 and combined-v3 resource container formats, including dictionary and picture-nibble expansion;
  • portable scalar, logic, resource, object, inventory, graphics, and cycle state; and
  • sound resource parsing, countdown scheduling, completion flags, channel participation, PC-speaker divisor output, and four-channel tone/silence output.

No original addresses, symbolic machine-code labels, disassembly commands, local paths, or test-harness references were copied into the substantive chapters. A new structural test verifies mdBook summary targets and rejects evidence-only terminology in future substantive spec chapters.

The four-channel attenuation-envelope contract was intentionally not promoted as complete. The evidence records that channels 0 through 2 reset their envelope index on each event while channel 3 preserves it, but the complete portable initialization/transition contract still needs a focused source pass. The spec states this limitation rather than filling the gap by inference.

A direct scan of all 468 readable dictionary-compressed Gold Rush records found initial 9-bit code 0x100 in every case. The valid v3 dictionary stream contract therefore requires a reset code at the beginning, removing ambiguity around decoder startup state.

The next specification pass added logic payload/message framing, message XOR decoding, main-stream jumps and conditional blocks, AND/OR/NOT condition-list semantics, all 19 valid 2.936 condition opcodes, and action opcodes 0x00..0x20. Room switching is stated as portable state transitions and re-entry ordering rather than as cleanup routine calls. Picture load, prepare, overlay, and show remain separate operations in the normative text because their visibility distinction is observable.

Before validation, the nested-call return rule was checked directly against the local instruction stream. This exposed a stale earlier statement in the evidence chapters: ordinary opcode 0x00 returns the instruction pointer after the terminator and does not clear the logic record’s resume field. Therefore a normally terminated callee returns to the caller’s next action. Only an action path that returns a zero continuation, such as room switching, propagates an abort through call_logic. Both the evidence book and specification were corrected before the chapter was promoted as validated.

The next action-catalog group promotes opcodes 0x21..0x64 using portable object concepts rather than record offsets. It covers object activation and position, view/loop/cel selection, priority and update partitions, horizon and control gates, collision distance, four animation modes, targeted/approach/ random movement, rectangle bounds, inventory locations, and sound control.

The final action-catalog group promotes 0x65..0xaf plus v3 slots 0xb0..0xb5. It preserves two unusual byte-stream effects: 0x95 consumes an extra byte when tracing is already active, while runtime 0xaf consumes no operand despite table-driven scanners assigning it length one. The configured message parameters used by 0x97/0x98 were later resolved from source as row, column, and width overrides. A structural test now requires every accepted v2 and v3 action opcode to appear in the specification.

Validation:

  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 309 tests.
  • mdbook build docs passed.
  • mdbook build spec passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check passed.
  • git diff --check passed.

2026-07-10: picture-command specification promotion

The picture evidence was restated as a portable full-EGA behavioral contract in spec/src/picture_resources.md. The chapter separates picture loading, prepare-time clearing, overlay decoding, and visible presentation; specifies the guarded data-reader versus raw-operand distinction; and covers every command from 0xf0 through 0xfa plus the 0xff terminator.

Raster semantics are explicit rather than delegated to conventional graphics primitives. The specification includes modulo-256 relative endpoints and line error accumulators, the visual-first seed-fill connectivity rule, all eight pattern row-word families and column masks, the seeded stipple transition, and the pattern plotter’s linear X-160 write into the next logical row. These are all externally distinguishable for valid command streams.

The specification deliberately excludes truncated operands and unsupported command-boundary bytes 0xfb..0xfe as malformed-data behavior. A structural test requires all valid picture commands and the terminator to remain present in the chapter.

2026-07-10: view, object, input, and persistence specification promotion

The next clean-room promotion pass added four portable chapters:

  • spec/src/view_resources.md for view offsets, row runs, mutable mirrored-cel orientation, baseline placement, transparent pixels, priority scanning, and preview strings;
  • spec/src/object_behavior.md for lifecycle, placement, update cadence, direction-based loop selection, cel cycling, movement, crossing collision, footprint-control acceptance, target/approach/random motion, drawing order, and refresh;
  • spec/src/input_text_and_menus.md for the dictionary file, string slots, parser normalization/results/matching, event types and mappings, text surfaces, inventory selection, and menu state; and
  • spec/src/session_and_persistence.md for room transitions, resource replay, selector behavior, save framing, the v3 block transform, restore/restart, and process termination.

The object pass reopened source ranges before promotion. The disposable SQ2 image was regenerated with:

python3 -B tools/decrypt_agi.py --game-dir games/SQ2 \
  --output build/cleanroom/AGI.decrypted.exe

Focused instruction reads covered the collision helper at image 0x4719, approach helper 0x0b36, random-motion helper 0x3f5a, target-motion helper 0x1672, and shared direction classifier 0x16ed. The collision helper proves that horizontal equality counts as overlap and that a vertical collision is current-baseline equality or strict saved/current order reversal. The action catalog was corrected accordingly: object reset action 0x21 also enables update/cycling state and selects the later partition; movement-rectangle membership is strict; and actions 0x40/0x41 gate the final footprint class state rather than latching whether one class appeared anywhere.

The motion helpers prove these portable details:

  • target/near classification uses strict bands, so equality with either threshold remains directional;
  • random mode uses random value modulo 9 for direction and repeatedly samples modulo 51 until its countdown is at least 6;
  • approach recovery chooses a nonzero modulo-9 direction, calculates half the center/baseline Manhattan distance plus one, and samples a delay no smaller than the step; and
  • retry-delay subtraction uses byte arithmetic followed by a signed nonnegative branch.

tools/agi_graphics.py now contains deterministic source-model helpers for those transitions. tests/test_graphics_rendering.py supplies random words explicitly and checks strict threshold edges, random countdown rejection, initial approach sentinel handling, stuck recovery, and retry-delay return to direct movement.

The persistence promotion deliberately stops short of claiming arbitrary binary save interchange. The 31-byte header, five little-endian length-prefixed blocks, known profile lengths, signature checks, replay language, control flow, and Gold Rush block-3 XOR key are specified. A complete portable mapping for every byte within all five blocks remains the next major serialization task.

Focused validation had three harmless false starts caused by stale guessed module names: tests.test_agi_graphics, tests.test_logic_opcode_evidence, and tests.test_agi_input. The actual modules are tests.test_graphics_rendering, tests.test_logic_doc_coverage, and tests.test_input_model. Corrected focused runs passed 68, 69, 75, 83, 90, and finally 101 tests as the chapters accumulated.

Full validation after the complete promotion pass:

  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 323 tests.
  • mdbook build docs passed.
  • mdbook build spec passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check passed.
  • git diff --check passed.

2026-07-10: profile 2.936 save-block mapping

The save-state work resumed with a byte-complete map rather than a list of only recognized fields. A fresh static instruction listing was generated from the disposable decrypted executable:

ndisasm -b 16 -o 0 -e 512 build/cleanroom/AGI.decrypted.exe \
  > build/cleanroom/SQ2_AGI_image.ndisasm

The first saved block starts with the seven-byte signature area and continues through all 256 variables, 32 packed-flag bytes, timer/horizon/movement globals, the key map, string storage, text configuration, and replay checkpoint. Block positions in the clean specification are relative to the block start; the evidence-side source association is the contiguous range 0x0002..0x05e2.

An absolute-data-reference scan plus focused reads of the string actions found five unresolved ranges. All 11 local SQ2 saves agree on their current contents:

Block-1 positionSizeObserved bytes
0x012d200 00
0x013d20f 00
0x01df44all zero
0x03eb480all zero
0x05d61zero

The 960-byte range beginning at source address 0x020d is exactly 24 groups of 40 bytes, but that arithmetic alone is not a slot contract. The parser action at image 0x750b explicitly accepts only indexes below 12, and current local logic resources use only the lower slots. The first 480 bytes are therefore mapped as twelve script-visible string slots; the following 480 bytes remain an opaque bank. String-copy actions can calculate beyond the lower bank without a bounds check, but malformed/out-of-range script behavior is outside the valid data model and is not evidence for 24 portable slots.

Block 2 has length 0x0387, exactly 21 records times the already source-mapped 43-byte object stride. Its field map covers every byte without padding. The restore routine at code.restore.replay_resource_events first preserves each record’s low X byte and flag word while caches/lists are reset. After resource replay it restores X, replaces object byte +0x02 with the object index, reloads the selected view when available, and calls the normal view-binding path. That path preserves valid selected loop/cel indexes while rebuilding payload, loop, and cel pointers, loop/cel counts, width, and height. Active/list state is rebuilt from the saved flags before the exact saved flag word is restored. Consequently the serialized pointer-shaped words are compatibility tokens, not portable object identity, and the event byte is normalized to object index on successful restore. All 11 local saves already contain indexes 0..20 there.

Block 4 has length 0x00c8, exactly 100 two-byte replay-pair slots. The active pair count and checkpoint count are in block 1. Only the active prefix is replay semantics; the remaining pairs are inactive capacity but still serialized.

The block-3 pass reopened code.inventory.initialize_metadata_and_objects at image 0x0fa5, the carried-item list builder at 0x3203, and inventory actions 0x5c..0x61. The local 331-byte OBJECT file XOR-decodes with the already source-derived repeating Avis Durgan key. Its decoded prefix is:

78 00 14

The loader interprets the first word as the item-table byte size and the third byte as the maximum drawable-object index. It advances the runtime inventory root past that header, sets the item-table end to root plus 0x78, records the runtime/save length as file length minus 3, and allocates (0x14 + 1) object records. This derives the observed structures rather than merely fitting them:

  • 40 three-byte inventory entries occupy block-3 positions 0x0000..0x0077;
  • 21 drawable objects times 43 bytes produce block-2 length 0x0387; and
  • the remaining 208 block-3 bytes are the zero-terminated item-name pool.

Each inventory entry is name_offset:u16le, location:u8; name offsets are relative to the runtime block. The current block has 40 entries, including 20 placeholder entries sharing the ? string and 20 distinct later names. Across all 11 local saves, only 11 location-byte positions vary. Every name offset and all 208 name-pool bytes match the decoded metadata file exactly. The location value 0xff is the carried-item marker consumed by the inventory list builder.

Block 5 was resolved directly from code.logic.serialize_resume_table at image 0x1364 and code.logic.restore_resume_from_table at 0x13a5. The serializer starts with SI = 0x0977, not the pointer stored there. It therefore emits a four-byte pair from the static cache-shaped head before following its +0x00 next pointer through the linked logic records. Current state makes that leading pair (logic 0, offset 0). Each linked record contributes:

logic_number:u16le = zero-extended record byte +0x02
resume_offset:u16le = word(+0x06) - word(+0x04)

After the last linked record, the serializer writes only 0xffff into the next record’s first word. It includes all four bytes of that terminator record in the returned block length, so the second word is ignored/stale rather than necessarily initialized by this pass. All 11 local saves happen to contain zero there. Their block lengths 16, 20, 24, and 28 are exact multiples of four and their nonterminal records begin with the static (0,0) head followed by the cached logic-0 record and other cached logics.

The restore helper scans block 5 from the beginning for the first record whose logic number matches a newly loaded cache record. On a match it sets the resume pointer to the newly loaded bytecode-entry pointer plus the saved offset; on 0xffff it returns without changing the default entry pointer. Only resource replay kind 0 calls this helper. The replay-pair sequence is therefore authoritative for which logics are loaded, while block 5 only supplies relative resume metadata. Corpus comparison confirms the two lists need not be equal: some block-5 cache records have no replay load, and some replayed logic numbers have no matching block-5 record and retain offset zero.

tools/agi_save.py now contains exhaustive maps/parsers for blocks 1 through 4 and the decoded metadata header that relates blocks 2 and 3. Region validation rejects gaps, overlaps, duplicate names, nonpositive sizes, and wrong total lengths. Tests cover all 1505 block-1 bytes, every field in each 43-byte object record, the fixed 21-record count, all 328 inventory/name bytes, the 100 replay pairs, the five unresolved block-1 ranges across all local saves, and the object-index event-byte invariant. The block-5 parser additionally validates four-byte alignment, byte-sized logic numbers, terminal placement, first-match lookup, duplicate entries, and ignored terminator payload. All five observed profile 2.936 save blocks now have byte-complete maps; the next specification task is the subsystem/version conformance matrix.

2026-07-10: clean specification conformance matrix

spec/src/conformance_matrix.md now turns the accumulated chapter boundaries into explicit claim guidance. It classifies the shared behavior of promoted profiles, the known 2.936/3.002.149 variants, dimensions supplied by each selected game’s data, partially specified domains, and behavior outside the current full-EGA target.

The matrix separates gameplay conformance from binary save interchange. The 2.936 save claim requires all five block maps, replay reconstruction, first-match logic-resume lookup, and byte preservation for opaque block-1 ranges. The 3.002.149 gameplay variants are listed, but arbitrary binary save interchange remains explicitly unavailable until its five blocks receive the same portable field mapping. A structural test requires all matrix sections and both profile claim statements to remain present.

Validation after the save mapping and matrix pass:

  • AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests passed 341 tests.
  • mdbook build docs passed.
  • mdbook build spec passed.
  • AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --check passed.
  • git diff --check passed.

2026-07-10: Gold Rush v3 save XOR key correction

The GR v3 save-map pass reopened the save helper at image 0x07bc after the original-engine save block failed to decode to a plausible runtime block under the previously modeled 59-byte key. The key observation is that helper 0x07bc loads DI = 0x072c and reads [DI], so 0x072c is a data-segment address. Reading the same numeric offset from the main executable image had accidentally sampled code bytes.

Local evidence:

  • games/GR/AGIDATA.OVL contains zero-terminated ASCII Avis Durgan at offset 0x072c.
  • The original-engine encoded save-block prefix c87769f82158e57363fb6f5dd6686f91457dca6606ac4011 decodes with repeating Avis Durgan to 8901008b011c9001049a011ca0011cb10108b80167c20167.
  • The decoded block matches the decoded GR OBJECT runtime payload, confirming the block transform rather than merely proving XOR round-trip behavior.

Implementation/test correction:

  • GR_V3_OBJECT_INVENTORY_XOR_KEY is now b"Avis Durgan".
  • The focused tests assert the exact key, the 11-byte wrap point, and the original save prefix known vector. This prevents any arbitrary repeating XOR key from passing by self-inverse round-trip behavior alone.

2026-07-10: Gold Rush v3 save blocks 2-5 structure pass

After correcting the GR v3 block-3 transform, the generated original-engine saves and decoded local metadata were rechecked for portable block dimensions.

Local reads:

  • games/GR/OBJECT, decoded with repeating Avis Durgan.
  • Generated original-engine saves under build/gr-v3-behavior/, including GRSG_001.1, GRSG_restore_001.1, and suite-generated signed/blank-prefix saves.

Observed metadata and save layout:

  • Decoded OBJECT header:
    • item-table byte size 0x0189;
    • maximum drawable object index 0x16 (22);
    • runtime inventory payload length 0x0713.
  • All checked generated GR saves have block lengths 1028, 989, 1811, 100, and 12.
  • Block 2 length 989 is exactly 23 * 43, matching object indexes 0..22 and the already source-mapped object-record stride.
  • Block 3 decodes with gr_v3_object_inventory_save_xor() to the decoded OBJECT runtime payload byte-for-byte. The decoded block contains 131 three-byte inventory entries followed by a 1418-byte zero-terminated name pool.
  • Block 4 length 100 is exactly 50 replay pairs.
  • The observed block 5 payload is 00 00 00 00 00 00 00 00 ff ff 00 00, matching the same four-byte logic-resume grammar used by the 2.936 profile: leading head record, cached logic-0 record, and terminator.

Implementation/test updates:

  • tools/agi_save.py now exposes generic object-record, replay-pair, inventory-block, and decoded-object-metadata helpers, plus GR-specific constants/wrappers for the observed profile 3.002.149 dimensions.
  • tests/test_save_resources.py now checks the GR OBJECT metadata dimensions when games/GR is present and checks the generated signed save dimensions when build/gr-v3-behavior/GRSG_001.1 is present.

Conclusion:

  • Blocks 2 through 5 of the observed Gold Rush v3 save state now have portable structure maps. Block 1 remains the outstanding byte-complete mapping task for profile 3.002.149 binary save interchange.

2026-07-10: Gold Rush v3 save block 1 structure pass

Goal: map the first observed profile 3.002.149 save block with the same byte-complete discipline used for profile 2.936.

Source observations:

  • GR save action 0x7d at image 0x29e5 writes the first state block at image 0x2aba..0x2ac9.
  • The writer helper call receives:
    • file handle from [bp-0xcc];
    • start address 0x0002;
    • length 0x0406 - 0x0002 = 0x0404.
  • Therefore block 1 is exactly DS:0x0002..0x0405, serialized as 1028 bytes.
  • The same action uses [0x0141] * 2 for block 4 length; observed saves have [0x0141] = 0x0032, matching 50 replay pairs.

Mapped block-1 partition:

PositionSizeMeaning
0x00007Signature prefix.
0x0007256Variables.
0x010732Flags.
0x01274Timer ticks.
0x012b2Horizon.
0x012d2Opaque word.
0x012f12Movement rectangle/coupling/prepared-picture fields.
0x013b2Movement rectangle enabled word.
0x013d2Opaque word, observed 0f 00.
0x013f2Replay-pair capacity, observed 0x0032.
0x01412Active replay-pair count.
0x0143196Forty-nine four-byte key-map entries.
0x02074Opaque gap before string slots.
0x020b480Twelve 40-byte string slots rooted at DS:0x020d.
0x03eb22Text/status/display/replay-checkpoint words and prompt byte.
0x04012Menu interaction gate word at DS:0x0403.
0x04031Key-release enqueue gate byte at DS:0x0405.

The detailed clean spec table expands the grouped ranges above into individual fields. The region map covers every byte exactly once and keeps four opaque ranges explicit: 0x012d..0x012e, 0x013d..0x013e, 0x0207..0x020a, and 0x03f6.

Implementation/test updates:

  • tools/agi_save.py now defines GR_V3_BLOCK1_REGIONS and split_gr_v3_block1(...).
  • tests/test_save_resources.py validates full byte coverage, the expanded 49-slot key map, twelve string slots, observed GR\0 signature prefix, replay capacity 0x0032, row defaults, menu gate, key-release gate, and explicit opaque ranges.

Conclusion:

  • The observed Gold Rush profile 3.002.149 save state now has a byte-complete structural map for all five blocks. The remaining save-related work is to resolve whether the explicit opaque ranges change during valid execution and to repeat this process for other interpreter/game profiles when promoted.

2026-07-10: configured modal-message parameters

Goal: resolve the action 0x97/0x98 parameter meanings from source before leaving text-window behavior as a partial clean-spec gap.

Source observations:

  • SQ2 action 0x97 at image 0x1c54 reads an immediate message number and calls shared helper 0x1c96.
  • SQ2 action 0x98 at image 0x1c71 reads a variable number, fetches var[arg0] as the message number, and calls the same helper.
  • Helper 0x1c96 reads three following bytes into:
    • [0x0d0b];
    • [0x0d0d];
    • [0x0d09].
  • If the third byte is zero, helper 0x1c96 stores 0x001e in [0x0d09] before display.
  • After the display path returns, helper 0x1c96 resets all three globals to 0xffff.
  • code.text.display_message_window at image 0x1d96 consumes those globals:
    • [0x0d09], when not 0xffff, overrides the maximum formatted text width;
    • [0x0d0b], when not 0xffff, overrides the computed message-window row;
    • [0x0d0d], when not 0xffff, overrides the computed message-window column.
  • Without a row override, the helper centers vertically from the formatted line count and the current display text area; it then adds the display base row [0x05dd].
  • Without a column override, the helper centers horizontally with (40 - formatted_line_width) / 2.

Conclusion:

  • The three configured-message operands are now resolved as one-shot row/column/width parameters. The remaining text presentation limitation is exact glyph bitmap selection for a target platform, not the meaning of actions 0x97 and 0x98.

2026-07-10: four-channel sound envelope spec promotion

Goal: close the stale clean-spec gap for four-channel sound attenuation semantics by rechecking the source-backed sound model and promoting only the portable command-output contract.

Commands run from /Users/peter/ai/agi/reverse:

  • sed -n '1,260p' PROGRESS.md
  • sed -n '1,220p' docs/src/current_status.md
  • sed -n '1,260p' docs/src/sound_and_audio.md
  • sed -n '1,260p' spec/src/sound.md
  • sed -n '1,380p' tools/agi_sound.py
  • sed -n '1,360p' tests/test_sound_resources.py
  • sed -n '7192,7238p' docs/src/clean_room_executable_notes.md
  • sed -n '7794,7824p' docs/src/clean_room_executable_notes.md
  • ndisasm -b 16 -o 0x7f96 build/cleanroom/AGI.decrypted.exe -e 0x8196 | sed -n '1,90p'
  • ndisasm -b 16 -o 0x801c build/cleanroom/AGI.decrypted.exe -e 0x821c | sed -n '1,170p'
  • ndisasm -b 16 -o 0x8162 build/cleanroom/AGI.decrypted.exe -e 0x8362 | sed -n '1,170p'
  • xxd -g 1 -s 0x17b8 -l 96 games/SQ2/AGIDATA.OVL

Source observations rechecked:

  • code.sound.driver_start copies four channel stream pointers, initializes all four channel countdowns to 1, stores the default envelope table pointer for each channel, disables each channel’s envelope index with 0xffff, and marks each channel active.
  • The playback tick decrements each active channel countdown. While the countdown is still nonzero, it calls the attenuation helper without consuming a new sound event.
  • When a countdown reaches zero, channels 0, 1, and 2 reset their envelope index to zero before reading the next event. Channel 3 (BX == 6) does not take that reset branch, so it preserves its current envelope index across event boundaries.
  • A channel terminator decrements the remaining-active-channel count, clears the channel active word, sets the channel base attenuation to 0x0f, and calls the attenuation helper.
  • The attenuation helper skips envelope and device-specific adjustment when the base attenuation is 0x0f.
  • Otherwise, if the envelope index is enabled, the helper reads one byte from the channel envelope table. Byte 0x80 disables the envelope and copies the previous envelope value back into the base attenuation. Other bytes are added to the event’s base attenuation as signed 8-bit deltas; negative underflow clamps to zero and positive overflow clamps to 0x0f.
  • The helper stores each clamped envelope result as the previous envelope value, adds runtime global attenuation adjustment byte 0x0020, clamps to 0x0f, applies the selector-2 non-silent below-8 adjustment, combines the result with the channel mask, and writes the resulting command byte.
  • The default table bytes are: fe fd fe ff 00 00 01 01 01 01 02 02 02 02 02 02 02 02 03 03 03 03 03 03 03 04 04 04 04 05 05 05 05 06 06 06 06 06 07 07 07 07 08 08 08 08 09 09 09 09 0a 0a 0a 0a 0b 0b 0b 0b 0b 0b 0c 0c 0c 0c 0c 0c 0d 80.
  • Existing helper/test coverage in tools/agi_sound.py and tests/test_sound_resources.py already models the table bytes, channel masks, delta-from-base behavior, clamps, sentinel handling, selector-2 adjustment, stop-silence bytes, PC-speaker divisor, and channel scheduling.

Documentation/spec updates:

  • spec/src/sound.md now defines the four-channel attenuation-envelope state fields, start/event initialization, channel-3 persistence, default envelope table, delta/clamp/sentinel behavior, global adjustment, selector-2 adjustment, and silent-base bypass.
  • spec/src/conformance_matrix.md no longer lists four-channel sound as a partial domain. Exact analog waveform synthesis remains outside the target; compatibility is defined at the resource, timing, tone/divisor, attenuation command, silence, active-state, and completion-flag boundary.
  • PROGRESS.md marks sound resources and playback as covered for the current spec target and removes the sound envelope from the highest-value remaining work.
  • tests/test_spec_book.py now guards the clean sound chapter’s timing and envelope coverage.

2026-07-10: script-visible resource reference audit

Goal: follow up on the multi-game census record errors without treating unreadable directory-looking entries as valid AGI behavior unless decoded scripts can observe them.

Commands run from /Users/peter/ai/agi/reverse:

  • python3 -B tools/game_census.py --game-dir games/KQ1 --game-dir games/KQ2 --game-dir games/KQ3 --game-dir games/KQ4D --game-dir games/LSL1 --game-dir games/PQ1 --game-dir games/SQ2 --game-dir games/GR
  • AGI_GAME_DIR=games/KQ1 python3 -B tools/disassemble_logic.py --stats
  • AGI_GAME_DIR=games/KQ1 python3 -B tools/disassemble_logic.py --limit 200 | rg -n "load_sound|start_sound|stop_sound|sound|0x62|0x63|0x64"
  • Python local print of KQ1 SNDDIR entries 30..39.
  • xxd -g1 -s $((34*3)) -l 24 games/KQ1/SNDDIR
  • AGI_GAME_DIR=games/KQ1 python3 -B tools/disassemble_logic.py --limit 100000 | python3 -c ...
  • Python local readable/unreadable scan for KQ1 and KQ4D sound resources.
  • AGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --limit 100000 | python3 -c ...
  • python3 -B -m py_compile tools/resource_reference_audit.py tests/test_resource_reference_audit.py
  • python3 -B -m unittest tests.test_resource_reference_audit
  • python3 -B tools/resource_reference_audit.py --game-dir games/KQ1 --game-dir games/KQ4D --output build/cross-version/resource_reference_audit_kq1_kq4d.json

Observations:

  • KQ1 Version 2.917 has four sound-directory entries that the generic v2 directory parser treats as present-like: sounds 34, 35, 36, and 37. Their directory bytes are immediately followed by ff ff ff absent entries, and the pointed-to data does not have a valid v2 volume header.
  • Decoded KQ1 logic uses immediate sound numbers 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21. None of the unreadable sound entries 34..37 are immediately referenced by decoded scripts.
  • KQ4D Version 3.002.102 has clean readable sound records including 70..79, plus a record-readable but malformed-family entry at 198 and many unreadable high sound-section entries. The earlier census also exposed entry 435; later byte-domain analysis corrected that entry as unaddressable rather than as a resource.
  • Decoded KQ4D logic uses immediate sound numbers 70..79 only. None of the unreadable high sound entries are immediately referenced by decoded scripts.

Implementation:

  • Added tools/resource_reference_audit.py. The tool scans decoded logic bytecode using the observed action/condition table signatures, collects immediate resource references for selected resource-loading/display actions, and compares those references with readable and unreadable directory entries. Variable-derived resource numbers are intentionally not counted as fixed immediate references.
  • Added tests/test_resource_reference_audit.py, which builds a synthetic split-directory fixture with one readable sound and one unreadable but script-referenced sound.

Validation:

  • python3 -B -m py_compile tools/resource_reference_audit.py tests/test_resource_reference_audit.py passed.
  • python3 -B -m unittest tests.test_resource_reference_audit passed.

Conclusion:

  • KQ1’s bad sound entries and KQ4D’s suspect high sound-section entries remain planning evidence, not promoted valid-resource behavior. If a future script reaches such numbers through variables, or a future interpreter version has a source-backed path for them, inspect that local case directly before adding a clean-spec rule.

2026-07-10: text glyph conformance boundary

Goal: resolve the remaining policy question in PROGRESS.md about whether exact platform glyph bitmaps belong in the current target profile.

Inputs reviewed:

  • spec/src/input_text_and_menus.md
  • spec/src/scope_and_conformance.md
  • spec/src/conformance_matrix.md
  • PROGRESS.md
  • Existing evidence notes and compatibility notes for prompt/status/input rows, text rectangles, configured modal-message geometry, and glyph-bearing QEMU probes.

Decision:

  • The current clean-room core specifies text at the character-cell boundary: text bytes, attributes, rows, columns, rectangles, modal blocking, prompt and status behavior, and save/restore of covered regions.
  • Exact 8-by-8 glyph bitmap shapes are a font-profile input, not an interpreter semantic for the portable full-EGA core. A bitmap-identical screenshot claim for text output must declare a font set in addition to the interpreter profile.
  • This decision matches the existing test strategy: QEMU probes can use glyph pixels as evidence that text was drawn, but the clean spec should not require a particular BIOS or platform font unless a future conformance tier deliberately adds that font profile.

Documentation updates:

  • spec/src/scope_and_conformance.md now states the text cell/font boundary.
  • spec/src/input_text_and_menus.md now has a Font boundary section instead of treating glyphs as a remaining presentation gap.
  • spec/src/conformance_matrix.md removes glyph output from partial domains and lists exact glyph bitmaps outside the current target unless a font profile is supplied.
  • PROGRESS.md marks the text/parser/menu/inventory chapter as covered for the current portable target.

2026-07-10: save reserved-state classification

Goal: resolve whether the remaining profile 2.936 and 3.002.149 block-1 ranges represent valid-execution state or reserved serialization capacity.

Source-first checks:

  • Rechecked the complete SQ2 and GR instruction listings for direct references to the two standalone saved words, the post-key-map tail, the string-bank tail, and the prompt/status gap.
  • SQ2 action 0x79 roots its key map at DS:0x0145 and stops after 0x27 four-byte records. The serialized bytes immediately following those 39 records contain ten additional zeroed four-byte records plus four zero bytes.
  • GR keeps the same key-map root but raises the source loop bound to 0x31. Its 49 records consume exactly the ten-record SQ2 tail, while the same final four zero bytes remain before string storage.
  • Both builds root valid 40-byte string slots at DS:0x020d. Source validation and local valid logic use only indexes 0..11. SQ2 serializes another twelve zeroed 40-byte records after those slots; GR removes that exact 480-byte bank and relocates the following text-state words to DS:0x03ed.
  • The standalone saved words at SQ2 DS:0x012f and DS:0x013f have no direct reference in the complete instruction listing. All 11 local SQ2 saves contain canonical bytes 0000 and 0f00; the observed GR save has the same values at the same serialized positions.
  • The one-byte prompt/status gap is zero in all local SQ2 saves and the observed GR save. In both layouts it separates byte state from following word state.

Interpretation:

  • The ranges are reserved serialization state, not unresolved game-visible fields. The post-key-map and post-string ranges are profile capacity/legacy storage demonstrated by their exact cross-version consumption or removal; the remaining words and bytes are reserved values or alignment storage.
  • A newly initialized save uses the observed canonical values. Restoring and re-saving an existing save preserves its supplied reserved bytes. Valid game operations do not assign behavior to those bytes.

Implementation and specification updates:

  • tools/agi_save.py replaces the unknown_* block-1 regions with explicit reserved words, inactive key-map tail, pre-string padding, reserved string bank, and text padding.
  • tests/test_save_resources.py checks the exact reserved partition and canonical values across all 11 local SQ2 saves and the generated GR save.
  • spec/src/session_and_persistence.md defines canonical initialization and byte-preserving interchange for reserved state.
  • spec/src/conformance_matrix.md now permits a binary save-interchange claim for the mapped Gold Rush/profile dimensions as well as profile 2.936.
  • docs/src/versions.md, docs/src/symbolic_labels.md, PROGRESS.md, and the current handoff note record the cross-version classification.

2026-07-10: KQ4D 3.002.102 bounded profile

Goal: apply the source-first cross-version workflow to KQ4D / AGI 3.002.102 and promote only differences supported by the local executable and valid data.

Repeatable tooling:

  • Added tools/compare_interpreter_tables.py. It accepts two explicit game directories and executables, detects each action/condition table from the established argc/meta signatures, compares parser contracts, and triages normalized handler-entry differences. Its report warns that a differing snippet is not itself a behavioral conclusion.
  • Added tests/test_compare_interpreter_tables.py for contract-versus-handler classification and local v3 table detection.
  • Generated:
    • build/cross-version/kq4d_gr_table_comparison.md
    • build/cross-version/kq4d_sq2_table_comparison.md

Table-level results:

  • KQ4D and GR expose 182 actions 0x00..0xb5 and 19 structured conditions 0x00..0x12 with zero parser-contract differences.
  • All 19 condition entry snippets normalize identically.
  • KQ4D/GR shared action differences are exactly: 0x12, 0x6f, 0x73, 0x76, 0x77, 0x78, 0x79, 0x89, 0x8a, 0xa3, 0xa4, and 0xa9.
  • KQ4D/SQ2 shared action differences are exactly 0x7c, 0x7d, 0x80, 0x84, and 0xad; all 19 condition entries again normalize identically.

Focused source conclusions:

  • KQ4D action 0x12 at image 0x1a1b passes its immediate byte directly to room switching. It does not call GR’s 0x7e..0x80 -> 0x49 remap helper.
  • KQ4D input actions retain SQ2’s display-mode/input-width branches. Action 0xa3 at 0x3cec sets word [0x0d58], 0xa4 at 0x3cfe clears it, and close-window action 0xa9 at 0x21fa clears both [0x0d58] and active window word [0x0d66].
  • KQ4D key mapping at 0x5081 compares its slot index with 0x27, preserving the 39-entry capacity. GR compares with 0x31.
  • KQ4D action 0xad at 0x6477 sets byte [0x05e5] to one and v3 action 0xb5 at 0x647f clears it. Action 0xb1 at 0x9838 writes menu gate word [0x05e3]; menu interaction at 0x9851 returns while it is zero.
  • The six v3-only slots have the same contracts and normalized handler bodies as GR. KQ4D also shares GR’s inventory temporary-state, restart marker, motion-mode-4 preservation, and save-XOR action variants.

Save-map source pass:

  • Save action 0x7d is at image 0x2a46. Its first-block write uses start 0x0002 and length 0x05e6 - 0x0002 = 0x05e4.
  • The first 0x05e1 positions match the complete profile 2.936 block-1 map. KQ4D appends menu gate word at block position 0x05e1 and release-gate byte at position 0x05e3.
  • The valid intended decoded eight-byte inventory metadata is 03 00 0f 03 00 00 3f 00: item-table size 3, maximum object index 15, one runtime entry, and name pool ?\0.
  • Therefore block 2 is 16 * 0x2b = 0x02b0 bytes and decoded block 3 is five bytes. KQ4D logic 0 calls action 0x8e(1), establishing one replay-pair slot and a two-byte block 4 for the selected demo data. Block 5 retains the common variable resume-record grammar.
  • The KQ4D save/restore paths and startup reader call XOR helper 0x07c3, whose body matches GR 0x07bc and repeats the Avis Durgan key at data 0x090c.

Local OBJECT packaging check:

  • The selected local games/KQ4D/OBJECT already contains the intended decoded eight bytes, although the source reader applies the XOR transform to the on-disk file. This is inconsistent with the valid source contract.
  • Added tools/v3_object_encoding_probe.py and tests/test_v3_object_encoding_probe.py. The tool makes unchanged and XOR-encoded writable copies under build/; it never modifies games/.
  • The first QEMU attempt failed before launch because the sandbox denied the local VNC bind. The elevated retry exposed a fixture bug: copied read-only directory modes prevented replacement. The tool now makes copied directories and files writable, matching the other fixture builders.
  • The initial successful captures were only the Sierra loader splash. A second run used --advance-loader and captured after Enter. Both variants reached the same KQ4D intro frame. That does not establish dual valid encodings: the intro had not exercised inventory metadata. The clean profile follows the source-defined XOR-decoded valid representation and treats the unchanged local packaging as an evidence anomaly.
  • QEMU report: build/kq4d-profile/object_encoding_after_loader_qemu_001.json.

Implementation/specification updates:

  • tools/agi_save.py now defines KQ4D block-1, object-record, inventory, and replay dimensions plus split/decode helpers.
  • tests/test_save_resources.py checks the 0x05e4 block-1 partition, 16 object records, one ? inventory entry, transformed five-byte block 3, and one replay pair.
  • spec/src/version_profiles.md defines a bounded 3.002.102 resource/logic/input/persistence profile.
  • spec/src/session_and_persistence.md adds the KQ4D demo block dimensions and makes the block-3 XOR rule common to the promoted v3 profiles.
  • spec/src/conformance_matrix.md explicitly withholds a full 3.002.102 gameplay claim until picture/view and object-update roles are compared.

2026-07-10: KQ4D 3.002.102 renderer/object source comparison

Goal: close the renderer/object blocker without inferring behavior from version numbers. Candidate helpers were paired by symbolic role, normalized snippets were used only for triage, and every reported mismatch was read in direct 16-bit disassembly.

The generic comparator gained repeatable explicit role pairs:

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ4D-3.002.102 --left-game-dir games/KQ4D \
  --left-exe games/KQ4D/AGI \
  --right-label GR-3.002.149 --right-game-dir games/GR \
  --right-exe games/GR/AGI \
  --role-pair LABEL=KQ4D_ADDRESS,GR_ADDRESS \
  --output build/cross-version/kq4d_gr_subsystem_roles.md

Rizin was also tried as a function-matching aid. Raw 16-bit analysis retained MZ-header-relative offsets, while rz-diff -t functions rejected the required 16-bit architecture configuration. It was therefore not used as evidence.

The completed pass covers 52 roles:

  • six view roles: resource load/discard, object view binding, loop-table and loop selection, and cel selection;
  • four picture lifecycle roles;
  • picture decode completion, command scanner, all channel/pattern/path/fill command handlers, coordinate reading, and line drawing;
  • buffer fill, horizontal/vertical line drawing, pixel writes, seed fill, and full refresh;
  • update-list construction, insertion, draw/refresh order, active/inactive partitions, list rebuild/flush, root membership, collision, control acceptance, dirty rectangles, and placement; and
  • frame timing/mode advance plus the object motion, pre-mode, mode-dispatch, and rectangle-boundary passes.

Exact KQ4D entries added to the symbolic map include:

code.object.update_dirty_rect              0x5b8c
code.object.place                          0x5d64
code.motion.update_objects                 0x1767
code.motion.pre_mode_and_boundary_update   0x065b
code.motion.dispatch_mode_step             0x0691
code.motion.rectangle_boundary_check       0x06f2

The first four above normalize exactly against GR 0x598f, 0x5cb3, 0x1720, and 0x0654. The mode dispatcher and boundary checker report a normalized difference only inside embedded jump-table bytes. Manual disassembly confirms that their selected branch bodies and post-table state changes match GR 0x068a and 0x06eb. The same embedded-table issue explains the earlier frame-mode report for KQ4D 0x4cf7 versus GR 0x4b0e.

The only renderer differences in the 52-role pass are KQ4D’s retained display-mode-2 branches in picture decode completion (0x6891), buffer fill (0x5673), and full refresh (0x5962). The underlying full-EGA command and raster helpers match. Non-EGA presentation is outside the current conformance target.

Conclusion: for valid expanded resources and the primary full-EGA target, KQ4D 3.002.102 shares the promoted picture, view, object, motion, animation, and refresh contracts. Combined with the previously mapped resource, logic, input, room, sound, and persistence behavior, this is sufficient to promote a 3.002.102 full-EGA gameplay profile. Binary save interchange remains scoped to the mapped KQ4D demo dimensions.

2026-07-10: KQ1 2.917 source-first profile

Goal: apply the same comparison workflow to an earlier v2 interpreter without assuming that its shared file layout implies shared behavior.

The first table report incorrectly used games/KQ1/AGI directly. Handler addresses aligned with SQ2, but every instruction decoded as nonsense because the file still had the loader transform applied. It was discarded. KQ1 was decoded with its own local loader key:

python3 -B tools/decrypt_agi.py --game-dir games/KQ1 \
  --output build/cross-version/kq1_agi.decrypted.exe

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ1-2.917 --left-game-dir games/KQ1 \
  --left-exe build/cross-version/kq1_agi.decrypted.exe \
  --right-label SQ2-2.936 --right-game-dir games/SQ2 \
  --right-exe build/cleanroom/SQ2_AGI.decrypted.exe \
  --output build/cross-version/kq1_sq2_table_comparison.md

The decoded report finds zero parser-contract or normalized-handler differences across 174 shared actions and 19 conditions. Direct disassembly of code.logic.action_dispatch at image 0x02c4 shows KQ1 accepts action bytes through 0xad; SQ2 accepts through 0xaf.

The v2 action count is source-independent table geometry: a fixed 0x20-byte trailer lies between the action and condition tables. Therefore (condition_base - action_base - 0x20) / 4 yields 170 actions for local 2.411/2.440, 174 for 2.917, and 176 for 2.936. The detector and tests now use this rule rather than assuming every split-layout build has 176 actions.

Fifty-one of the 52 picture/view/object/motion roles normalize exactly after accounting for the later linked-code displacement. The remaining role, code.object.frame_timer_update at image 0x0563, differs in one branch:

2.917: cmp byte [object+0x0b],4 ; jnz skip_four_loop_table
2.936: cmp byte [object+0x0b],4 ; jc  skip_four_loop_table

Thus 2.917 automatically direction-selects the four-loop table only for exactly four loops, while 2.936 accepts four or more. The two/three-loop paths, cel timer, movement, collision, placement, raster, update-list, and refresh roles match.

Save source and selected-game dimensions:

  • writer image 0x2753 and restore image 0x2512 use the common five-block grammar and a 0x05e1 first block;
  • decoded KQ1 OBJECT metadata has item-table size 0x51, maximum object index 17, 18 object records, 27 inventory entries, and a 0x0148 runtime inventory block;
  • logic 0 executes action 0x8e(100), defining 100 replay pairs; and
  • KQ1 block lengths are 0x05e1, 0x0306, 0x0148, 0x00c8, and a variable common logic-resume block.

The promoted 2.917 profile is limited to valid resource records. Four KQ1 sound directory entries fail the generic header check, and the immediate resource-reference audit finds no script reference to them.

2026-07-10: full KQ4 / AGI 3.002.086 source-first profile

Goal: inspect the newly supplied full KQ4 independently of the existing KQ4D demo, determine its actual interpreter profile, and promote only source-backed externally observable differences.

Initial inventory and resource checks:

python3 -B tools/game_census.py \
  --game-dir games/KQ4 --game-dir games/KQ4D

python3 -B tools/resource_reference_audit.py \
  --game-dir games/KQ4 \
  --output build/cross-version/resource_reference_audit_kq4.json

The full game embeds Version 3.002.086; KQ4D embeds Version 3.002.102. KQ4 uses combined KQ4DIR and KQ4VOL.N files. The selected copy contains 177 present logic entries, 148 readable pictures, 243 readable views, and 96 readable sounds. Three logic records and one sound record are direct; the remaining present readable records use v3 transforms.

Pictures 150 and 151 point into absent KQ4VOL.6; views 198 and 199 point into absent KQ4VOL.7. No decoded immediate reference selects these entries. That does not establish picture unreachability because KQ4 selects pictures through variables. The missing-volume records are excluded from the valid-data profile.

The first generic table report assigned every v3 build 182 actions and treated KQ4 trailer bytes as opcodes 0xb2..0xb5. Source geometry corrected this. A fixed 0x4a-byte data trailer separates the v3 action and condition tables, so

action_count = (condition_base - action_base - 0x4a) / 4

KQ4’s bases 0x061d and 0x092f yield 0xb2 entries, actions 0x00..0xb1. KQ4D and GR yield 0xb6. Direct dispatcher disassembly at KQ4 image 0x02c4 independently confirms maximum action 0xb1.

The corrected comparisons were generated as:

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ4-3.002.086 --left-game-dir games/KQ4 \
  --left-exe games/KQ4/AGI \
  --right-label KQ4D-3.002.102 --right-game-dir games/KQ4D \
  --right-exe games/KQ4D/AGI \
  --output build/cross-version/kq4_full_kq4d_demo_table_comparison.md

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ4-3.002.086 --left-game-dir games/KQ4 \
  --left-exe games/KQ4/AGI \
  --right-label SQ2-2.936 --right-game-dir games/SQ2 \
  --right-exe build/cleanroom/SQ2_AGI.decrypted.exe \
  --output build/cross-version/kq4_full_sq2_table_comparison.md

All 178 shared KQ4/KQ4D action operand contracts and all 19 condition contracts match. All condition handlers normalize identically. Shared action differences are limited to 0xad and 0xb0:

  • KQ4 action 0xad, image 0x6468, increments byte [0x15a0]; KQ4D sets its relocated gate to one.
  • KQ4 action 0xb0 consumes one ignored byte and reaches the no-op handler at 0x5467; KQ4D’s slot consumes no operands.
  • KQ4 action 0xb1, image 0x9812, consumes one byte and stores the menu interaction gate word [0x1d9e].

Every KQ4/SQ2 parser contract also matches over the 176 shared actions. Shared handler differences are 0x7c, 0x7d, 0x80, and 0x84: KQ4 already has the v3 inventory-selector temporary state, block-3 save XOR, restart prompt-marker behavior, and motion-mode-4 preservation. KQ4 retains SQ2’s direct room destination, display/input-width branches, 39-entry key map, and increment-style release gate.

A normalized 52-role KQ4/KQ4D source comparison covered view loading and cel selection, picture lifecycle, every picture command and raster helper, update lists, composition, collision, control acceptance, dirty rectangles, placement, motion, and animation. Primary full-EGA roles match after relocation. Embedded jump-table bytes account for normalized reports in frame mode and motion dispatch; the selected branch bodies match.

Two genuine observable object differences remain:

  1. At code.object.frame_timer_update, image 0x0563, KQ4 applies the four-direction loop table when loop count is four or greater. KQ4D uses the later exactly-four-unconditional/more-than-four-requires-f20 rule.
  2. At code.motion.update_objects, image 0x1751, KQ4 uses a strict-positive branch for proposed X. Exact zero therefore enters the clamp and left-boundary-report path. KQ4D uses a nonnegative branch and accepts exact zero without reporting. Both clamp negative values and report code 4.

KQ4’s motion dispatcher at 0x067a already accepts modes 1 through 4. Its remaining movement, collision, placement, picture, view, and refresh branch bodies match KQ4D for the full-EGA target.

Save and metadata source observations:

  • writer image 0x2a30 and restore image 0x27dd use the five-block grammar;
  • first block write length is 0x05e3 - 2 = 0x05e1, matching 2.936;
  • menu gate [0x1d9e] and release gate [0x15a0] lie outside block 1;
  • XOR helper image 0x07ad repeats key data at 0x08f9, Avis Durgan;
  • decoded OBJECT metadata has item table size 0x87, 45 items, maximum drawable object index 25, 26 records, runtime block length 0x02c6, and a 575-byte name pool; and
  • logic 0 executes action 0x8e(250), defining 250 replay pairs.

The selected full KQ4 block lengths are therefore 0x05e1, 0x045e, 0x02c6, 0x01f4, and a variable common logic-resume block. Block 3 is XOR-transformed on disk. These are full-game data dimensions, not universal 3.002.086 constants.

Conclusion: full KQ4 3.002.086 is a separate promoted profile between the current 2.936 and 3.002.102 evidence points. The analysis required no dynamic experiment because the dispatch boundary, handler deltas, object branches, and save dimensions were directly resolved from local disassembly and decoded valid game data.

Validation:

AGI_GAME_DIR=games/SQ2 python3 -B -m unittest \
  tests.test_save_resources \
  tests.test_disassemble_logic_tables \
  tests.test_compare_interpreter_tables \
  tests.test_spec_book

AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests
mdbook build docs
mdbook build spec

AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py \
  --report build/compatibility-suite/kq4_3002086_local.json

The focused run passed 58 tests. The full and aggregate runs each passed 362 tests. Both books built, and the aggregate opcode-evidence freshness check passed. An initial aggregate invocation without AGI_GAME_DIR was rejected by the intentional no-default-game guard; rerunning with the explicit SQ2 input produced the successful report above.

2026-07-10: KQ2 2.411 and LSL1 2.440 source-first profiles

Goal: apply the same bounded cross-version workflow to the two earliest local interpreters and promote only disassembly-backed observable differences.

Input and executable preparation:

python3 -B tools/game_census.py \
  --game-dir games/KQ2 --game-dir games/LSL1

python3 -B tools/decrypt_agi.py --game-dir games/KQ2 \
  --output build/cross-version/kq2_agi.decrypted.exe

file games/KQ2/AGI games/KQ2/SIERRA.COM games/LSL1/LL.COM

KQ2 is Version 2.411 and uses the loader-managed AGI transform. LSL1 is Version 2.440; LL.COM is already a full 38 KiB MZ executable. Both games use split direct v2 resource records, and the census reports no record errors.

Static table reports:

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ2-2.411 --left-game-dir games/KQ2 \
  --left-exe build/cross-version/kq2_agi.decrypted.exe \
  --right-label LSL1-2.440 --right-game-dir games/LSL1 \
  --right-exe games/LSL1/LL.COM \
  --output build/cross-version/kq2_lsl1_table_comparison.md

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ2-2.411 --left-game-dir games/KQ2 \
  --left-exe build/cross-version/kq2_agi.decrypted.exe \
  --right-label KQ1-2.917 --right-game-dir games/KQ1 \
  --right-exe build/cross-version/kq1_agi.decrypted.exe \
  --output build/cross-version/kq2_kq1_table_comparison.md

Both early action tables start at data 0x061b; condition tables start at 0x08e3. The v2 geometry rule yields 170 actions. Dispatcher image 0x0291 compares with 0xa9, independently confirming valid range 0x00..0xa9. All 170 KQ2/LSL1 table records and all 19 condition handlers match. Restart is the only normalized action-handler difference: KQ2 0x241f always prompts, while LSL1 0x2435 checks flag 16 first.

Against 2.917, shared handler triage is limited to screen shake, save/restore, restart, heap diagnostics, and configured-message table records. The screen shake difference is an alternate display-mode-4 branch outside the target. The early heap format at data 0x09ff is:

heapsize: %u
now: %u  max: %u
max script: %d

The later rm.0, etc. line is absent.

Configured-message correction: early table records for actions 0x97 and 0x98 contain argc 3, but the wrappers consume the message selector and then helpers KQ2 0x1c43 / LSL1 0x1c59 consume row, column, and width. Valid execution therefore advances four bytes. The original linear stats pass used three and drifted into false opcodes. action_operand_count(...) now applies the source-observed width, while stats preserve table_argc=3 as evidence.

Renderer/object report:

build/cross-version/kq2_lsl1_subsystem_comparison.md

The 55 role pairs cover resource loading, view binding/selection, picture lifecycle and every command/raster helper, update lists, collision, control acceptance, placement, animation, and motion. Forty-nine normalize outright. The frame-mode report differs only in embedded jump-table bytes; selected branches match. Four 0xf0..0xf3 reports differ because KQ2 updates the low draw-state byte and LSL1 updates the containing word. The decoder initializes the word to zero and all raster reads use only the low byte, so the upper-byte difference is not observable in valid full-EGA output.

The two observable picture differences are source direct:

  • KQ2 0xf9 image 0x61f5 executes lodsb; ret, consuming and ignoring the mode byte.
  • KQ2 0xfa image 0x625e loops over coordinate pairs and calls ordinary pixel writer 0x50fb once per pair.
  • LSL1’s complete pattern block 0x6294..0x6397 normalizes instruction for instruction with KQ1’s 0x6438..0x653b, including mode, radius, shape, and stipple handling.

A version-aware local picture model now selects KQ2’s early behavior with PictureRenderer(..., pattern_brushes=False). A focused test confirms ignored mode and two single-pixel pair plots. A command-aware resource scan found no 0xf9/0xfa in selected KQ2 pictures. Selected LSL1 pictures 2, 20, 29, 30, and 35 use 0xfa but never 0xf9, leaving the default radius zero.

Both early frame-timer helpers at 0x0530 select directional four-loop mapping only when loop count equals four. Motion dispatch 0x0647 accepts modes 1..3. Collision, placement, control scan, update lists, movement, and boundary bodies match after relocation.

Sound source map:

RoleKQ2LSL1
driver start0x7bc90x7cec
tick0x7c2b0x7d4e
stop core0x7cca0x7ded
event/tone output0x7ce90x7e0c
timer hook0x809a0x81c3

The early start path initializes four stream pointers, four countdowns to one, and four active words. It has none of the later envelope pointer/index/value arrays. The tick decrements active countdowns and emits only when consuming an event or terminator. Event attenuation is the control low nibble plus global adjustment byte 0x0020, clamped to 15; there is no envelope or selector-2 attenuation adjustment. Selector zero advances one channel and uses the PC-speaker path; every nonzero selector advances four channels.

KQ2 writes both high and low non-PC tone bytes unconditionally. LSL1 writes the high byte, then omits the low byte when (high & 0xe0) == 0xe0. Local sound model switches cover selector-8 channel behavior and KQ2’s unconditional low byte; early_sound_attenuation_output(...) covers the direct attenuation.

Save source and existing-file confirmation:

  • KQ2 writer 0x2693, restore 0x24af;
  • LSL1 writer 0x26b9, restore 0x24d5;
  • each writer loads 0x05e1, subtracts base address 2, and writes block-1 length 0x05df;
  • that partition equals the first 0x05df bytes of the 2.936 map and omits final two-byte saved_replay_count;
  • KQ2 decoded OBJECT: item table 0x00ff, maximum object index 16, 17 object records, 85 items, runtime block 0x0256;
  • LSL1 decoded OBJECT: item table 0x003f, maximum object index 16, 17 object records, 21 items, runtime block 0x0134;
  • KQ2 logic 0 calls action 0x8e(60); LSL1 calls 0x8e(144); and
  • two local KQ2 saves and three local LSL1 saves confirm first-four block lengths 05df/02db/0256/0078 and 05df/02db/0134/0120 respectively.

No QEMU experiment was needed: the opcode bounds, restart branch, picture commands, sound event path, and exact serialized lengths are direct source or existing valid-file observations. Focused graphics, sound, save, table, and spec tests passed after modeling these variants.

2026-07-10: same-version PQ1/2.917 and KQ3/2.936 cross-checks

Goal: test whether the promoted KQ1 and SQ2 behavior was version-shaped or accidentally game-specific.

KQ3 uses the loader transform and was decoded with its local key. PQ1 AGI is already MZ. Reports:

build/cross-version/pq1_kq1_table_comparison.md
build/cross-version/kq3_sq2_table_comparison.md

PQ1 and KQ1 have the same action/condition table bases, all 174 action records, all 19 condition records, and zero normalized handler differences. After MZ headers are removed, both images are 38,912 bytes and differ only at:

0x5aa4: PQ1 50 / KQ1 4b
0x5aa6: PQ1 00 / KQ1 31
0x5aa7: PQ1 65 / KQ1 00

These bytes lie in the expected-game signature literal. All 55 same-address resource, renderer, object, and motion roles normalize identically.

KQ3 and SQ2 have the same 176 actions and 19 conditions with zero table or handler differences. Their loaded image lengths are also 38,912 bytes. The only differences are signature bytes:

0x5b6c: KQ3 4b / SQ2 53
0x5b6e: KQ3 33 / SQ2 32

All 55 same-address subsystem roles match. This strongly supports treating the current gameplay rules as version profiles while keeping signatures and save dimensions as game data.

Selected-game save derivation:

  • PQ1 decoded OBJECT: item table 0x004b, maximum object index 19, 20 records, 25 inventory items, runtime block 0x016e, 291-byte name pool;
  • PQ1 logic 0 executes 0x8e(250), giving block 4 length 0x01f4;
  • valid local PQSG.8 and SQSG.9 confirm blocks 05e1/035c/016e/01f4; local SQSG.1 is truncated before block 4 and is excluded from valid evidence;
  • KQ3 decoded OBJECT: item table 0x00a5, maximum object index 16, 17 records, 55 inventory items, runtime block 0x0307, 610-byte name pool; and
  • KQ3 logic 0 executes 0x8e(127), giving block 4 length 0x00fe.

Local save helpers and tests now encode both sets of dimensions. KQ3 has no selected local save file, so its binary layout is source/game-metadata backed rather than existing-file confirmed.

2026-07-10: reusable normalized symbolic-role matcher

Added tools/match_interpreter_roles.py to make the recurring relocation pass repeatable. It requires explicit reference/target game directories and executables. Candidate entry points come from direct call/jump targets, action/condition handlers, same-address role anchors, and optional explicit candidates. Each reference role is disassembled and normalized with the same rules as the table comparator.

Validation runs:

python3 -B tools/match_interpreter_roles.py \
  --reference-label SQ2-2.936 --reference-game-dir games/SQ2 \
  --reference-exe build/cleanroom/SQ2_AGI.decrypted.exe \
  --target-label KQ3-2.936 --target-game-dir games/KQ3 \
  --target-exe build/cross-version/kq3_agi.decrypted.exe \
  --sq2-subsystems \
  --output build/cross-version/kq3_sq2_role_matches.md

python3 -B tools/match_interpreter_roles.py \
  --reference-label SQ2-2.936 --reference-game-dir games/SQ2 \
  --reference-exe build/cleanroom/SQ2_AGI.decrypted.exe \
  --target-label KQ2-2.411 --target-game-dir games/KQ2 \
  --target-exe build/cross-version/kq2_agi.decrypted.exe \
  --sq2-subsystems \
  --output build/cross-version/kq2_sq2_role_matches.md

KQ3 produced 48 unique and seven ambiguous exact matches, with zero unmatched roles. KQ2 produced 36 unique, five ambiguous, and 14 unmatched roles. The KQ2 unmatched set corresponds to known source variants or indirect picture-command entries, demonstrating that the report is useful triage rather than a false claim of equivalence. Four focused matcher tests cover role parsing, candidate collection, relocation normalization, and report classification.

2026-07-11: all-profile role audit and ordered resource discard

Goal: continue from the completed profile matrix, audit every promoted local interpreter with the normalized role matcher, and inspect a concrete valid resource-lifecycle edge from source before changing the portable spec.

Commands included:

python3 -B tools/match_interpreter_roles.py ... --sq2-subsystems
ndisasm -b 16 -e 0x40cd -o 0x3ecd build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -e 0x4daa -o 0x4baa build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -e 0x4be8 -o 0x49e8 build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -e 0x3b79 -o 0x3979 build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -e 0x163c -o 0x143c build/cleanroom/SQ2_AGI.decrypted.exe

Generated role reports under build/cross-version/ cover KQ1, PQ1, KQ2, LSL1, KQ4, KQ4D, and GR against the SQ2 role set. The unmatched rows belong to already documented categories: early pattern handlers, indirect short picture command entries, exact-four-loop selection, motion-mode changes, v3 resource containers, and alternate display refresh branches. No new version profile was justified by this pass.

The discard source pass found a portable lifecycle rule that had been stated too weakly. View lookup 0x3979 leaves in [0x1000] the address of the root or preceding next-link word that points at the selected record. View discard 0x3f0d writes zero through that address. Picture lookup 0x49e8 and discard 0x4bce perform the same operation through [0x1214]. Therefore discard truncates the family list at the selected record: the selected resource and all later records in that family become unreachable, rather than deleting only one entry.

Both discard paths flush object update lists, call code.heap.rewind_to (0x143c) with the selected record, rebuild update lists, and refresh the free-memory state. Since resource allocations share the bump heap, continuing to use a discarded payload is not a portable valid-data behavior. The clean spec now defines ordered same-family retention/truncation and requires a live selection to be released before discard. tools/agi_resources.py contains a narrow portable transition model with regression tests; no QEMU experiment was needed because both the link mutation and heap rewind are direct source observations.

A linear corpus audit then disassembled all present logic resources from KQ1, KQ2, KQ3, KQ4, KQ4D, LSL1, PQ1, SQ2, and GR. Discard actions are used in every profile: picture discard counts range from SQ2’s one to KQ4’s 125, and view discard counts range from zero in SQ2 to 76 in KQ4. None of the selected game scripts uses variable-view discard action 0x99.

The clearest allocator-discipline example is KQ2 logic 67. Its room-entry path loads views in the order 53,59,51,52,57,60. Its cleanup branch discards them in the exact reverse order 60,57,52,51,59,53.

Most picture cases use load_picture_var, prepare_picture_var, then discard_picture_var immediately. View cases similarly unwind temporary loads or stop/deactivate/rebind the relevant object around discard. These shipped-script observations support the valid-use discipline while the more general selected-and-later truncation rule remains direct executable evidence.

The corpus reports are disposable generated files under build/cross-version/*_logic_disassembly.txt. SQ2 was limited to logic numbers below 140 because its two known out-of-range trailing directory entries are not valid resource records.

2026-07-11: expanded local interpreter census

The private input set added BC, MG, MH1, MH2, SQ1, SQ1.22, and XMAS. The expanded read-only census was written to build/cross-version/game_census_expanded.md. No file under games/ was modified.

BC and SQ1.22 use the already observed loader-managed executable transform. They were decoded with each selected game’s own loader key into generated MZ files under build/cross-version/. The other five executables are direct MZ images.

Dispatch-table discovery initially failed for SQ1 and XMAS. Their action and condition signatures are separated by an early 0x26-byte trailer rather than the later v2 0x20 bytes. The detector now evaluates both observed v2 geometries and requires a unique plausible result. Source dispatch bounds and table geometry establish:

BuildAction table/countCondition table/count
SQ1 2.0890x03e7, 155 (0x00..0x9a)0x0679, 19
XMAS 2.2720x0417, 161 (0x00..0xa0)0x06bb, 19
BC 2.4390x061b, 1700x08e3, 19
MG 2.9150x061d, 1740x08f5, 19
MH1 3.002.1070x0620, 1820x0942, 19
MH2 3.002.1490x0440, 1820x0762, 19

Generated table comparisons show exact action/condition contracts and normalized handlers for BC versus LSL1, MG versus KQ1, and MH1 versus KQ4D. Role matching plus manual disassembly confirms BC’s shaped/stippled pattern and early sound paths as relocated LSL1 matches, and MG’s mapped full-EGA core as a KQ1 match. MH1’s mapped full-EGA core matches KQ4D, though its whole image has unmapped startup/data differences.

SQ1.22 and KQ1 loaded images have equal lengths and differ at only image bytes 0x5aa4, 0x5aa6, and 0x5aa7, all in the embedded expected-signature region. Every mapped role is an exact same-address match.

MH2 and Gold Rush loaded images have equal lengths and only 29 differing bytes. They classify as the action-0x12 call site plus Gold Rush helper 0x0062, three startup allocation-size words (0x0b80 versus 0x0a00), and the MH2/GR signature literal. MH2 0x19d4 reads its operand and calls ordinary room switch directly. This proves the Gold Rush 0x7e..0x80 -> 0x49 mapping is a build-specific rule, not generic 3.002.149 behavior.

For SQ1/XMAS, direct source slices established two portable version changes. SQ1 action 0x86 at image 0x01e8 has no operand and always performs sound stop, shutdown cleanup, and DOS termination. XMAS uses the same entry address but consumes one byte, exits immediately for 1, and otherwise shows a confirmation before exit. XMAS menu slots 0x9c..0xa0 point into the tiny stubs at 0x8400..0x8404, which advance the bytecode pointer and return with no menu state. SQ1 lacks those slots.

SQ1 position actions 0x25/0x26 at 0x7141/0x7199 write current coordinates, erase old drawn state when needed, and then update saved coordinates. XMAS 0x7196/0x71d3 writes current and saved coordinates together without that erase call. The XMAS string-equality entry delegates to 0x0d8b; that helper still normalizes both strings before comparison, so the shorter entry is not a behavioral difference.

SQ1 and XMAS OBJECT files begin with valid plain headers 4e 00 11 and 03 00 11. They define 26/one inventory entries. SQ1 uses the third byte as a 17-record count; XMAS increments it and allocates 18 records. XOR decoding them was the earlier analysis mistake. A transform-neutral parser and tests preserve the plain form. BC, MG, and the later inputs retain the repeating-key encoded form.

Original saves establish BC block lengths 1503,731,309,254,variable, MG 1503,903,5,220,variable, and SQ1.22 1505,774,328,100,variable. The later source trace below resolves why the MG save must not be associated with the currently bundled executable.

XMAS’s active files match its .ORG variants. ORIGINAL.BAT, LOGMETH.BAT, and VOLMETH.BAT show three selectable distribution methods. The original directory retains three disk-number nibbles while the selected payload is named VOL.0; generic installed-layout missing-volume errors are therefore a packaging artifact.

The initial MH1/MH2 resource-reference audit aborted on the first unreadable logic, which made it unsuitable for incomplete distributions. The audit now records unreadable source logics and continues through every readable script; a focused synthetic test verifies that references found before/after a bad logic remain reported.

The tolerant report build/cross-version/mh_resource_reference_audit.json finds MH1 logic 136 unreadable and direct references from readable scripts to unreadable views 7, 74, 75, 76, 77, and 85. MH1 is therefore incomplete for valid gameplay analysis. MH2 has 31 unreadable logics; no readable script directly references an unreadable resource, but skipped scripts prevent a complete reachability claim.

Early 2.089/2.272 subsystem completion and MG save resolution

Full 16-bit disassemblies were generated locally as build/cross-version/sq1_2089.ndisasm and build/cross-version/xmas_2272.ndisasm. The picture scanners at SQ1 0x5baf and XMAS 0x5c38 subtract 0xf0 and reject dispatch indices above 8. They therefore recognize 0xf0..0xf8; neither build has the later 0xf9/0xfa pattern slots. Command, coordinate, line, pixel, and fill bodies match after relocation.

Post-logic object routines SQ1 0x05cc and XMAS 0x04c6 test the automatic loop flags and loop counts but do not test movement countdown byte +0x01. Both use the two-direction table for loop counts 2 and 3, the four-direction table for exactly 4, and no automatic change for other counts. KQ2 0x0530 adds the later countdown-equals-1 gate.

The parse-string actions at SQ1 0x1817 and XMAS 0x17f7 both compare the slot selector with 6. SQ1 word-sequence predicate 0x0965 first requires the operand count to equal the parser count/error position, then compares every word with exact or 0x0001 wildcard semantics. XMAS 0x087d has the later remaining-count loop and explicit 0x270f tail terminator.

Sound start/tick/write entries are SQ1 0x74f5/0x7557/0x7615 and XMAS 0x7514/0x7576/0x7634. Both select only channel 0 for device zero, select all four channels otherwise, initialize countdowns to 1, and lack attenuation envelopes. Both call a device-2 helper (0x7657/0x7680) that adds 3 to a control low nibble below 8. SQ1 then emits both tone bytes and the resulting control byte. XMAS emits both tone bytes, adds the global adjustment to the entire resulting control byte modulo 256, then uses signed <= 15 to retain the result or emits 0x0f otherwise.

SQ1 save/restore at 0x2501/0x2335 writes and reads four blocks. The writer uses lengths 0x03db, 17 * 0x2b = 0x02db, 0x0153, and twice replay capacity word DS:0x0145, then closes the file. XMAS 0x24f5/0x2315 uses five blocks: 0x03db, 18 * 0x2b = 0x0306, 0x000f, twice the same capacity word, and the variable result of its logic-resume serializer.

The replay writer uses DS:0x0147 as its active pair count, confirming that the adjacent DS:0x0145 value serialized as block-4 length is capacity rather than current occupancy. The common block-1 tail is now source-partitioned: 39 four-byte key mappings at DS:0x0149, four padding bytes, six live 40-byte strings at DS:0x01e9, six reserved 40-byte records, and text/input/status state at DS:0x03c9..0x03dc. These correspond to block-relative offsets 0x0147..0x03da.

The earlier prefix is now fully partitioned from script handlers and runtime consumers. Relative to block start DS:0x0002, it contains signature, variables, flags, and timer through offset 0x012a; display mode at 0x012b; horizon at 0x012d; previous navigation event at 0x012f; motion rectangle bounds at 0x0131..0x0138; object-0 direction coupling, prepared picture, coupled direction, and rectangle-enable words at 0x0139..0x0140; a startup-only count at 0x0141; and replay capacity/count at 0x0143..0x0146. Action 0x3f writes horizon, 0x5a/0x5b configure the rectangle, 0x83/0x84 control direction coupling, picture preparation writes the prepared-picture word, and 0x8e sets replay capacity. The startup-only count has no later script-visible consumer and remains a preserved selected-build value rather than portable gameplay state.

For MG, tools/compare_interpreter_tables.py extracted the current MZ image to build/cross-version/mg_2915_image.bin; its disassembly is build/cross-version/mg_2915.ndisasm. Startup 0x0fa5 decodes OBJECT, reads third header byte 0x5a, increments it at 0x1001, and multiplies 91 by the 43-byte record size at 0x100e. Save writer 0x2751 writes block 1 length 0x05e1, the resulting block 2 length 0x0f49, and metadata block length 5. The bundled MGSG.1 instead contains 0x05df, 0x0387, and 0x0005 and is dated 5 November 1987, while AGI is dated 16 November 1987. The save is an older incompatible artifact, not evidence for current-header semantics.

Early object composition and movement comparison

The early builds use different temporary structures around object drawing. SQ1 0x036c scans active records in object-table order. Records whose flag mask 0x0011 equals 0x0011 receive a baseline/fixed-priority key and are selection-sorted by the smallest key; the first table record wins a tie. SQ1 0x0615b separately scans records whose same mask equals 0x0001 and draws them immediately in table order. Wrapper 0x0524 therefore draws the 0x0010-clear partition in object-number order, then draws the set partition in key order.

XMAS 0x02c1 takes a membership predicate and applies the same stable selection sort to either partition. Wrappers 0x060e9 and 0x06100 select the set and clear partitions respectively, and 0x06151 draws the clear partition followed by the set partition. The cross-partition order is the same as SQ1, but XMAS key-sorts the clear partition too. Overlapping valid objects in that partition can therefore produce different pixels when object number order and baseline/fixed-priority order disagree.

The associated restore and dirty-refresh passes preserve those memberships. SQ1 uses a temporary pointer array at 0x0653 plus a separate clear-partition array at 0x12ab; XMAS uses two linked-list roots. This storage choice is not itself portable behavior.

The SQ1 movement routine at 0x13c7 calls its restore helper before scanning objects and its rebuild/refresh helper afterward. The movement body beginning at 0x13d2 matches XMAS 0x13bf after relocation. The frame-mode updater SQ1 0x48a8 / XMAS 0x487a, collision test 0x3f8b / 0x3f6d, control acceptance 0x4e25 / 0x4df7, dirty-rectangle updater 0x4f4a / 0x4f23, and placement helper 0x515c / 0x51c6 likewise match after relocation. Thus the new portable delta is composition ordering, not movement, collision, or control acceptance.

Early shared-action boundary against 2.411

The XMAS/KQ2 action-table triage was read at each differing entry rather than treated as behavior by itself. Three shared action families establish an early-profile boundary that SQ1 shares with XMAS because those entries match between 2.089 and 2.272.

SQ1/XMAS show-picture handlers 0x43e7/0x43cc call the full refresh and set the picture-shown word. KQ2 0x4a4c first clears f15 and invokes the active text-window close helper. The older show action therefore does neither of those two prefix effects.

SQ1/XMAS object-distance handlers 0x4061/0x4043 calculate absolute baseline-Y distance plus absolute center-X distance and store AL directly. KQ2 0x46b9 retains the sum as a word, clamps values above 0xfe, then stores the result. Inactive input objects still produce 0xff in both families.

SQ1/XMAS target-motion handlers 0x6358/0x63a7 and 0x63ce/0x641d initialize mode 3, targets, saved/override step, completion flag, partition membership, and object-0 coupling state, then return. KQ2 0x6954/0x69d1 performs the same setup and additionally calls 0x1621, which calculates direction immediately and completes immediately when the target lies within the strict step bands. The old builds defer that helper’s effects until their next eligible target-motion pass.

The SQ1 action 0x4d handler at 0x652e clears direction byte +0x21 but not autonomous mode byte +0x22; XMAS 0x657d clears both. SQ1 action 0x4e at 0x6567 does not clear either object byte and only changes object-0 globals, including clearing the remembered navigation event. XMAS 0x65ba clears mode byte +0x22 for every object and applies the later object-0 coupling state.

SQ1/XMAS inventory handlers 0x2e89/0x2e87 implement the carried-item display inline. They scan three-byte metadata entries in item order, select only location byte 0xff, lay entries out in two columns, display the empty inventory text when needed, and wait for one acknowledgement event. Neither handler reads the later inventory-interaction flag or writes variable byte v25. KQ2 0x30d6 instead calls the later list selector that branches on the interaction flag and can write the selected item or 0xff to v25.

Early profile residual-edge closure

The remaining XMAS/KQ2 action-entry differences were reduced to their normal full-EGA branches. Input configuration 0x6eb0/0x752e stores the same base, bottom, row, and eight-pixel display offset outside KQ2’s alternate mode 2. Number input 0x6872/0x6e2b uses the same four-character editor and decimal low-byte result outside that mode. Input disable/enable and explicit refresh/erase similarly reach the same ordinary text-line and prompt state; the extra tests select alternate mode 2 paths.

KQ2 display-mode toggle 0x758a adds an interrupt-timing guard before the same mode-bit toggle and redraw sequence used by XMAS 0x6eec. This does not change the specified state transition or final pixels. The restart difference adds open-resource/file cleanup before accepted reinitialization. Successful view-description preview differs in temporary backing allocation and diagnostic checks but uses the same selected cel, centered placement, description text, modal wait, and restoration. Joystick calibration remains hardware-conditional and outside the full-EGA gameplay target.

All shared condition contracts are covered. Successful valid-data view binding, loop selection, and cel selection share the same transitions; SQ1 adds upper bound diagnostics that do not alter valid selectors. Combined with the prior picture/raster, composition, frame, collision, control, placement, dirty-region, motion, sound, save, and accepted-action analysis, no residual valid full-EGA domain remains unclassified for the observed 2.089 and 2.272 builds.

2026-07-11: directory-tail addressability closure

Goal: classify KQ1 and KQ4D sound-directory anomalies without promoting garbage-memory or malformed-resource behavior.

Static evidence and local reads:

  • KQ1’s 2.917 code.resource.read_volume_payload_once is an exact normalized match for the 2.936 direct-record reader. There is no sound-specific alternate volume-header path.
  • KQ1 SNDDIR entries 34 through 37 are raw triples 22 0e 2f, 22 0e 8f, 22 0e ed, and 22 12 6b. They select VOL.2 offsets 0x20e2f, 0x20e8f, 0x20eed, and 0x2126b.
  • The selected KQ1 VOL.2 is only 0x1630b bytes. Every one of those offsets lies beyond the file by 43,812 to 44,896 bytes, so no record header exists to interpret.
  • KQ4D’s combined-directory sound section begins at DMDIR:0x02d5. Resource selectors are unsigned bytes, so only entries 0 through 255 are reachable; directory-like triples later in the file cannot be selected.
  • Within that byte range, entries 70 through 79 select clean sound records. Entry 198 points to DMVOL.0:0x0778, whose v3 metadata selects picture-nibble expansion and whose expanded payload begins with picture commands. Parsing it as sound fails with a truncated channel event.
  • Entries 221 and 223 through 236 point into stored payload bytes rather than to 12 34 record headers. Decoded KQ4D scripts reference only sounds 70 through 79.

Implementation and validation:

  • tools/agi_resources.py now reports an explicit directory-offset-beyond- volume diagnostic before checking a header.
  • Directory parsing now exposes at most 256 entries per family, matching the script-visible byte resource-number domain.
  • Focused resource, census, reference-audit, and specification tests passed 34 tests. The regenerated census reports four KQ1 out-of-volume sound entries and 15 addressable KQ4D bad-header sound entries; no referenced unreadable entry remains in either game.

Conclusion: KQ1 entries 34 through 37 are stale or out-of-package locations, not an alternate sound format. KQ4D bytes beyond sound entry 255 are unaddressable file tail, while its reachable high anomalies are malformed or cross-family entries outside the valid-data contract.

2026-07-11: top-level cycle ordering closure

Goal: replace the provisional runtime-cycle summary with an instruction-backed ordering contract while keeping interrupt delivery details outside the portable specification.

The decrypted SQ2 2.936 executable was regenerated from the immutable local game input and its main loop was disassembled with:

python3 -B tools/decrypt_agi.py --game-dir games/SQ2 \
  --output build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -o 0x0100 -e 0x0300 \
  build/cleanroom/SQ2_AGI.decrypted.exe

Direct instruction order at image 0x0150 establishes the following call and state sequence:

  • 0x015b calls code.cycle.wait_for_pacing at image 0x7f78;
  • 0x015e calls code.input.clear_status_bytes at image 0x4c23, which clears 50 bytes beginning at data 0x1218, then the loop clears flags 2 and 4;
  • 0x0175 calls code.input.update_timed_events at image 0x61f2, followed by code.input.process_cycle_events at image 0x357c; that helper clears data bytes 0x001c (v19) and 0x0012 (v9) before processing events;
  • the loop mirrors the object-0/global direction state, then calls the pre-logic object pass at image 0x0644;
  • after snapshotting v3 and flag 9, 0x01b4 calls code.control.save_abort_context at image 0x7ee0, and 0x01bd invokes logic 0;
  • a zero logic return clears v9, v5, v4, and flag 2, refreshes the saved v3 value, and re-enters logic 0 without branching through the pacing, input, direction-mirror, or pre-motion calls;
  • normal return restores object 0’s direction and conditionally redraws the status line when the saved v3 or flag-9 values differ;
  • the loop clears v5, v4, and flags 5, 6, and 12; and
  • alternate text mode at data 0x1757 suppresses the final object/frame update call at image 0x0563; otherwise that call runs before the loop returns to pacing.

The separately examined timing path increments the pacing and elapsed-time counters independently of this call chain. Existing sound evidence likewise places sound progression in timer-driven work. The behavioral contract therefore models timer and sound ticks as asynchronous inputs observed at cycle boundaries, not as a fictitious ordered call inside the synchronous loop. It does not require an independent implementation to reproduce DOS interrupt delivery between individual bytecode instructions.

Conclusion: the 2.936 top-level cycle now has a complete portable phase order, including exact transient-state lifetimes, immediate logic-0 re-entry, and the alternate-text-mode update gate. Instruction addresses and helper boundaries remain evidence-only and are not part of the clean-room specification.

2026-07-11: QEMU inverse-text corruption and VGA BIOS patch

Goal: determine why SQ2 2.936 produces repeated glyphs in black-on-white text under QEMU, distinguish game behavior from emulator behavior, and produce a reproducible workaround that does not modify the immutable game input.

Font-address helper audit

The initial FIXAGI.COM experiment asked INT 10h/AX=1130h, BH=03h for the active 8-by-8 font, enabled the i440FX F0000h shadow for writes, and copied 1024 bytes to F000:FA6E. Static inspection found that its original copy loop changed DS before loading the saved source offset. The subsequent mov si,[srcoff] therefore read from the VGA ROM segment rather than from the COM program. Memory dumps confirmed that this version replaced a correct font with unrelated bytes.

After moving the saved-offset load before the DS change and adding a byte-for-byte verification pass, the helper copied the intended font. The source and destination font SHA-256 was:

4cd5079f275e2544bd396e9acd38895f88cba007be4b7069e9e2628166801413

The installed QEMU system BIOS already contains that same 1024-byte table at the ROM location corresponding to physical F000:FA6E. Leaving the i440FX PAM0 region read-only (10h), write-only (20h), or read/write (30h) did not change the corrupted dialog. The font remained intact while SQ2 ran. Changing from QEMU standard VGA to QEMU Cirrus VGA also reproduced the same failure. These observations reject missing font bytes, shadow write protection, and the selected emulated VGA card as causes.

Interpreter instruction path

Disassembly of the decrypted SQ2 executable identified the graphics glyph preparation helper at image 0x2c55. For ordinary characters it reads eight bytes from F000:FA6E + character * 8 into a scratch glyph at data offset 0x0ea6. The inverse path complements the four copied words and returns character byte 80h.

The caller around image 0x2aa5 saves interrupt vector 43h, temporarily sets that vector so character 80h resolves to the scratch glyph, invokes BIOS INT 10h/AH=09h, and restores the vector. This means the interpreter does not directly write inverse glyph pixels in this path; it asks the video BIOS to draw from a temporary font vector.

A QEMU GDB-stub/rizin trace stopped on the first inverse dialog character, S. The source glyph at physical 0xffd06 was:

78 cc e0 70 1c cc 78 00

The copy at the runtime scratch address was identical, and the inversion produced:

87 33 1f 8f e3 33 87 ff

Immediately before INT 10h, vector 43h contained 103b:0aa6, the BIOS character height was 8, and therefore character 80h selected exactly the scratch glyph at 103b:0ea6. Registers included AX=ff80, BX=000f, and CX=0001. A hardware read watchpoint on the scratch glyph did not trigger during the BIOS call. The firmware did not dereference the vector SQ2 had correctly installed.

Firmware confirmation and correction

The official LGPL VGABIOS 0.7a binary was tested as an alternate QEMU option ROM. It reproduced the repeated glyph. Inspection of its corresponding source showed why: its planar, CGA, and linear graphics character renderers select private compiled-in vgafont8, vgafont14, or vgafont16 arrays rather than the current interrupt-vector font.

tools/setup_vgabios.py now reads the tracked pristine 0.7a standard VGA ROM from third_party/vgabios/, checks SHA-256 cd9fdd6a789dcd22b8a6b3b152788d43238de49cce674cff57bdeb94580246c6, and applies an exact binary patch assembled from tools/vgabios_int43_patch.asm. The patch redirects the planar EGA glyph-byte fetch through the current vector 43h, occupies 44 bytes of verified unused ROM padding at 0xa1a4, and updates the option-ROM checksum. It deliberately leaves the firmware’s pixel mask, attribute, plane, and destination logic unchanged.

The repository copy is byte-identical to the official upstream binary. Its LGPL 2.1 license and provenance record include official binary and complete source archive URLs, the binary digest above, and source-archive SHA-256 9d24c33d4bfb7831e2069cf3644936a53ef3de21d467872b54ce2ea30881b865. The generated patched ROM has deterministic SHA-256 cfbbc5e3f97cb40cbc315b68e1e52d4488e6e27a47b339452a6a4ebf00f01247 and remains ignored disposable output.

The corrected ROM was run with:

qemu-system-i386 -m 16 -boot c \
  -drive file=build/freedos/freedos.img,format=raw,if=ide,index=0,media=disk \
  -vga none \
  -device VGA,romfile=/absolute/path/build/vgabios/vgabios-0.7a-int43.bin \
  -display vnc=127.0.0.1:6 -monitor stdio

SQ2 was advanced through its introduction, station scene, and default-name screen without running FIXAGI.COM. The first playable-room dialog rendered the expected distinct black glyphs on white, including the complete text beginning Orbital Station 4 is one of many. This validates the diagnosis and establishes the patched VGA BIOS as the preferred QEMU screenshot environment.

Local verification after integrating the generated ROM into shared harness launches passed 425 tests with four expected optional-fixture skips. The full compatibility runner, both mdBooks, opcode-evidence checks, deterministic ROM rebuild comparison, and whitespace validation also passed.

After promoting the pristine ROM to a tracked third-party input and integrating the builder into normal FreeDOS setup, a separate temporary FreeDOS image was created successfully without altering the populated manual-test image. Final verification passed 427 tests with the same four expected skips, rebuilt both books, and passed the complete compatibility and opcode-evidence gates.

Behavioral conclusion: the original interpreter consumes an external 8-by-8 font profile and can temporarily substitute glyph data for inverse graphics text. Exact glyph shapes remain a declared platform-font input in the clean specification. The firmware implementation details and QEMU workaround remain in the evidence book and harness documentation rather than becoming portable engine requirements.

2026-07-11: KQ4 route and v3 message storage

The full KQ4 playthrough index initially failed because decode_logic_messages() applied the repeating-key XOR to every resource. Scanning all present records showed that failures clustered across dictionary- expanded logic, whose expanded message regions were already readable, while KQ4’s three direct logic records retained encoded text.

Direct KQ4 source inspection established the selection rule:

  • code.resource.read_record at image 0x311b reads expanded and stored lengths from the v3 header.
  • When the lengths match, the direct-read branch at 0x3304..0x3324 sets word [0x0f5e] to one.
  • When lengths differ, dictionary expansion at 0x3326..0x334e clears that word.
  • code.logic.load_record at 0x13d9 constructs the logic record. At 0x1458..0x148c it tests [0x0f5e] and calls the repeating-key helper over the message text only when the word is nonzero.

The disassembler and playthrough index now derive message decoding from the volume record transform. All 177 present KQ4 logics decode, yielding 5,148 parser predicates, 469 room transitions, 86 score-changing sites, and declared maximum score 230.

The game-resource pass reconstructs a complete static 230-point route. Its notable score constraint is the frog chain: taking the crown directly from the held frog awards two points and lets the frog return; the catch parser remains available, so recatching and kissing it enters the separate three-plus-two point transformation sequence. Three successive Lolotte errands each award seven points, and the first two board crossings each award two. With alternate room copies collapsed, the resulting ledger totals 230.

agi_graphics.py now obtains picture and view payloads through the generic version-aware resource reader rather than opening v2 split directory names. The SQ2 graphics baseline still passes all 91 focused tests. A KQ4 qualitative sweep decoded all 146 locally readable pictures with patterned brushes disabled because brush-table offsets remain interpreter-build-specific. Pictures 150–151 remain unreadable because the selected private copy lacks KQ4VOL.6.

2026-07-11: PQ1 static winning-route reconstruction

The immutable local games/PQ1 evidence set was indexed without modifying its files. Its 118 present logic resources all decode. The resulting index contains 3,147 parser predicates, 108 explicit room transitions, 114 positive score operations, and 25 inventory slots. Logic 101 assigns maximum score 245; logics 79, 103, and 104 establish the successful Bains capture, trial, and terminal parade sequence.

The decoded messages and transitions establish a complete story dependency chain: patrol briefing and equipment, Helen Hots citation, homicide scene, Blue Room and cafe encounters, DUI and Hoffman arrests, transfer to Narcotics, no-bail warrant, park drug bust, Cotton Cove identification, Hoffman/Taselli/ Bains research, Hotel Delphoria cover, back-room poker, Room 404 transmitter, Bains capture, conviction, and parade.

A syntax-only sum of positive score operations is 270. Direct branch analysis has proved 13 points of mutually exclusive or lower-valued alternatives across the traffic-stop proposition, gun-locker parser branches, phone-number parser branches, drug-source questioning, duplicate newspaper availability, and the late Room 404 fallback. The remaining 257 is an upper bound: another 12 points must be excluded by cross-phase reachability to reconcile the declared 245. This gap is documented rather than silently assigned to route actions.

Two bundled save files were parsed read-only. Both preserve score 156 of 245 near the hotel operation and agree on the selected game’s save dimensions and inventory names. They are consistency evidence, not route sources. A brush-disabled sweep decoded all 71 present pictures and qualitatively corroborated the station, patrol, investigation, jail, courthouse, hotel, poker, and ending geography.

2026-07-11: LSL1 static maximum-score route

The immutable local games/LSL1 evidence set was indexed without modifying its files. All 46 present logic resources decode, yielding 2,274 parser predicates, 16 explicit room switches, 49 positive score operations, and 21 inventory slots. Logic 51 assigns maximum score 222. The positive score operations also sum to 222, with no negative score operation.

Direct control-flow and message analysis places every score site in one route. The route passes through Lefty’s restroom, hallway, television/pimp bypass, protected upstairs encounter, and fire escape; the convenience-store, telephone, wine, and pocket-knife chain; casino funding, disco pass, Fawn’s gifts, and the chapel; Fawn’s wine trap and rope escape; the return for the window pills; Faith and the penthouse control; Eve’s apple; the inflatable-doll side sequence; and logic 45’s final 25-point terminal encounter.

The independently grouped score equation is 3 + 55 + 29 + 53 + 82 = 222: three general awards, 55 around Lefty’s, 29 from the store/telephone chain, 53 from casino/disco/chapel, and 82 from the hotel/penthouse chain. A qualitative brush-disabled sweep decoded all 43 present pictures and corroborated the compact geography. Random adult-verification questions, casino outcomes, movement, and real-time sunrise remain original-interpreter replay work.

BC maximum-score route reconstruction

The immutable local games/BC input was indexed with tools/logic_playthrough_index.py. All 85 present logic resources decoded. Logic 107 assigns maximum score 230; the 40 positive award sites have a raw sum of 463. Inspection of their enclosing conditions and state writes established the shared alternatives described in games/bc_playthrough_analysis.md. Selecting one member of each group reduces the total exactly to 230. Logic 71 compares the final score with maximum minus 15 and displays its strongest congratulations at or above that threshold; the selected terminal reward supplies the last 15 points.

The selected static route uses the Gwystyl Hen Wen rescue, wagon castle entry, one dungeon escape clue, one Eilonwy introduction, the hidden-passage and Dyrnwyn chains, the sword-for-cauldron bargain, the mirror resolution, and the returned-sword ending. A qualitative picture sweep rendered all 69 present pictures with patterned brushes disabled. No original-interpreter playthrough has yet established movement and timing reachability for the complete route.

MG complete-route reconstruction

The immutable local games/MG input was indexed and disassembled without modification. All 73 present logic resources decode. The selected build is Mixed-Up Mother Goose under interpreter 2.915, with 49 pictures, 100 views, 53 sounds, and 159 indexed room switches.

Logic 0 assigns 18 as the maximum, initializes the score variable to zero, randomly distributes twenty custom props among eligible rooms, and implements a one-prop carried state outside the ordinary inventory table. A successful recipient interaction calls one of eighteen rhyme-animation logics. Their common completion signal is converted by logic 0 into exactly one score/count increment after the animation and sound complete. At count 18, logic 0 enters logic 102, which runs the cast celebration and displays the final congratulations before disabling normal game-control menu entries.

The twenty props serve eighteen goals. Seventeen rhymes consume one prop each; Old King Cole requires pipe, bowl, and fiddlers in that order and increments the counter only after the fiddlers. The complete recipient/prop map, regular 35-room outdoor grid, nine attached rooms, terminal contract, and replay plan are recorded in games/mg_playthrough_analysis.md. A qualitative brush-disabled sweep decoded all 49 present pictures. Exact randomized placement and movement remain original-interpreter replay work.

MH1 partial terminal-route reconstruction

The immutable local games/MH1 input identifies Manhunter: New York 1.22 under interpreter 3.002.107. Its v3 combined directory advertises 66 present logics, but logic 136 selects MHVOL.3:0x1d323, beyond that file’s available length of 0xfe00. The playthrough indexer’s error path was corrected to use the structured directory-entry fields, so it now records that unreadable logic and continues. Sixty-five logics decode, yielding 11 parser predicates, 138 room switches, 93 inventory mutations, and no score or maximum-score writes.

The readable logic establishes a four-phase assignment value: Bellevue Hospital, the missing Grand Central maintenance robot, the dead Orb at Greenwood Cemetery, and illegal Alliance-computer access. Logic 131 changes phase 1 to 2 after its Data Card report sequence. Returning home with the common completion flag advances phase 2 to 3 and phase 3 to 4. The late game collects Modules A through D, records four installed-module states in logic 154, enters the Alliance and ship controls, and records four distinct bomb-drop states in logic 151. Only all four states together switch to logic 162, whose conclusion displays To be continued.... No numeric score participates in this contract.

Resource completeness prevents a full route claim. The missing logic’s room owns the Crowbar in OBJECT; readable scripts directly reference unavailable views 7, 74, 75, 76, 77, and 85; and only 196 of 237 present picture entries decoded from the available volumes. The proven phase skeleton, inventory evidence, ending condition, retry/death observations, and exact remaining work are recorded in games/mh1_playthrough_analysis.md.

MH2 partial route and missing-volume boundary

The immutable local games/MH2 input identifies Manhunter: San Francisco 3.02 under interpreter 3.002.149. Its combined directory advertises 96 present logics, but 31 select absent file MH2VOL.3. The playthrough indexer was extended to catch missing-file errors through the same explicit unreadable- resource path used for malformed and out-of-range records. It now retains each missing logic’s number, advertised volume, offset, and error while continuing to index the other 65.

The readable set contains six parser predicates, 135 room switches, 32 inventory mutations, and no score or maximum-score writes. Logic 0 directly dispatches to many missing rooms, including 131-133 and numerous late rooms from 164 through 194. Logic 172 also switches to missing room 175. The gap is therefore on ordinary room flow rather than a group of proved-unused records. The same absent volume leaves 71 of 248 present pictures, 67 of 181 present views, and 73 of 195 present sounds unreadable.

Readable story evidence establishes the New York-to-San Francisco pursuit, acquisition of the ID Card and MAD, and two assignments selected by the phase value: a burning boat at Pier 5 and a human on the Ghirardelli Square sign. It also maps the available travel network, 32 inventory entries, Three Aces prize choices, hybrid Rat/Dog/Human experiment disclosures, and the planned release near Fisherman’s Wharf. The words The End in logic 146 belong to an in-world Orb presentation, while several YOU WON messages belong to local arcade sequences; neither is a whole-game terminal contract. The actual ending and complete winning route remain unavailable until the matching MH2VOL.3 is provided. The bounded route and replay backlog are recorded in games/mh2_playthrough_analysis.md.

Cross-interpreter picture brush audit

The brush audit began from each interpreter’s disassembled picture command scanner, not from rendered output. For encrypted AGI files, the existing local loader transform produced the loaded MZ image; direct MZ interpreters were stripped to their load image. A structural scanner signature then yielded the final accepted command, dispatch-table offset, and handlers. The brush data was located independently through the eight observed column-mask words at four-byte spacing, followed by the eight radius pointers.

The first audit invocation imported agi_graphics, whose global command-line path parser consumed the first repeated --game-dir; that incomplete report omitted SQ1. The audit tool was corrected to keep its pure structural scanner local, and the all-game run then reported all 16 selected directories.

The command was:

python3 -B tools/brush_table_audit.py \
  --game-dir games/SQ1 --game-dir games/XMAS \
  --game-dir games/KQ2 --game-dir games/BC --game-dir games/LSL1 \
  --game-dir games/MG --game-dir games/KQ1 --game-dir games/SQ1.22 \
  --game-dir games/PQ1 --game-dir games/SQ2 --game-dir games/KQ3 \
  --game-dir games/KQ4 --game-dir games/KQ4D --game-dir games/MH1 \
  --game-dir games/GR --game-dir games/MH2 \
  --output build/brush-audit/report.md

SQ1 2.089 and XMAS 2.272 accept commands only through 0xf8. KQ2 2.411 accepts 0xf9/0xfa but contains no shaped-brush table: source inspection confirms that 0xf9 consumes and ignores its operand and 0xfa calls the ordinary pixel writer for each coordinate pair. Every build from BC 2.439 onward contains the complete shaped/stippled routine and directly references the structurally selected mask and pointer tables.

The full v2 builds share radius-one rows e000 e000 e000 and horizontal-clamp immediate 0x0140. Every observed v3 build instead uses radius-one rows 4000 e000 4000 and immediate 0x013e. Since the 4000 rows overlap neither examined logical-column mask, the normal masked output is two center-row pixels rather than v2’s 2 by 3 block. All eight column masks and all other radius rows are identical. Comparing a normalized 0x104-byte brush-routine window found that clamp immediate to be the only code difference across the full v2/v3 families. This corrects the earlier broad statement that GR’s pattern plotter was only a relocation of SQ2’s: most of the routine is, but these two observable brush-family differences are real.

The local renderer was changed to discover brush tables structurally. It now uses the selected radius-one family to apply the corresponding edge clamp and automatically falls back to KQ2’s point behavior when no complete brush table exists. The games input directories were read only; all decoded images and the generated report remained under disposable build/brush-audit/.

Replacement MH1/MH2 resource-completeness audit

The selected games/MH1 and games/MH2 inputs were replaced with alternate copies. A fresh census found that both now include volume 3. The checks were:

python3 -B tools/game_census.py \
  --game-dir games/MH1 --game-dir games/MH2 \
  --format markdown \
  --output build/cross-version/mh_replacement_census.md
python3 -B tools/resource_reference_audit.py \
  --game-dir games/MH1 --game-dir games/MH2 \
  --output build/cross-version/mh_replacement_reference_audit.json
AGI_GAME_DIR=games/MH1 python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/mh1-replacement/index.json
AGI_GAME_DIR=games/MH2 python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/mh2-replacement/index.json

MH1 now decodes all 66 present logics, 237 pictures, and 138 views. Its sound directory contains 101 present-looking entries. Entries 136 and 138 point into ordinary bytes in MHVOL.0 rather than v3 record headers and are not referenced by decoded scripts. The remaining 99 sounds all decode, and the immediate reference audit finds exactly those same 99 sound numbers. Every statically addressable view also resolves.

MH2 now decodes all 96 present logics, 248 pictures, and 181 views. Sound entries 215 and 216 select absent MH2VOL.6, but neither is referenced. The other 193 sounds all decode and exactly match the script-referenced sound set. No source logic is skipped in either replacement. The four sound-tail entries are classified as inert directory debris rather than missing valid gameplay resources.

The first closure check falsely reported view 0 as unavailable in both games. Source and existing opcode evidence showed that tools/resource_reference_audit.py had treated set.loop operand 1 as a view number and had selected operand 2 instead of operand 0 from add.to.pic. Correcting those roles removed the false view-0 dependency. The tool now also reports referenced_unavailable, which compares all references with the readable set and therefore catches absent entries as well as malformed present ones. A synthetic test covers the corrected set.loop, add.to.pic, and absent-reference cases.

The replacement MH1 interpreter is 3.002.102, not the former copy’s 3.002.107. Its AGIDATA.OVL is byte-identical to KQ4D’s; the equal-length AGI executables differ at only two embedded signature bytes. Replacement MH2 retains the previously classified 3.002.149 relationship with Gold Rush: equal-length AGIDATA files are byte-identical and the AGI files differ at the same 29 classified bytes. No file under games/ was modified by the audit.

Replacement MH1/MH2 playthrough reconstruction

The former MH1 and MH2 playthrough chapters were deleted before analysis so the complete replacements would not be fitted into the earlier incomplete narratives. Fresh indexes were generated from the commands above, and terminal logic was identified by searching for continuation messages and outgoing room edges. Incoming transition predicates were then traced backward through the current scripts.

MH1 logic 162 has no outgoing transition. Its only normal incoming edge is logic 151 offset 0x0616, guarded by the late-story flag, flight stage 12, and four separate bomb-region flags. Logic 154 consumes Modules A, B, C, and D at independent cockpit positions before entering logic 151. The phase variable used by the assignment scripts progresses through Bellevue Hospital, Grand Central Terminal, Greenwood Cemetery, and Alliance-computer orders. The shared MAD parser recognizes six named records, but recognized-name flags and report phase are separate; exact successful answer order remains a replay question.

MH2 logic 187 has no outgoing transition. Its only incoming edge is logic 183 offset 0x047f, guarded by maze state 4, X greater than 129 and less than 144, and Y less than 24. Logic 183 wraps movement at all four edges and remaps its direction state, so reaching the same screen boundary in another maze state is not sufficient. Backward tracing found the complete late chain through the Rat Mask 1 and Full Flask consumption, hatchet and ring branches, Orb-on-a-Stick sequence, statue/Orb Card access, lava/slave controller, helicopter, logic 186 control sequence, and final maze.

The terminal scripts reference multiple pictures rather than one static end screen. Representative MH1 pictures 76, 79, 89, 90, 130, and 189 and MH2 pictures 19, 31, 35, 39, 205, 253, and 254 were rendered under build/playthrough/ to corroborate scene identity. These renders were not used to infer control flow. Neither game contains conventional score or maximum-score mutations, so terminal reachability is the applicable winning criterion.

SQ1.22 precise success-path reconstruction

The SQ1.22 work used only the immutable games/SQ1.22 resources and the project’s locally inferred decoders. The locator index was regenerated with AGI_GAME_DIR=games/SQ1.22 python3 -B tools/logic_playthrough_index.py, and all 101 readable logics were disassembled. Every positive and negative v3 mutation was traced through its enclosing branches and jumps; logic 104’s 202 assignment supplied the declared maximum.

WORDS.TOK decoding resolved accepted parser synonym groups, while OBJECT decoding supplied inventory numbers. Visual and priority/control channels were rendered for pictures 1 through 71 under build/playthrough/sq122/media/. Movement conclusions use the control channel plus room-logic rectangle, horizon, priority, and ignore-block operations. The control renders were qualitative movement evidence; exact score predicates came from logic code.

The late static pass resolved three important non-parser transitions. Logic 65 maps walking rectangles to keypad values, so 6858 is entered by crossing digit regions 6, 8, 5, and 8 and then the ENTER region. Logic 53 requires the player width in x 60..74 with baseline y 107..111, an open washer state v72=3, and no existing disguise before its five-point laundry branch. Slot logics 113-115 subtract the selected wager, choose result classes with random operations, apply wager-scaled payouts, and saturate a winning total at 250. These facts are represented by coordinate edges and retry loops in docs/src/games/sq1_22_success_path.json.

The selected route contains 45 score nodes totaling 202: 61 Arcada, 49 Kerona, 43 Ulence Flats, and 49 Deltaur. Alternative positive sites for the combined Orat/spider resolution, discounted/credit ship path, second vent branch, and roaming-guard shot are excluded. Dynamic confirmation remains outstanding; no QEMU observation was used to fill a static-evidence gap.

2026-07-12: SQ1.22 persistent interpreter controller

Goal: replace timing-sensitive manual QEMU play with a persistent semantic controller that pauses the original interpreter at cycle boundaries and exposes coherent state without modifying the private game input.

Only local evidence was used. tools/interpreter_controller.py validates the decoded SQ1.22 executable SHA-256 as 97dbc528ff4588b424d8c4e43035d619588c0c6b4376cc1ddea1fb83cea67656. Focused ndisasm reads of the local decoded image established cycle image 0x015b, shared string-editor loop/call sites 0x0df2/0x0df5 with return 0x0df8, and modal loop/call sites 0x1d1b/0x1d22 with return 0x1d25. The profile uses the separately mapped 2.917 data locations rather than assuming the 2.936 offsets.

The disposable session disk was produced with:

python3 -B tools/interpreter_controller.py prepare \
  --base-image build/freedos/freedos.img \
  --game-dir games/SQ1.22 --dos-game-dir SQ122 \
  --raw-output build/interpreter-controller/session/sq122.raw \
  --output build/interpreter-controller/session/sq122.qcow2

The controller launched QEMU with QMP and GDB Unix sockets, -S, the generated INT-43h-compatible VGA ROM, and -display cocoa,zoom-to-fit=on. Runtime signature scanning found physical image base 0x63a0; stopped registers gave DS 0x102f. A first state read decoded 18 object records. The visual and priority endpoints read the live combined logical-buffer segment and produced 160-by-168 PPM images.

Initial input tests found two harness problems. QMP rejects key transitions while the VM is stopped, so a key press must be sent after debugger resume. An atomic press/release pair was not observed by the title logic because both transitions arrived before cycle input sampling; a semantic tap must keep the key down through one stop and release it through the next. More importantly, QEMU’s real-mode GDB path honored the cycle breakpoint but not simultaneous UI breakpoints. Reinstalling the set after a debugger single-step did not change that result.

A targeted run with only the string hook active stopped at image 0x0df2, proving the static hook was correct. Interrupting a blocked all-hooks run gave image-relative IP 0x4523; the aligned stack included the call chain ending in return 0x0df8, again proving execution was inside the shared editor. This isolated the failure as an emulator debugger limitation rather than a wrong interpreter model.

The final controller keeps one breakpoint active. It normally selects cycle 0x015b. If a cycle does not return, it interrupts and reads 256 stack bytes; aligned return 0x0df8 selects the string hook, while 0x1d25 selects the modal hook. Accepting/dismissing switches back to the cycle hook. The hooks and all mutations are runtime debugger state against the disposable VM; no file in games/SQ1.22 changed.

Live confirmation followed this semantic chain:

  1. Stop at cycle 1 in room 67.
  2. Press Enter, interrupt the blocking cycle, and classify ordinary prompt_string_to_slot input in logic 69 from return 0x0df8.
  3. Submit roger one transition at a time and return to cycle 3 in room 2 with previous room 69.
  4. Submit a normal look command, classify modal return 0x1d25, and confirm one red-border/white-interior screenshot box plus the interpreter window-active word.
  5. Dismiss with Enter, return to cycle 13, and confirm both modal indicators clear.

Conclusion: persistent cycle stepping, shared string input, modal interaction, state reads, logical-buffer rendering, and independent dialog detection now work together against the real SQ1.22 interpreter. The one-breakpoint limit is documented as a QEMU harness constraint and is not AGI behavior.

SQ1.22 checkpoint hook-state correction (2026-07-13)

During the dynamic completion playthrough, checkpoint score8_keycard was created at the ordinary cycle hook. Roger later entered a fatal room-2 modal, which made the controller select the modal hook. Loading the earlier QEMU checkpoint restored the interpreter CPU/memory state but left the controller’s live GDB hook selection associated with the later modal interaction. Subsequent movement requests therefore failed to reach valid semantic stops and were interrupted at image offset 0xe256.

This was a harness-state defect, not observed game behavior. Controller checkpoints now record their active semantic breakpoint reasons together with held and pending keys. Restore reinstalls the saved reason before reading the restored state or accepting another mutation. A focused unit test confirms that a saved cycle reason is selected and the transaction cache is cleared on restore.

SQ1.22 deterministic controller RNG patch (2026-07-13)

The completion playthrough needs reproducible game-visible random outcomes. Static inspection of the locally decoded SQ1.22/2.917 image found the shared generator at image 0x70f9. Its state is word DS:0x1707. When that word is zero, image 0x7108..0x710f executes bytes b4 00 cd 1a 89 16 07 17, reading BIOS ticks through interrupt 1a and storing DX as the seed. The generator then advances the state with multiplier 0x7c4d and increment one and returns the XOR of the state bytes.

Direct calls to this generator occur at image 0x0c0b, 0x3f8e, 0x3fa8, and 0x4f71. The surrounding reductions identify approach-motion recovery, random-motion countdown, random direction, and the script action random_range_to_var. Thus the shared state covers the original engine’s four source-backed game-randomness paths. The image contains two other interrupt 1a calls at 0x4137 and 0x414c; source inspection shows that they bound a startup display-adapter test and never feed DS:0x1707 or a game random result.

tools/interpreter_controller.py prepare now validates the pristine decoded hash and exact seed bytes, then changes only the disposable generated AGI copy. It replaces the eight bytes above with ba ed 5e 90 89 16 07 17: load fixed seed 0x5eed, one padding nop, and store it to the same state word. The original zero-state guard, generator transition, and output mixing remain unchanged. The XOR transform was reapplied to create the DOS payload; no file under games/SQ1.22 was modified.

Runtime discovery reads the loaded patch bytes and rejects an older play disk instead of silently using BIOS time. GET /v1/profile reports fixed-seed mode and seed 24301. A focused unit test verifies that no byte outside the eight-byte seed sequence changes. The canonical disposable disk was rebuilt with:

python3 -B tools/interpreter_controller.py prepare \
  --base-image build/freedos/freedos.img \
  --game-dir games/SQ1.22 --dos-game-dir SQ122 \
  --raw-output build/interpreter-controller/session/sq122.raw \
  --output build/interpreter-controller/session/sq122.qcow2

Reading C:\\SQ122\\AGI back from the generated raw disk, applying the local loader transform, and checking image 0x7108 produced baed5e9089160717, exactly matching the expected patch. The focused controller suite passed 38 tests.

Cold-build reproducibility was checked independently of the existing build/ tree. A new temporary directory under /tmp was used for an empty download cache, a freshly downloaded and verified FreeDOS archive, a newly formatted 64 MiB base image, and new raw/qcow2 controller outputs. Running the ordinary controller prepare command against those fresh paths and reading the DOS AGI back again produced the same baed5e9089160717 bytes at image 0x7108. Thus deleting build/ removes only generated artifacts: running tools/setup_freedos_image.py --force followed by the documented controller prepare command recreates the deterministic interpreter patch from tracked tool code and immutable games/SQ1.22 input.

SQ1.22 live playthrough: room 1 corrections (2026-07-13)

A fresh fixed-seed controller run disproved two assumptions in the static success path. After the room-69 name editor returned to room 2, the automatic alarm dialog came from logic 94. Entering room 1 left f53 clear and the scientist countdown inactive. Logic 1 sets f53 only on a room-1 exit. A bounded room 1 -> 2 -> 1 loop therefore proved necessary: the exit set f53, and the return initialized v32=250 (observed as 247 at the first stopped state after room setup).

At v32=135, logic 1 opened the scientist-entry dialog, then the collapse dialog and reached v51=2, v34=6. Waiting alone could not initialize v33; its only relevant assignment is at logic-1 offset 0x04a8, after the LOOK/EXAMINE scientist branch and its wound-description modal. A failed TALK TO SCIENTIST experiment produced message 56 and left v33=0, proving that the talk branch is not the trigger. LOOK AT SCIENTIST produced message 10; dismissing it advanced to v51=4 and a decreasing v33. When v33 reached one, dismissing messages 11 and 12 changed score v3 from 0 to 2.

At console position (82,108), LOOK AT SCREEN entered the shared string editor. Submitting ASTRAL BODY set v50=1, v35=4; a bounded 205-cycle wait reached v50=2 and f35=true. GET CARTRIDGE changed score from 2 to 7 and inventory object 1 from room marker 0 to carried marker 0xff. The coherent post-dialog state at controller cycle 943 was saved as checkpoint score7_cartridge and milestone capture sequence 3.

SQ1.22 live playthrough: Arcada routing and persistence (2026-07-13)

All local walking in this run used the controller’s breadth-first planner over the live priority channel. Room transitions, elevators, and airlock doors were separate guarded stages because the priority image alone does not describe those dynamic transitions.

The route from room 2’s upper doorway to the room-3 keycard is not a direct crossing of room 3. The dynamically confirmed path is room 3 upper-left, room-3 elevator down, room 3 lower-right, room 4 lower-left, room-4 elevator up, then left into room 3 upper-right. GET KEYCARD at (152,67) changed the score from 7 to 8 and carried inventory object 5. Room 3’s elevator stops near baseline 67 and 151; its transfer requires a separate direction selection through the open doorway rather than treating the whole shaft as one priority path.

The common room-encounter logic also invalidated an unguarded route through room 7. On entry it can set v67=1 and a random countdown in v138; the safe hiding strip is only x 98..149, y 121..131. The fixed-seed run observed three armed entries (v64 81, 57, and 97) before a fourth entry produced v64=34 and left v67=0. Leaving immediately for room 6 and re-entering on an armed roll avoided the fatal projectile sequence without guessing at movement.

In room 6, PRESS OPEN BAY DOOR set v52=1 and raised the score to 10. Room 7 required the dynamic elevator sequence after INSERT KEYCARD: approach the reader, wait for its door state to reach v30=1, then cross its right/up threshold into room 9. The insertion raised the score to 12.

Live room-9 behavior reversed the closet labels in the static guide. PRESS LEFT BUTTON opened the gadget closet (v70=3), while PRESS RIGHT BUTTON opened the suit closet (v69=3). The GET GADGET predicate uses full ego width in x 40..71, so left coordinate 71 is too far right for the seven-pixel ego; x 61 succeeded and raised the score to 14. The suit pickup at x 79 raised the score to 16 and set variable v81=1. This variable is distinct from flag f81, which later records AutoNav.

The room-9 airlock required PRESS AIRLOCK BUTTON, a priority-planned walk to the open doorway, and a separate leftward crossing into room 8. In room 8, PRESS PLATFORM BUTTON exactly once set f54, left v52=1, and raised the score to 17. After the platform animation and its modal, a newly planned path to (53,101) reached the pod; ENTER SHIP switched to room 10.

Built-in F5/F7 persistence was tested before the long route. Slot 1 was first saved as Score 7 cartridge. F7 restored room 3, score 7, position (73,151) and the expected inventory after the controller handled the directory editor, slot selector, and confirmation as distinct semantic stops. F5 later overwrote slot 1 as Score 19 prelaunch. The save/restore selector and final confirmation are host-time input loops not covered by the normal cycle hook; the run temporarily selected the cycle breakpoint again after the confirming Enter and then reconciled held/released keys.

SQ1.22 live playthrough: launch and first Kerona state (2026-07-13)

Room-10 command effects were verified independently. CLOSE DOOR set f155, FASTEN BELT set f44, PRESS POWER set f188, and PRESS AUTONAV set f81 and raised the score from 17 to 19. Input must be reconciled after a modal or animation before submitting the next command: the first throttle attempt began while input was still settling and only the suffix hrottle reached the prompt. Submitting that partial text produced the expected parser error and no state change. After dismissal and key reconciliation, PULL THROTTLE set f80 and began the launch.

The observed automatic sequence is more detailed than the static route: room 10 -> room 8 (pod exits the bay) -> room 12 -> room 10 (in-flight pod) -> room 13 -> room 30 (landing animation) -> room 14. Logic 12 raised the score from 19 to 34 after its animation, then displayed the narrow-escape message. The in-flight room-10 logic waited 225 countdown cycles before its monitor message, then used a 27-cycle transition and 150-cycle exterior interval before room 13. These waits were bounded from the local disassembly rather than polling one cycle at a time.

The first room-13 visit did not award 25 points. Logic 13’s 25-point action is guarded by previous room 37, whereas the first approach arrived from room 10. The live score therefore remained 34 through rooms 13 and 30. Room 14’s GET SURVIVAL KIT raised it to 36 and carried object 11. UNBUCKLE BELT set f31, and EXIT returned to room 30.

The global OPEN SURVIVAL KIT branch was then verified outside the pod. It cleared object 11, carried object 12 (Dehydrated Water), object 19 (Xenon Army Knife), and the opened-kit marker object 22. A live-priority plan moved the ego from (40,106) to (40,111) so its full baseline footprint satisfied room 30’s glass rectangle. GET GLASS carried object 6 and raised the score from 36 to 39. Thus the old guide’s claimed pre-Kerona score 61 is 25 points too high; that conditional award belongs to the later room-37 return path.

2026-07-15: XMAS.230 / AGI 2.230 profile

Goal: identify the previously unobserved interpreter in games/XMAS.230, compare it with the neighboring local 2.089 and 2.272 builds without external AGI material, and promote every source-backed valid full-EGA difference as a new behavioral profile.

Read-only input inventory

The investigation began with git status --short, focused file listings, MZ inspection, hashes, and local string extraction. No file under games/ was modified. AGIDATA.OVL contains Version 2.230; AGI.EXE is an MZ executable whose loaded image is 33,792 bytes. Selected hashes are:

  • AGI.EXE: 875547deb3d83b7ed2dd7578d357f43b6a5843a9ac8de1332245df9f60ff4570;
  • AGIDATA.OVL: 33a327736deb611df48d0d3232c1958b00af1b1cae5fba45ed3aadf88871af8a;
  • EGA_GRAF.OVL: cc0402c7dcea0c9b9e51262a8d455abbe0b3e86c0a39dd8f7ecb3e57ffe8ec2c;
  • OBJECT: 10701142dc86bb5b19fadf812e4096ed5206ea51b10f826786872a9d07ee2477; and
  • WORDS.TOK: 4f816ed3917dc129e84a7620afd69448893d56ab8a47f99ebf10874da8294710.

OBJECT and WORDS.TOK are byte-identical to the local XMAS 2.272 copies. The split directories and VOL.0 are not byte-identical. The selected 2.230 copy contains only VOL.0, although present directory entries also name volumes 1 and 2. The read-only census therefore reports 15 readable logics, 9 pictures, 100 views, and 10 sounds, all direct v2 records, plus missing-file errors for resources on the absent volumes. Those missing components are a selected-package limitation, not a new resource transform.

Commands and generated reports used for this pass included:

python3 -B tools/game_census.py --game-dir games/XMAS.230 \
  --game-dir games/SQ1 --game-dir games/XMAS \
  --output build/cross-version/xmas_2230_selected_census.json
python3 -B tools/compare_interpreter_tables.py \
  --left-label XMAS-2.230 --left-game-dir games/XMAS.230 \
  --left-exe games/XMAS.230/AGI.EXE \
  --right-label SQ1-2.089 --right-game-dir games/SQ1 \
  --right-exe games/SQ1/SQ.EXE \
  --output build/cross-version/xmas_2230_sq1_2089_tables.md
python3 -B tools/compare_interpreter_tables.py \
  --left-label XMAS-2.230 --left-game-dir games/XMAS.230 \
  --left-exe games/XMAS.230/AGI.EXE \
  --right-label XMAS-2.272 --right-game-dir games/XMAS \
  --right-exe games/XMAS/AGI.EXE \
  --output build/cross-version/xmas_2230_xmas_2272_tables.md
python3 -B tools/match_interpreter_roles.py \
  --reference-label XMAS-2.272 --reference-game-dir games/XMAS \
  --reference-exe games/XMAS/AGI.EXE \
  --target-label XMAS-2.230 --target-game-dir games/XMAS.230 \
  --target-exe games/XMAS.230/AGI.EXE --sq2-subsystems \
  --output build/cross-version/xmas_2230_early_role_matches.md

Loaded-image slices were written only under build/cross-version/ and disassembled with ndisasm -b 16 -o 0. Resource payloads were read through the local agi_resources decoder. Focused Python comparisons enumerated view loop and cel offsets, compared corresponding 2.230/2.272 payload bytes, and normalized all seven EGA overlay entry routines.

Dispatch-table boundary and hybrid profile

The 2.230 action table is at data offset 0x03e7 and contains 155 entries 0x00..0x9a. Its condition table is at 0x0673 and contains the common 19 entries 0x00..0x12. The 0x20 bytes between those table geometries match the later early-v2 trailer, whereas SQ1 2.089 uses a 0x26-byte trailer.

Against SQ1 2.089, all 155 action parser contracts match; normalized action entries differ only at 0x25, 0x26, 0x31, 0x3c, 0x80, and 0x81. Against XMAS 2.272, the 155 shared entries have one parser-contract difference, the operand on 0x86, and seven normalized entry differences: 0x31, 0x4d, 0x4e, 0x77, 0x81, 0x86, and 0x8b. All 19 2.230/2.272 condition handlers match after relocation. The six unshared 2.272 actions are 0x9b..0xa0.

Manual source inspection classifies the portable choices:

  • 0x86 is the zero-operand unconditional exit used by 2.089. Actions 0x9b and later do not exist.
  • Position actions 0x25/0x26 at 0x70fa/0x7137 match 2.272: current and previous coordinates are written together without first erasing drawn state.
  • Action 0x3c and list roles 0x6081, 0x60bb, and 0x60d8 restore, build/sort, and refresh both drawing-key partitions as in 2.272.
  • Motion-clear actions 0x4d/0x4e at 0x64e7/0x6520 are exact relocated SQ1 matches. They retain autonomous mode; 0x4d clears direction and 0x4e changes only object-0 coupling/navigation state.
  • The 2.230 word-sequence predicate 0x0851 matches 2.272 and implements the 0x270f tail terminator. String equality is also a relocated match.
  • Action 0x77 omits a conditional alternate-display helper added in 2.272; its ordinary full-EGA input-line clear is unchanged. The differing restart, successful view-preview, and joystick entries reduce to cleanup, temporary allocation/diagnostic, and hardware paths without another valid full-EGA output rule.

Packed view-loop orientation

The unique valid-data difference is the 2.230 view-loop header. Loop selector 0x350d reads the first loop byte, masks it with 0x0f, and stores that low nibble as the object’s cel count. If header bit 0x80 is set, it compares bits 0x30 with the selected loop. On a change, bit 0x40 requests helper 0x50af, which iterates header & 0x0f cels and mirrors every encoded row in place. The selector then replaces header bits 0x30 with the new orientation. The header and shared row streams are therefore mutable loaded-resource state.

Action 0x31 at image 0x36e2 independently reads the selected loop header, executes and ax,0x000f, subtracts one, and stores the highest valid cel index. The neighboring 2.089 and 2.272 handlers subtract one from the unmasked byte.

Direct selected-resource comparison confirms the intended format migration:

  • views 10, 11, and 14 have two directory loops sharing one loop structure whose 2.230 header is 0xc4; and
  • views 16, 17, and 18 similarly use header 0xc6.

The corresponding 2.272 payloads have ordinary headers 0x04/0x06 and set bit 0x80 in each cel’s control byte instead. The 2.230 cels leave that bit clear. Resource lengths, loop/cel offsets, dimensions, and row streams remain aligned, so the orientation marker moved from the loop header to the cel control byte between these two builds.

Other subsystem classification

Exact normalized role matches relocate the 2.272 frame timer (0x049a), movement core (0x1317), parser (0x1759), save/restore (0x2447/0x2279), acknowledgement-only inventory (0x2dcf), collision (0x3f0f), wrapped distance (0x3fe5), retained-state show-picture (0x436e), picture decode/scanner/line/fill roles, control acceptance, placement, dirty rectangles, deferred target motion, and direct position actions. Manual call-site inspection resolves the short list wrappers as 0x6081 restore, 0x60bb rebuild/sort, and 0x60d8 refresh.

Sound start 0x7478 and tick 0x74da share the early one-versus-four-channel scheduler. Output 0x7598 is an exact relocated SQ1 match: it emits both tone bytes and applies only the device-2 low-nibble-plus-three rule, without 2.272’s global whole-byte addition and signed clamp.

The picture scanner 0x5baa accepts only 0xf0..0xf8. All seven EGA overlay entry routines normalize exactly against both 2.089 and 2.272; their byte differences relocate interpreter globals and calls rather than changing the full-EGA output contract.

The five-block save writer matches 2.272. Startup role 0x0e50 reads the third plain OBJECT header byte at 0x0e82, increments the local record count at 0x0e9d, and multiplies it by the 0x2b record size at 0x0eaa. The normalized 2.272 role at 0x0e7c has the same sequence. Thus header byte 0x11 defines 18 runtime object records rather than merely suggesting that interpretation from the save-block constant. The metadata block is 0x000f. Readable logic 0 calls action 0x8e(200), so selected block 4 contains 200 replay pairs and is 0x0190 bytes. The resulting fixed lengths are 0x03db, 0x0306, 0x000f, and 0x0190, followed by the variable logic-resume block. Canonical pristine values for reserved early block-1 bytes remain unknown, as for the neighboring early profiles.

Conclusion: AGI 2.230 is neither an alias of 2.089 nor 2.272. It is a source-backed hybrid with a distinct packed view-loop format, so it is promoted as a separate valid full-EGA behavioral profile.

SIERRA.COM loader decompilation notes

This note summarizes the current clean-room understanding of SQ2/SIERRA.COM. Current tooling derives this path from an explicitly selected game directory, not from a repository-default SQ2 copy. It is derived from local bytes, ndisasm, Rizin output, and the reproducible transform in tools/decrypt_agi.py.

Address model

SIERRA.COM is a DOS COM file. File offset 0x0000 is loaded at memory offset 0x0100, so memory offset = file offset + 0x0100.

The first instruction is e9 9d 02, a near jump from memory 0x0100 to memory 0x03a0. The loader body therefore begins at file offset 0x02a0.

Key internal variables

These names are provisional and based on observed use:

Memory offsetProvisional nameEvidence
0x036dfile_handleWritten after DOS open and later used for read/close.
0x0371agi_load_segmentWritten after DOS memory allocation; used as the segment into which AGI is read.
0x0373agi_loaded_paragraphsCleared before loading AGI; incremented by rounded-up read byte counts.
0x0375next_read_segmentStarts as agi_load_segment and advances during file reads.
0x037acommand_tail_copyReceives PSP bytes from 0x80 before the MZ jump.
0x0141decode_key_tablePassed as CS:0x0141 to the transform routine.

High-level loader flow

Pseudocode:

entry:
    disable_interrupts()
    SS = CS
    SP = 0x02c1
    enable_interrupts()

    install_critical_error_handler()
    copy PSP command tail from 0x80 to command_tail_copy

    perform_machine_or_memory_setup()
    save_video_state()

    agi_load_segment = allocate_largest_available_dos_block()

retry_files:
    if not load_file_to_segments("agi", agi_load_segment):
        display("Can't find the file 'agi'...")
        key = wait_for_enter_or_escape()
        if key == Enter:
            goto retry_files
        else:
            goto exit

    if not can_open("agidata.ovl"):
        display("Can't find the file 'agidata.ovl'...")
        key = wait_for_enter_or_escape()
        if key == Enter:
            goto retry_files
        else:
            goto exit

    decode_loaded_agi()
    mark_graphics_check_flag()
    load_and_jump_to_mz_image()

exit:
    restore_critical_error_handler()
    set_cursor_position(row=0x18, col=0)
    dos_exit()

The loader only checks that agidata.ovl exists. It does not load that file before transferring control to the transformed MZ executable.

File loading routine

The routine at memory 0x0415:

load_file_to_segments(path_dx, agi_load_segment):
    handle = dos_open_readonly(path_dx)
    if open_failed:
        return carry_set

    agi_loaded_paragraphs = 0
    next_read_segment = agi_load_segment

    loop:
        bytes_read = dos_read(handle, segment=next_read_segment, offset=0,
                              count=0xfe00)
        if read_failed:
            return carry_set
        if bytes_read == 0:
            break

        paragraphs = (bytes_read + 0x0f) >> 4
        agi_loaded_paragraphs += paragraphs
        next_read_segment += paragraphs
        if next_read_segment != 0:
            continue loop

    dos_close(handle)
    return carry_clear

The separate routine at memory 0x045f only opens and closes a file to test existence.

AGI decode routine

The call at memory 0x08fc prepares:

source_start_segment = agi_load_segment
source_end_segment = agi_load_segment + agi_loaded_paragraphs
key_segment = CS
key_offset = 0x0141
call transform(source_start_segment, source_end_segment, key_segment, key_offset)

The transform routine at memory 0x09f4:

carry = 0
source_segment = source_start_segment

while source_segment < source_end_segment:
    DS = source_segment
    ES = key_segment
    SI = 0
    DI = key_offset

    repeat 128 times:
        key_byte = ES[DI]
        DS[SI] = DS[SI] XOR key_byte
        SI += 1

        rotated_key_byte = rotate_right_through_carry(key_byte, carry)
        carry = low_bit(key_byte)
        ES[DI] = rotated_key_byte
        DI += 1

    DI = key_offset
    if carry:
        ES[DI] = ES[DI] OR 0x80

    source_segment += 8

Applying this transform to the selected SQ2 AGI with the key table from SIERRA.COM file offset 0x0041 produces build/cleanroom/AGI.decrypted.exe, a valid DOS MZ executable.

MZ transfer routine

The routine at memory 0x0c46 treats the decoded AGI image as an MZ executable:

image = agi_load_segment:0000
if image[0:2] != "MZ":
    display("Bad program image.")
    exit

psp_segment = agi_load_segment
load_base = psp_segment + 0x10
source_image_segment = psp_segment + image.header_paragraphs

entry_cs = load_base + image.initial_cs
entry_ip = image.initial_ip
entry_ss = load_base + image.initial_ss
entry_sp = image.initial_sp

for each relocation entry:
    relocation_segment = source_image_segment + relocation.segment
    relocation_offset = relocation.offset
    word_at_relocation += load_base

copy executable image from source_image_segment to load_base
dos_set_psp(psp_segment)
SS:SP = entry_ss:entry_sp
copy command_tail_copy back to PSP:0x80
DS = psp_segment
far_jump(entry_cs:entry_ip)

Header values recovered from the decoded image:

FieldValue
SignatureMZ
Blocks/pages0x004d
Relocations0x0021
Header paragraphs0x0020
Minimum extra paragraphs0x0271
Maximum extra paragraphs0xffff
Initial SS:SP0x0be9:0x0080
Initial CS:IP0x0000:0x6777
Relocation table offset0x001c

Decrypted executable observations

The decoded image begins with visible interpreter identity text at file offset 0x0200:

Adventure Game Interpreter
Copyright (C) 1984, 1985, 1986 Sierra On-Line, Inc.
Authors: Jeff Stephenson & Chris Iden

The decoded image also contains local overlay/file names near file offsets 0x9986 through 0x99eb:

CGA_GRAF.OVL
JR_GRAF.OVL
EGA_GRAF.OVL
HGC_GRAF.OVL
VG_GRAF.OVL
IBM_OBJS.OVL
HGC_OBJS.OVL
AGIDATA.OVL
AGI.EXE

Decrypted AGI executable notes

This page records observations from build/cleanroom/AGI.decrypted.exe, the MZ image reproduced from local SQ2 AGI bytes by tools/decrypt_agi.py with an explicit --game-dir/AGI_GAME_DIR.

Address model

The decrypted file is a DOS MZ executable with header_paragraphs = 0x20. Therefore file offset 0x0200 maps to runtime image offset 0x0000.

In this note:

  • file offset means an offset in build/cleanroom/AGI.decrypted.exe.
  • image offset means file offset - 0x0200.
  • Segment constants in the executable are MZ-relocated at load time. The values shown here are the pre-relocation values as stored in the decrypted image.

Startup overlay load

The MZ entry point is CS:IP = 0000:6777.

The entry routine at image offset 0x6777 performs a two-stage startup:

temporary_ss = 0x0be9 + (0x0080 >> 4)
temporary_sp = ((0x0be9 - 0x0a01) << 4) + 0x1000
SS:SP = temporary_ss:temporary_sp

save caller ES
DS = temporary_ss
ES = temporary_ss
load_overlay(8)
restore caller ES

SS:SP = 0x0be9:0x1000
DS = 0x0a01
[0x16af] = DS
[0x16b1] = caller ES
ES = DS
[0x16ab] = 0x0be9
jump 0x0078

The important point is that overlay number 8 is loaded before the program switches DS to 0x0a01. Overlay 8 is AGIDATA.OVL, so the interpreter’s main data segment is populated from that file before the main startup body at image offset 0x0078 runs.

DOS file wrappers

Several small routines wrap DOS interrupt 21h file services:

Image offsetDOS functionObserved behavior
0x5cadAH=3cCreate/truncate file. Takes path pointer in DX and attributes in CX; returns 0xffff on carry/error. The byte at 0x5cac is padding before the prologue.
0x5cceAH=3dOpen file. Takes path pointer in DX and mode in AL; returns 0xffff on carry/error.
0x5cefAH=3fRead file. Takes handle in BX, buffer in DS:DX, byte count in CX; returns zero on carry/error.
0x5d12AH=40Write file. Takes handle in BX, buffer in DS:DX, byte count in CX; returns zero on carry/error.
0x5d35AH=41Delete file. Takes path pointer in DX; returns zero on carry/error.
0x5d52AH=3eClose file handle in BX.
0x5d6bAH=42Seek. Takes handle in BX, offset in CX:DX, origin in AL; returns 0xffff:0xffff on carry/error.
0x5d94AH=45Duplicate file handle in BX; returns 0xffff on carry/error.
0x5db2AH=47Get current directory for the default drive into caller buffer after first writing a leading backslash. If the returned path contains /, the helper writes / over the first byte, so slash normalization remains a little odd and should be rechecked dynamically.
0x5deaAH=19Return current drive as a lowercase letter by adding 0x61 to DOS’s zero-based drive number.
0x5e01AH=1a, then AH=4eSet the DTA pointer from the third argument, then perform find-first with pattern pointer in DX and attributes in CX; returns 0xffff on carry/error.
0x5e26AH=4fFind-next using the current DTA; returns 0xffff on carry/error.
0x5e3eAH=19, AH=0e, AH=19, AH=0eProbe whether a lowercase drive letter can be selected. It saves the current drive, tries to set the requested drive, checks whether DOS reports that drive as current, restores the original drive, and returns 1 on success or 0 on failure.
0x5e73AH=57, AL=0Get file date/time for handle BX; returns the time word in CX.

Before most file-service calls, helper 0x5e8d temporarily switches DS to segment 0x0a01 and clears word [0x184d]. The exact meaning of that global is still open.

The helper at image offset 0x5b49 compares absolute buffer 0x0002 against the embedded string SQ2\0 at image offset 0x5b6c, calling helper 0x02ae on mismatch. It is reached by logic action handler 0x0e7e, which copies a resolved logic message into 0x0002 before the check. In SQ2 this buffer is both a runtime game signature and the save-file stem prefix used by the filename formatter below.

The helper at image offset 0x5b73 formats savegame-like names with the local format string at data offset 0x132c, %s%s%ssg.%d. Its arguments are the path buffer at 0x1962, a separator string chosen from the current path contents, the signature/prefix string at 0x0002, and the numbered slot. Nearby local strings include slash/backslash separators at 0x1327, 0x1328, and 0x135f, plus the user-facing example (For example, "B:" or "C:\savegame") at 0x1339. If 0x0002 contains SQ2, slot 1 formats as SQ2SG.1; if that buffer is blank, the same helper formats SG.1.

The helper at image offset 0x5bdd validates or probes a user-supplied path: it trims leading spaces, fills an empty path with the current directory helper 0x5db2, strips a trailing slash/backslash except for one-character paths, records a drive-like letter in byte [0x1363], tests bare separators and drive-only strings specially, and otherwise calls the find-first wrapper 0x5e01 with attribute mask 0x10. It returns 1 for accepted/probed paths and 0 for failure.

Save/restore state file

Logic actions 0x7d (save_game_state) and 0x7e (restore_game_state) are the observed save and restore paths. Both use helper 0x85e5 to select or prepare a target slot/path and use the savegame filename buffer at 0x1c8c.

Save action 0x7d (save_game_state) at image offset 0x2753:

  1. Sets word [0x0615] = 1 and temporarily changes byte [0x0d15] to 0x40.
  2. Calls 0x85e5(0x73).
  3. If byte [0x0e72] is zero, formats and displays confirmation text rooted at 0x0db6, then waits through helper 0x4618.
  4. Creates file 0x1c8c through DOS wrapper 0x5cad.
  5. Writes 31 raw bytes from 0x1c6c.
  6. Writes length-prefixed blocks through helper 0x28c6.

Helper 0x28c6(handle, pointer, length) writes:

u8 length_low
u8 length_high
length bytes from pointer

It returns 1 only when both one-byte length writes and the payload write return the requested byte counts. The save action treats a short write of either the 31-byte header or any length-prefixed block as a recoverable save failure: close the handle, delete the partial file named at 0x1c8c, display message 0x0e46, restore modal/text state, and continue after the opcode.

If file creation itself returns 0xffff, the save action displays formatted message 0x0df0 using the path buffer at 0x1962, restores modal/text state, and continues without attempting a delete.

The observed save blocks are:

Source pointerLength sourceLocal SQ2 lengthObserved role
0x00020x05e11505Main scalar/string/config/runtime block.
[0x096b][0x096f]903Object table bytes.
[0x0971][0x0975]328Three-byte entry table bytes.
[0x1707][0x0141] * 2200Resource-event pair buffer.
0x0985return value from 0x136416..28 observedFour-byte logic-number/resume-offset records, including a cache-head record and 0xffff terminator.

Restore action 0x7e (restore_game_state) at image offset 0x2512:

  1. Sets word [0x0615] = 1 and temporarily changes byte [0x0d15] to 0x40.
  2. Calls 0x85e5(0x72).
  3. If byte [0x0e72] is zero, formats and displays confirmation text rooted at 0x0d34, then waits through helper 0x4618.
  4. Opens file 0x1c8c through DOS wrapper 0x5cce.
  5. Seeks to offset 0x1f from the start of the file, skipping the raw description/header.
  6. Reads length-prefixed blocks through helper 0x26b0.

Helper 0x26b0(handle, destination) reads a little-endian 16-bit length from two one-byte reads, then reads that many bytes into destination, returning 1 only when all reads return the requested byte count.

If code.dos.open_file returns 0xffff, the restore action displays formatted message 0x0d73, restores modal/text state, and continues after the opcode. If opening succeeds but any length-prefixed block read fails, restore closes the handle, displays message 0x0d87, and enters code.system.exit_with_cleanup; this path terminates the DOS process rather than returning to the current logic stream.

On successful restore, the handler refreshes display/resource state and writes zero into its saved return pointer before leaving. The action therefore ends the current logic stream instead of continuing after 0x7e; compatibility probes must distinguish this from ordinary cancel/open-failure continuation.

The observed restore destinations mirror the saved blocks:

0x0002
[0x096b]
[0x0971]
[0x1707]
0x0985

The local SQ2 saves SQ2SG.1 through SQ2SG.11 all match this envelope: a 31-byte description/header, four fixed-size blocks with lengths 1505, 903, 328, and 200, and a fifth variable-size block. The parser and serializer in tools/agi_save.py round-trip those saves byte-for-byte and reject truncated blocks, trailing bytes, or inconsistent block metadata.

Local strings in AGIDATA.OVL confirm the path roles: 0x0d34 starts About to restore the game, 0x0d73 starts Can't open file, 0x0d87 starts Error in restoring game, 0x0db6 starts About to save the game, 0x0df0 starts the directory-full/write-protected message, and 0x0e46 starts the disk-full save-error message.

The shared selector helper 0x85e5 is the user-facing slot/path state machine. It remembers whether the prompt marker was visible, erases that marker, saves text attribute state, stops active sound, sets the text attribute pair, then calls path prompt helper 0x8705. If no runtime path/description is already in [0x0e72], 0x8705 displays the save or restore path question and edits the path buffer at 0x1962 through modal edit helper 0x8794; path validator 0x5bdd accepts or rejects that path. Helper 0x86a3 displays the insert-disk style message when the selected drive/path is unavailable.

Slot helper 0x8814 scans up to 12 numbered save files through summary reader 0x8b9f. Reader 0x8b9f formats a slot filename, opens it, reads the 31-byte description into the slot summary record, seeks two bytes forward from the current position to skip the first length prefix, reads seven bytes from the first saved state block, and compares those bytes with the string at 0x0002. This filters candidate saves to the current game signature/prefix. It also records the DOS timestamp returned by 0x5e73. The selector displays the available descriptions, marks the current row with a right-arrow glyph (0x1a), clears the old row with a space, and loops on normalized events: Enter accepts, Escape cancels, and movement events 1 and 5 move the current row up and down with wrap. In save mode, accepting an empty-description slot opens a second modal edit prompt rooted at 0x1baa and fills the 31-byte header buffer at 0x1c6c before the handler creates the file. On any exit, 0x85e5 restores text state and redraws the prompt marker if it was visible before entry.

Overlay loader

The routine at image offset 0x67c3 loads one numbered overlay.

Pseudocode:

load_overlay(number):
    saved_ds = DS
    DS = relocated_segment(0x0970)

    descriptor = 0x0004 + (number - 1) * 0x10

    handle = dos_open(path = DS:[descriptor + 0x08], mode = 0)
    saved_handle = handle

    byte_count = DS:[descriptor + 0x0e] << 4
    destination_segment = DS:[descriptor + 0x04]

    BX = handle
    CX = byte_count
    DX = 0
    DS = destination_segment
    dos_read(BX, DS:DX, CX)

    dos_close(saved_handle)
    DS = saved_ds

There is no observed check of the read byte count in this routine. It asks DOS to read the descriptor-provided maximum byte count and then closes the file.

Overlay descriptor table

The overlay table is in the decrypted executable at file offset 0x9900, image offset 0x9700. The first descriptor starts at image offset 0x9704. Each descriptor is 16 bytes.

Observed fields used by load_overlay:

Descriptor offsetMeaning
+0x04Destination segment.
+0x08Filename offset inside the overlay-table segment.
+0x0eMaximum read size in paragraphs; byte count is this value shifted left four bits.

Other descriptor words are not yet fully explained. The word at +0x06 matches the destination segment plus the paragraph count for overlays 1-7. For overlay 8 it is 0x0bf1, while destination plus paragraph count is 0x0be9; this may be related to the temporary stack used during startup, but that remains only a hypothesis.

Parsed descriptors:

NumberFileDestination segmentWord +0x06Name offsetParagraph countMax bytes
1CGA_GRAF.OVL0x09800x09ab0x00860x002b0x02b0
2JR_GRAF.OVL0x09800x099e0x00930x001e0x01e0
3EGA_GRAF.OVL0x09800x09a50x009f0x00250x0250
4HGC_GRAF.OVL0x09800x09db0x00ac0x005b0x05b0
5VG_GRAF.OVL0x09800x09970x00b90x00170x0170
6IBM_OBJS.OVL0x09db0x09f10x00c50x00160x0160
7HGC_OBJS.OVL0x09db0x0a010x00d20x00260x0260
8AGIDATA.OVL0x0a010x0bf10x00df0x01e80x1e80

The filename offsets are relative to the table segment. For example, descriptor 8 has name offset 0x00df, and the table segment begins at image offset 0x9700, so the string AGIDATA.OVL is at image offset 0x97df.

Command-line flags observed so far

The routine at image offset 0x00c4 reads the PSP command tail through the segment saved at [0x16b1]. It scans for - followed by a single lower-case letter and stores mode values in the interpreter data segment through ES.

Observed flags:

FlagStore
-c[0x1130] = 0
-r[0x1130] = 1
-e[0x1130] = 3
-h[0x1130] = 2
-v[0x1130] = 4
-p[0x112e] = 0
-t[0x112e] = 2
-s[0x112e] = 8

The meanings of these variables are not fully proven yet. However, the overlay selection routine below uses these locations to choose graphics and object overlays.

Graphics and object overlay selection

The routine at image offset 0x821c chooses and loads two overlays using load_overlay.

Pseudocode from observed control flow:

saved_value = call 0x5a74()
[0x1807] = saved_value

if [0x1130] == 2:
    object_overlay = 7        # HGC_OBJS.OVL
    graphics_overlay = 4      # HGC_GRAF.OVL
else:
    object_overlay = 6        # IBM_OBJS.OVL

    if [0x1130] == 3:
        graphics_overlay = 3  # EGA_GRAF.OVL
    else if [0x1130] == 4:
        graphics_overlay = 5  # VG_GRAF.OVL
    else if [0x112e] == 0:
        graphics_overlay = 1  # CGA_GRAF.OVL
    else:
        graphics_overlay = 2  # JR_GRAF.OVL

load_overlay(graphics_overlay)
load_overlay(object_overlay)
call 0x5528()

This proves that AGIDATA.OVL is loaded during executable startup, while the selected graphics and object overlays are loaded later by normal interpreter initialization code.

Symbolic Labels

This file records project-local symbolic names for interpreter routines, tables, and globals. The names are intended to survive comparison with later interpreter versions whose code may move to different offsets.

Addresses in this chapter are observations from the current Space Quest 2 build. They are not part of the portable specification. When a later executable has a matching routine at a different address, map that address to the same symbolic label and note any behavioral differences in the evidence trail.

Naming Rules

  • code.* labels name executable routines or overlay entry points.
  • data.* labels name runtime globals, data tables, or pointers.
  • table.* labels name dispatch or lookup tables.
  • opcode.action.* and opcode.cond.* labels name bytecode operations. The complete opcode catalog lives in the logic bytecode chapter and in tools/disassemble_logic.py.
  • Prefer symbolic labels in prose once a label exists, followed by the SQ2 address when useful for verification.
  • Mark uncertain labels as provisional in the notes/evidence column rather than encoding uncertainty in the label itself.

Address columns use these meanings:

  • SQ2 image offset: offset in build/cleanroom/AGI.decrypted.exe.
  • SQ2 data offset: runtime DS offset in SQ2/AGIDATA.OVL unless otherwise noted.
  • SQ2 overlay offset: near offset in the named overlay file.

Core Logic Interpreter

LabelSQ2 addressNotes/evidence
code.engine.main_cycleimage 0x0150Top-level interpreter cycle. Calls input/system helpers, mirrors object-0 direction/global direction state ([0x0139] == 0 copies object0 +0x21 to [0x000f], nonzero copies [0x000f] to object0), runs pre-motion mode updates, invokes logic 0 through code.logic.call_logic, restores object0 +0x21 from [0x000f], then runs code.object.frame_timer_update unless text-attribute mode byte [0x1757] is nonzero.
code.engine.wait_for_cycle_counterimage 0x7f78Top-level cycle throttle called near the start of code.engine.main_cycle; reads byte DS:0x0013 (v10), spins until word [0x1784] is at least that value, then clears [0x1784].
code.input.clear_status_bytesimage 0x4c23Clears 50 bytes at data.input.mapped_status_bytes before each synchronous input phase.
code.input.update_timed_eventsimage 0x61f2Updates enabled timed input sources against the tick counter and enqueues changed movement/status events before ordinary cycle input dispatch.
code.input.process_cycle_eventsimage 0x357cClears v19 and v9, services requested modal menu interaction, drains pending events, and updates raw-key, direction, and mapped-status state for the cycle.
code.control.save_abort_contextimage 0x7ee0Saves the current control context used by room/restart-style nonlocal returns so logic 0 can be re-entered without repeating pre-logic cycle phases.
code.logic.interpret_mainimage 0x293cMain logic bytecode loop. Reads opcodes from current logic bytecode and dispatches actions/conditions.
code.logic.action_dispatchimage 0x02c4Action dispatcher. Uses table.logic.action_dispatch.
code.logic.condition_dispatchimage 0x07e3Condition dispatcher. Uses table.logic.condition_dispatch.
code.logic.condition_input_word_sequenceimage 0x095cCondition handler for 0x0e; matches the parsed word ID table against variable-length word ID operands, with 0x0001 as a wildcard and 0x270f as an immediate successful terminator.
table.logic.action_dispatchdata 0x061dFour-byte action table entries for opcodes 0x00..0xaf.
table.logic.condition_dispatchdata 0x08fdFour-byte condition table entries for opcodes 0x00..0x12 in this build.
code.logic.message_xor_rangeimage 0x07abXORs logic message text in place.
data.logic.message_xor_keydata 0x08f1Zero-terminated XOR key bytes, observed as Avis Durgan.
code.logic.resolve_messageimage 0x21f0Resolves a current-logic message number through the message pointer table.
code.logic.load_cachedimage 0x117dLoads a logic resource and keeps it linked in the logic cache.
code.logic.load_resourceimage 0x119aReads, initializes, and message-decodes a logic resource.
code.logic.truncate_cache_to_headimage 0x10f7If data.logic.cache_root is nonzero, stores zero in the first logic cache record’s +0x00 next-link field. Room-switch cache reset uses this to preserve the first logic record while unlinking later cached logic records.
code.logic.call_logicimage 0x12aeTemporarily switches current logic, runs code.logic.interpret_main, and may unlink a transient record.
code.logic.save_resume_ip_actionimage 0x1335Action handler for 0x91; stores the current bytecode pointer in the current logic record’s resume pointer field +0x06.
code.logic.restore_entry_ip_actionimage 0x134aAction handler for 0x92; copies the current logic record’s entry pointer field +0x04 back to resume pointer field +0x06.
code.logic.serialize_resume_tableimage 0x1364Serializes the static cache-head record and each linked logic cache record as four-byte logic-number/relative-resume pairs at data.logic.resume_table, then appends a 0xffff terminator record and returns the byte length.
code.logic.restore_resume_from_tableimage 0x13a5Scans data.logic.resume_table for the first entry matching a loaded logic’s byte resource number and rebuilds its resume pointer as bytecode entry plus saved offset. Stops without changing it at the 0xffff terminator.
code.startup.allocate_runtime_memoryimage 0x43eaStartup memory setup around DOS AH=4a/AH=48h; seeds data.heap.current_top_0a55 and data.heap.base_0a57 from the allocated segment converted to a DS-relative byte offset, then computes data.heap.limit_0a5b.
code.heap.allocateimage 0x13d6Bump-allocates from data.heap.current_top_0a55 up to data.heap.limit_0a5b. On success it returns the old top, advances the current top, refreshes data.vars.free_memory_pages_0011 through code.heap.update_free_memory_var, and updates data.heap.high_water_0a5f. On exhaustion it displays the out-of-memory message and calls the restart/exit helper at 0x02ae; no recoverable failure return was observed.
code.heap.current_topimage 0x1430Returns data.heap.current_top_0a55.
code.heap.rewind_toimage 0x143cStores a caller-supplied pointer in data.heap.current_top_0a55; unlike allocation/reset paths, this helper does not refresh the free-memory byte.
code.heap.save_temporary_markimage 0x144bCopies data.heap.current_top_0a55 to data.heap.temporary_mark_0a5d.
code.heap.restore_temporary_markimage 0x145aIf data.heap.temporary_mark_0a5d is nonzero, rewinds data.heap.current_top_0a55 to that mark and clears the mark.
code.heap.save_room_reset_markimage 0x1476Copies data.heap.current_top_0a55 to data.heap.room_reset_mark_0a59. Startup calls this after initial object/inventory setup and logic 0 load.
code.heap.reset_dynamic_stateimage 0x1485Flushes object update lists through code.object.flush_update_lists_restore, clears the temporary heap mark, restores the current heap pointer from data.heap.room_reset_mark_0a59, and refreshes the free-memory byte. Used by room switch, restart, and restore paths.
code.heap.update_free_memory_varimage 0x14a0Computes data.heap.limit_0a5b - data.heap.current_top_0a55, stores the high byte at data.vars.free_memory_pages_0011, and returns the free-byte count.
code.heap.show_status_actionimage 0x14bdAction handler for 0x87; formats heap size, current usage, high-water usage, room/reset mark usage, and max resource-event/script count.

Resources and DOS Files

LabelSQ2 addressNotes/evidence
code.resource.load_all_directoriesimage 0x4305Loads LOGDIR, VIEWDIR, PICDIR, and SNDDIR.
code.resource.reset_room_cachesimage 0x10d0Room-switch cache reset helper. Calls code.logic.truncate_cache_to_head, clears the view cache root, clears the sound cache root, and clears the picture cache root.
code.resource.read_volume_payload_retryimage 0x2e32Retries the generic volume reader until it returns data or reports failure.
code.resource.read_volume_payload_onceimage 0x2e56Reads one resource payload from a volume file using a directory entry.
code.resource.logic_dir_entryimage 0x4371Resolves logic directory entries from data.resource.logic_dir.
code.resource.view_dir_entryimage 0x43a5Resolves view-like directory entries from data.resource.view_dir.
code.resource.picture_dir_entryimage 0x43d9Resolves picture-like directory entries from data.resource.picture_dir.
code.resource.sound_dir_entryimage 0x440dResolves sound-like directory entries from data.resource.sound_dir.
code.view.clear_cache_rootimage 0x396dClears the view-like resource cache root at [0x0ffa]; called by room-switch cache reset.
code.picture.clear_cache_rootimage 0x49dcClears the picture-like resource cache root at [0x120e]; called by room-switch cache reset.
code.sound.clear_cache_rootimage 0x50ccClears the sound-like resource cache root at [0x125a]; called by room-switch cache reset.
code.dos.create_fileimage 0x5cadDOS int 21h wrapper for AH=0x3c. Returns 0xffff on carry/error.
code.dos.open_fileimage 0x5cceDOS int 21h wrapper for AH=0x3d. Returns 0xffff on carry/error.
code.dos.read_fileimage 0x5cefDOS int 21h wrapper for AH=0x3f. Returns zero on carry/error, so callers compare the returned byte count with the requested count.
code.dos.write_fileimage 0x5d12DOS int 21h wrapper for AH=0x40. Returns zero on carry/error, so callers compare the returned byte count with the requested count.
code.dos.delete_fileimage 0x5d35DOS int 21h wrapper for AH=0x41. Returns zero on carry/error.
code.dos.close_fileimage 0x5d52DOS int 21h wrapper for AH=0x3e; callers observed so far do not inspect an error return.
code.dos.seek_fileimage 0x5d6bDOS int 21h wrapper for AH=0x42. Returns 0xffff:0xffff in DX:AX on carry/error.
code.dos.duplicate_handleimage 0x5d94DOS int 21h wrapper for AH=0x45. Returns 0xffff on carry/error.
code.dos.get_current_directoryimage 0x5db2Writes a leading slash/backslash then calls DOS AH=0x47 for the default drive. Used by save-path prompting.
code.dos.get_current_drive_letterimage 0x5deaCalls DOS AH=0x19 and returns lowercase drive letter a plus the zero-based drive number.
code.dos.find_firstimage 0x5e01Sets the DTA with AH=0x1a, then calls DOS AH=0x4e; returns 0xffff on carry/error.
code.dos.find_nextimage 0x5e26DOS int 21h wrapper for AH=0x4f. Returns 0xffff on carry/error.
code.dos.probe_drive_selectableimage 0x5e3eSaves the current drive, attempts to select a requested lowercase drive letter, checks whether DOS reports it as current, restores the original drive, and returns 1 on success.
code.dos.get_file_timeimage 0x5e73DOS int 21h wrapper for AH=0x57, AL=0; selector code uses the returned time word from CX.
code.dos.prepare_callimage 0x5e8dShared pre-call helper that temporarily switches DS to segment 0x0a01 and clears word [0x184d].
code.event.disable_recordingimage 0x705eClears data.event.recording_enabled; restore replay and temporary view-resource display use this so replay/internal loads do not append new resource-event pairs.
code.event.enable_recordingimage 0x706dSets data.event.recording_enabled; room switch enables recording after resetting the pair buffer, restore/display-mode replay calls it from the post-table finish target at 0x6927, and temporary view-resource display re-enables it before returning.
code.event.reset_pair_bufferimage 0x707cAllocates the pair buffer when data.event.pair_capacity > 0 and no buffer exists, then resets write pointer and active pair count.
code.event.record_pairimage 0x70b1Appends a two-byte (kind, value) pair when flag 7 is clear and recording is enabled; enforces data.event.pair_capacity and updates data.event.pair_high_water.
code.event.prepare_replay_cursorimage 0x712fSets replay read cursor to data.event.pair_buffer_base and recomputes the write/end cursor from data.event.pair_count.
code.event.next_replay_pairimage 0x714cReturns the current replay pair pointer and advances by two bytes, or returns zero at end.
table.restore.replay_event_dispatchimage 0x6915Nine-word jump table for resource-event replay kinds 0..8. Linear disassembly from before the table can swallow the following call 0x706d; disassemble at 0x6927 to see the post-loop re-enable.
code.restore.finish_replay_and_reenable_recordingimage 0x6927Exit target reached when code.event.next_replay_pair returns zero. Calls code.event.enable_recording, then rebinds object view payloads, restores saved object flags, refreshes display/input state, and returns from replay.
code.event.set_pair_capacity_actionimage 0x716aAction handler for 0x8e; writes data.event.pair_capacity, calls code.event.reset_pair_buffer inside update-list flush/rebuild calls, and thereby resets the pair-buffer write cursor and active count.
code.event.save_pair_count_actionimage 0x718bAction handler for 0xab; copies data.event.pair_count to data.event.saved_pair_count.
code.event.restore_pair_count_actionimage 0x719dAction handler for 0xac; restores data.event.pair_count and recomputes data.event.pair_buffer_write.

Pictures and Display

LabelSQ2 addressNotes/evidence
code.picture.load_resourceimage 0x4a3bLoads/caches a picture-like payload.
code.picture.prepareimage 0x4acfSelects cached picture payload and prepares decode state.
code.picture.overlay_prepareimage 0x4b3bOverlay picture path used before decoder entry code.picture.decode_no_clear.
code.picture.discardimage 0x4bceFinds the picture cache record, clears the preceding link slot so that record and later picture records become unreachable, then rewinds the shared heap to the selected record around update-list flush/rebuild.
code.picture.decode_with_clearimage 0x6445Fills logical buffer, then falls into picture command decoding.
code.picture.decode_no_clearimage 0x6440Decodes picture commands without the extra clear/fill entry work.
code.picture.command_scanimage 0x6475Walks picture command bytes from data.picture.current_payload.
table.picture.command_dispatchdata 0x15d6Dispatch table for picture command bytes 0xf0..0xfa.
code.picture.cmd_set_visual_draw_nibbleimage 0x6494Picture command 0xf0; enables low-nibble drawing state.
code.picture.cmd_disable_visual_draw_nibbleimage 0x64b5Picture command 0xf1; disables low-nibble drawing state.
code.picture.cmd_set_control_draw_nibbleimage 0x64c7Picture command 0xf2; enables high-nibble control drawing state.
code.picture.cmd_disable_control_draw_nibbleimage 0x64edPicture command 0xf3; disables high-nibble control drawing state.
code.picture.cmd_draw_corner_path_y_firstimage 0x6612Picture command 0xf4; draws alternating vertical/horizontal corner paths.
code.picture.cmd_draw_corner_path_x_firstimage 0x6603Picture command 0xf5; draws alternating horizontal/vertical corner paths.
code.picture.cmd_draw_absolute_linesimage 0x6646Picture command 0xf6; draws point-to-point absolute lines.
code.picture.cmd_draw_relative_linesimage 0x665ePicture command 0xf7; draws relative vector steps.
code.picture.cmd_seed_fillimage 0x66abPicture command 0xf8; seed-fill entry.
code.picture.cmd_set_pattern_modeimage 0x6524Picture command 0xf9; stores pattern mode byte.
code.picture.cmd_pattern_plotimage 0x64ffPicture command 0xfa; patterned plot/fill-family command.
code.picture.read_coord_pairimage 0x66b8Reads clamped X/Y coordinate pair for picture commands.
code.picture.draw_lineimage 0x66e1Draws a line between current and target picture coordinates.
code.picture.seed_fillimage 0x533bExpands from a seed coordinate through matching buffer nibbles.
data.picture.seed_fill_span_scratchdata 0x126c..0x1279Scratch bytes/words used by code.picture.seed_fill to track current span limits, vertical direction, saved row/column state, and deferred branch bounds. Field meanings are source-backed but intertwined, so keep this as a conservative block label unless a future version requires per-field names.
code.display.clear_logical_bufferimage 0x5528Clears the logical graphics/control buffer.
code.display.full_refreshimage 0x5546Copies/rebuilds the visible display from logical buffers.
code.display.fill_buffer_wordimage 0x5257Fills the logical buffer segment with a caller-supplied word.
code.display.draw_horizontal_lineimage 0x526fDraws a horizontal line in the logical buffer using active picture draw state.
code.display.draw_vertical_lineimage 0x52abDraws a vertical line in the logical buffer using active picture draw state.
code.display.pixel_writeimage 0x52f9Writes pixel/control nibbles into the logical buffer.
code.display.map_visual_color_for_adapterimage 0x5685Maps picture visual color bytes before they are stored in draw masks. Returns the input unchanged for the EGA target path; delegates to the graphics overlay at 0x9815 only for hardware selector [0x112e] == 0 with modes other than 2 or 3.
code.display.shake_screen_actionimage 0x7a00Action handler for 0x6e; reads a count byte and performs display-mode-specific transient screen-shake work. The normal path writes CRT controller registers 0x02 and 0x07 from data.display.shake_offset_table.
overlay.cga.map_visual_color_for_modeCGA_GRAF.OVL near 0x9815CGA overlay color mapper called by code.display.map_visual_color_for_adapter; indexes three-byte entries in data.display.cga_color_map and returns either a duplicated byte for mode 0 or a two-byte word for mode 1.

Objects, Views, and Motion

LabelSQ2 addressNotes/evidence
code.view.load_resourceimage 0x39f7Loads/caches a view-like payload.
code.view.discardimage 0x3f0dFinds the selected view record, clears the preceding link slot so that record and later view records become unreachable, then rewinds the shared heap to the selected record around update-list flush/rebuild. Actions 0x20 and 0x99 enter through immediate/variable wrappers at 0x3ecd and 0x3ee9.
code.object.bind_viewimage 0x3ae7Binds a cached view payload to an object record.
code.object.select_groupimage 0x3bb7Selects a top-level view subresource/group.
code.object.select_group_tableimage 0x3c1bComputes a group table pointer from the view payload.
code.object.select_frameimage 0x3ccbSelects a derived frame/entry and updates object size/pointers.
code.object.setup_transient_display_objectimage 0x2d52Builds/draws the temporary object-like record rooted at 0x0eb4 from staged bytes 0x0eae..0x0eb3; records event kind 5 plus three parameter pairs for restore replay.
code.object.placeimage 0x593aPlaces an object and performs priority/control collision adjustment. If the initial candidate fails bounds/collision/control tests, it searches in a widening spiral: left 1, down 1, right 2, up 2, then repeats with increasing segment lengths.
code.object.collision_testimage 0x4719Object-object rectangle/crossing test used by placement and movement. Returns zero when object flag bit 0x0200 bypasses collision; otherwise scans eligible objects and rejects overlapping/crossing candidates.
code.object.control_acceptanceimage 0x56b8Tests an object’s proposed footprint against high-nibble control/priority classes in data.display.logical_buffer_segment; source and QEMU probes validate selected 0x0002, 0x0100, and 0x0800 flag effects, including the final scanned class state.
code.object.frame_timer_updateimage 0x0563Per-cycle active-object scan that decrements frame timer byte +0x20, calls code.object.advance_frame_by_mode at zero, and reloads +0x20 from +0x1f.
code.object.advance_frame_by_modeimage 0x48b3Dispatches object frame mode byte +0x23; modes loop or stop frames and may set completion flag byte +0x27.
data.object.group_for_direction_two_or_three_groupsdata 0x08ddDirection-to-group table used by code.object.frame_timer_update when object byte +0x0b is 2 or 3 and bit 0x2000 is clear.
data.object.group_for_direction_four_plus_groupsdata 0x08e7Direction-to-group table used by code.object.frame_timer_update when object byte +0x0b is at least 4 and bit 0x2000 is clear.
code.object.build_update_list_sortedimage 0x0358Shared update-list builder. It scans the object table, accepts records through a callback, computes a draw key from baseline Y or code.control.priority_to_y, selection-sorts by ascending key, and inserts nodes through code.object.insert_update_node_head.
code.object.insert_update_node_headimage 0x042fAllocates a 16-byte render/update node and inserts it at the head of the supplied root list while preserving the first inserted node as the tail.
code.object.draw_update_list_tail_to_headimage 0x045eDraws a root list from tail toward head, saving each backing rectangle and then drawing the object’s selected frame.
code.object.refresh_update_list_saved_posimage 0x0488Walks a root list from head toward tail, calls code.object.update_dirty_rect, and updates saved-position fields plus stationary bit 0x4000.
code.object.build_active_update_listimage 0x6a26Builds update-list root 0x16ff using callback code.object.accept_active_root_16ff.
code.object.build_inactive_partition_listimage 0x6a3dBuilds update-list root 0x1703 using callback code.object.accept_root_1703.
code.object.flush_update_lists_restoreimage 0x6a54Flushes roots 0x16ff and 0x1703 through helper 0x0307, restoring saved backing rectangles and freeing nodes.
code.object.rebuild_draw_update_listsimage 0x6a8eRebuilds/draws root 0x1703, then rebuilds/draws root 0x16ff.
code.object.refresh_update_listsimage 0x6aabRuns dirty-rectangle/saved-position refresh helper 0x0488 over root 0x1703, then root 0x16ff.
code.object.clear_root_16ff_membershipimage 0x6b44Clears object bit 0x0010 when set, moving an active object from root 0x16ff eligibility to root 0x1703 eligibility after a flush/rebuild.
code.object.set_root_16ff_membershipimage 0x6b62Sets object bit 0x0010 when clear, moving an active object from root 0x1703 eligibility to root 0x16ff eligibility after a flush/rebuild.
code.object.update_dirty_rectimage 0x5762Refreshes object dirty-rectangle state.
code.object.save_rect_overlay_entryoverlay IBM_OBJS.OVL:0x9db0Entry jump to rectangle save routine.
code.object.restore_rect_overlay_entryoverlay IBM_OBJS.OVL:0x9db3Entry jump to rectangle restore routine.
code.object.draw_overlay_entryoverlay IBM_OBJS.OVL:0x9db6Entry jump to selected-frame drawing routine.
code.object.rewrite_frame_orientationimage 0x587dRewrites bit-0x80 frame data when cached orientation bits differ from object +0x0a.
code.control.priority_to_yimage 0x4cbbMaps a priority/control value back to a Y-like sort key. In SQ2’s normal table mode it scans downward from sentinel index 0xa8; the direct formula branch appears source-present but no SQ2 write has been found that enables it.
code.motion.update_objectsimage 0x150aPer-cycle object movement/update pass.
code.motion.pre_mode_and_boundary_updateimage 0x0644Scans active objects with countdown byte +0x01 == 1, dispatches mode byte +0x22, then applies rectangle-boundary helper code.motion.rectangle_boundary_check when enabled.
code.motion.rectangle_boundary_checkimage 0x06d9Compares current and next baseline points against script rectangle globals [0x0131..0x013d], setting bit 0x0080 and clearing direction on crossing when bit 0x0002 is clear.
code.motion.dispatch_mode_stepimage 0x067aDispatches object mode byte +0x22 to random, approach-first-object, or target-direction helpers when countdown byte +0x01 is ready.
code.motion.start_target_directionimage 0x1672Computes initial direction toward object target fields.
code.motion.compute_directionimage 0x16edDirection lookup from current and target coordinates.
code.motion.complete_target_motionimage 0x16b9Restores step state, sets completion flag, clears target mode.
code.motion.random_mode_stepimage 0x3f5aPer-cycle random-motion mode handler.
code.motion.random_directionimage 0x3fa3Picks a random direction-like byte.
code.motion.approach_first_object_stepimage 0x0b36Per-cycle approach-first-object mode handler.

Text, Input, and Save State

LabelSQ2 addressNotes/evidence
code.text.display_stringimage 0x1ce8Displays a resolved string and returns an interaction result in some paths.
code.text.display_message_windowimage 0x1d96Builds a modal message window: closes any prior active saved window, formats the message, computes packed save/restore rectangle coordinates, calls code.text.draw_boxed_window, sets data.text.window_active_0d1d, prints the formatted text, and refreshes text/input areas.
code.text.close_window_stateimage 0x1f2bRestores/clears active text-window state and always clears data.input.width_flag_0d0f. If data.text.window_active_0d1d is set, it restores the rectangle saved by code.text.display_message_window through code.text.restore_saved_rectangle; QEMU close_text_window_state_clears_input_width_flag validates the inactive-window clear side.
code.text.format_stringimage 0x2374Formats text into caller-provided buffers.
code.text.format_message_to_bufferimage 0x1f54Formats/copies a resolved logic message into a stack buffer.
code.text.draw_boxed_windowimage 0x5590Helper called by the modal message window path with packed rectangle coordinates and attribute word 0x040f; it delegates to overlay save/fill helpers around 0x9812.
code.text.restore_saved_rectangleimage 0x560cHelper called by code.text.close_window_state with the packed saved-window rectangle words. It loads those words and delegates to overlay restore helper 0x980c.
code.text.clear_rowsimage 0x2b78Helper used by action 0x69; wraps code.text.clear_bounds with left column 0 and right column 0x27. QEMU text_rect_clear_rows_removes_formatted_text validates rows 5..6 clearing logical Y 40..55 to visual color 0.
code.text.clear_rowimage 0x2ba6Wraps code.text.clear_rows with top row equal to bottom row. Used by status-line hide (0x71) and input-line disable (0x77); QEMU text_hide_clear_behaviour_001 validates a configured row clearing to visual color 0.
code.text.clear_boundsimage 0x2bc4BIOS int 10h scroll/clear wrapper used by action 0x9a; arguments map to top, left, bottom, right, attribute. QEMU validates text columns as four logical pixels wide and rows as eight logical pixels tall in the EGA target.
code.text.redraw_status_lineimage 0x34bdRedraws the status-line-like area when data.text.status_line_enabled is nonzero, using the current text attribute globals and the status row global. QEMU status_line_show_draws_configured_row validates visible output on the configured row.
code.text.show_status_lineimage 0x3547Action handler for 0x70; sets data.text.status_line_enabled and calls code.text.redraw_status_line. QEMU status_line_show_draws_configured_row validates the row draw.
code.text.hide_status_lineimage 0x355cAction handler for 0x71; clears data.text.status_line_enabled and clears the configured status row.
code.text.set_attribute_pairimage 0x77d5Shared helper for action 0x6d and text-mode transitions. Stores derived text/window attributes in globals [0x05d1], [0x05cd], and [0x05cf]. QEMU text_attribute_pair_changes_attr_mode_clear_color validates that pair (0, 1) changes the later 0x6a clear to visual color 15.
code.input.set_width_flag_actionimage 0x3939Action handler for 0xa3; sets data.input.width_flag_0d0f. QEMU validates the wider live-input path with a long blank string slot 0.
code.input.clear_width_flag_actionimage 0x394bAction handler for 0xa4; clears data.input.width_flag_0d0f. QEMU validates the narrowed live-input path after 0xa3.
data.input.width_flag_0d0fdata 0x0d0fWord tested by input helper code.input.handle_input_char; when set, the helper uses a fixed 0x24 character cap, otherwise it derives the cap from fixed string slot 0. Also cleared by code.text.close_window_state.
data.text.configured_message_width_0d09data 0x0d09One-shot modal-message width override used by actions 0x97 and 0x98; a zero operand is replaced with 0x1e, and the value resets to 0xffff after the display action.
data.text.configured_message_row_0d0bdata 0x0d0bOne-shot modal-message row override used by actions 0x97 and 0x98; the display-window helper adds the current display base row when computing the text cursor row.
data.text.configured_message_column_0d0ddata 0x0d0dOne-shot modal-message column override used by actions 0x97 and 0x98; without it the helper centers the window horizontally from the formatted line width.
data.text.window_active_0d1ddata 0x0d1dWord set after a modal message window saves/draws its rectangle. code.text.display_message_window closes an existing active window before opening a new one; code.text.close_window_state tests this word before restoring the saved rectangle, then clears it.
data.text.window_saved_lower_right_0d23data 0x0d23Packed rectangle word computed by code.text.display_message_window and passed as the first restore argument to code.text.restore_saved_rectangle.
data.text.window_saved_upper_left_0d25data 0x0d25Packed rectangle word computed by code.text.display_message_window and passed as the second restore argument to code.text.restore_saved_rectangle.
code.text.enter_attr_modeimage 0x76caAction handler for 0x6a; erases the prompt marker, sets byte [0x1757], derives attributes, enters the overlay text mode through entry 0x9803, then clears a text rectangle. QEMU text_attribute_enable_clears_visible_surface validates a black visible logical surface with the default pair, and text_attribute_pair_changes_attr_mode_clear_color validates the stored pair path.
code.text.leave_attr_modeimage 0x78cbShared cleanup for action 0x6b; clears byte [0x1757], recomputes attributes, calls overlay entry 0x9806, then redraws status and input-line areas. QEMU text_attribute_disable_restores_picture_draw validates that ordinary picture/object drawing is visible again after this action.
code.input.edit_stringimage 0x0da9Blocking string editor used by 0x73 and 0x76. It copies the destination buffer to a local edit buffer, displays it, waits through code.input.wait_event, dispatches keys through data.input.edit_key_table, copies accepted text back on Enter, and returns without copying on Escape.
code.input.handle_input_charimage 0x3652Shared input-line character helper. Normal characters append to visible buffer 0x0fa4; backspace erases one visible character; Enter copies the visible buffer into source buffer 0x0fce, parses it, clears visible length [0x0ff8], and redraws the input row.
code.input.erase_input_lineimage 0x3726Action handler for 0x8a; loops code.input.handle_input_char(0x08) while visible length [0x0ff8] is nonzero. QEMU input_line_erase_clears_typed_buffer validates the visible-row erase.
code.input.refresh_input_lineimage 0x3753Action handler for 0x89; when input is enabled, calls the source-to-visible refresh helper in the normal EGA path. QEMU input_line_refresh_repaints_entered_buffer validates repaint from source buffer 0x0fce after Enter.
code.input.append_source_to_visibleimage 0x37a5Appends bytes from source input buffer 0x0fce into visible buffer 0x0fa4 until visible length [0x0ff8] reaches the source length, drawing each byte through 0x29f6.
code.input.wait_for_event_or_tickimage 0x4529Blocks until an event queue record is available or timer globals [0x0129]/[0x012b] change, then returns the event record pointer. Menu interaction uses this before normalizing raw and movement events.
code.input.wait_eventimage 0x45d7Blocking event wait helper. Calls the event normalizer at 0x459e until the returned word is neither 0x0000 nor 0xffff.
code.input.enqueue_eventimage 0x44a9Enqueues an input/event record with a type word and value word in the circular queue rooted near data.input.event_queue. Menu selection uses this to enqueue type 3 item ids.
code.input.dequeue_eventimage 0x44f9Dequeues one 4-byte event record from data.input.event_queue_base, returning zero when read and write pointers match.
code.input.reset_event_stateimage 0x4482Resets input-related state around helper calls 0x466f and 0x6326, then resets event queue write/read pointers [0x120a] and [0x120c] to base 0x11ba. Used by room switch and pause/calibration paths.
code.input.keyboard_irq_hookimage 0x6036Keyboard interrupt hook. Handles selected raw scan codes, tracks pressed/released latches, and when data.input.key_release_enqueue_gate_1530 is nonzero can enqueue (type=2, value=0) through code.input.enqueue_event on selected key releases. Local model tools/agi_input.py pins the tracked-key latch contract.
code.input.drain_bios_keysimage 0x467fRepeatedly polls BIOS-key helper 0x5a89, maps known movement keys through code.input.map_raw_direction_key, and enqueues type-2 movement events or type-1 raw key events.
code.input.map_raw_direction_keyimage 0x46b6Scans data.input.menu_direction_event_map for a raw key word and returns the movement value, or 0xffff when unmapped.
code.input.remap_display_adapter_eventimage 0x46e8When display adapter word [0x112e] == 2, scans data.input.display_adapter_event_map and converts matching type-1 events to type-2 movement events.
code.input.normalize_confirm_eventimage 0x4634Normalizes type-1 event values for confirm/editor paths: observed mappings include 0x0101/0x0301 to Enter (0x0d) and 0x0201/0x0401 to Escape (0x1b).
code.input.show_prompt_markerimage 0x37f7Draws the configured prompt marker byte from data.input.prompt_marker_char when the marker is not already visible and the display-mode gates allow it.
code.input.erase_prompt_markerimage 0x382eClears the prompt-marker visible flag and echoes backspace when a prompt marker byte is configured.
code.input.set_prompt_marker_charimage 0x38b4Action handler for 0x6c; resolves a message and stores its first byte in data.input.prompt_marker_char. QEMU input_prompt_empty_message_suppresses_marker validates that an empty message stores zero and suppresses marker drawing on input-line redraw.
code.input.redraw_input_lineimage 0x38d7Redraws the configured input-line area when enabled, including the prompt marker, fixed string slot 0, and the visible input buffer. QEMU input_line_enable_clears_configured_row validates the configured row clear with empty prompt/input text.
code.input.set_line_configimage 0x78f0Action handler for 0x6f; stores input/status row globals and computes display offset global [0x1379] from the first operand.
code.input.map_key_eventimage 0x4c3dAction handler for 0x79; appends a key/event mapping word and mapped value to the first free four-byte slot rooted at data.input.key_event_map.
code.words.parse_string_slot_actionimage 0x1958Action handler for 0x75; clears parser flags 2 and 4, validates slot index < 12, and parses fixed string slot data.strings.slots + index * 0x28.
code.words.parse_bufferimage 0x18acClears parsed word ID/pointer tables, normalizes the source string, looks up up to ten output words, sets parser flag 2 when any recognized or unknown output slot exists, and records count/error position in data.words.parsed_count_or_error_position.
code.words.normalize_string_for_parseimage 0x199dCollapses separator runs to spaces, drops ignored punctuation, trims a trailing space, and writes data.words.normalized_parse_buffer.
code.words.lookup_next_normalized_wordimage 0x1a6bLooks up the current normalized token in WORDS.TOK, advances data.words.current_parse_pointer_0cd1, returns 0xffff for unknown tokens, returns zero for ignored dictionary words, and returns nonzero dictionary IDs for parsed words.
code.words.terminate_unknown_tokenimage 0x1bc7Replaces the next space or terminator in the normalized parse buffer with zero for an unknown token.
code.words.advance_dictionary_entryimage 0x1be4Advances from one compressed dictionary entry to the next by finding the suffix byte with bit 0x80 and stepping over the two-byte ID.
code.view.display_resource_textimage 0x5edbShared helper for actions 0x81 and 0xa2; disables resource-event recording while it loads, displays, and optionally discards a temporary view resource.
data.input.edit_key_tableimage/data 0x0e64Key dispatch table used by code.input.edit_string. The observed SQ2 bytes map 0x03 and 0x18 to clear-current-input, 0x08 to backspace, 0x0d to accept/copy, and 0x1b to cancel/return. Evidence: xxd -g 1 -s 0x1060 -l 0x20 build/cleanroom/AGI.decrypted.exe.
code.save.restore_game_stateimage 0x2512Restore-game action handler.
code.save.save_game_stateimage 0x2753Save-game action handler.
code.save.copy_description_to_string_actionimage 0x2726Action handler for 0xaa; copies up to 0x1f bytes from runtime save-description buffer [0x0e72] into string slot 0x020d + arg0 * 0x28.
code.save.read_length_prefixed_blockimage 0x26b0Reads a length-prefixed memory block from a save file.
code.save.write_length_prefixed_blockimage 0x28c6Writes a length-prefixed memory block to a save file.
code.save.format_slot_filenameimage 0x5b73Formats a numbered save filename with %s%s%ssg.%d, using data.save.path_buffer_1962, a chosen slash/backslash separator, data.save.signature_prefix_0002, and the slot number.
code.save.select_slot_or_pathimage 0x85e5Shared save/restore slot/path selection helper. Saves/restores text state, stops sound, delegates path prompting, scans selectable save slots, formats the selected filename, and returns zero for cancel/no selection.
code.save.check_drive_or_path_availableimage 0x86a3Selector helper that compares the selected drive/path state and displays the insert-disk style message when the target is unavailable.
code.save.prompt_path_if_neededimage 0x8705Selector helper that fills a default path when needed, displays the save/restore path prompt, edits data.save.path_buffer_1962, validates it through the path validator at 0x5bdd, and returns zero on cancel/failure.
code.save.edit_modal_text_fieldimage 0x8794Modal edit helper used by the save selector. Draws a prompt window, clears the edit row, calls the line editor with a 31-character cap, closes the window, and returns one only when Enter accepted the edit.
code.save.select_numbered_slotimage 0x8814Scans up to 12 numbered save files, displays description rows, handles Enter/Escape/up/down selection, and returns the selected slot number or zero. In save mode it prompts for a new description before accepting an empty-description slot.
code.save.read_slot_summaryimage 0x8b9fFormats a numbered save filename, opens it, records the file timestamp, reads the 31-byte description, seeks past the first block length prefix, compares a short signature fragment with data.save.signature_prefix_0002, and returns whether the slot is a valid candidate.
code.restore.replay_resource_eventsimage 0x681cRestore/display-mode state rebuild. Stops sound, clears resource caches, disables resource-event recording while replaying saved resource/event pairs, then reaches code.restore.finish_replay_and_reenable_recording at 0x6927 to re-enable recording, rebind active object views, and refresh display/input/status state.
code.dos.validate_pathimage 0x5bddSave-selector path validator. Skips leading spaces, fills an empty path with the current directory, strips a trailing slash/backslash for multi-character paths, accepts a single separator, probes drive-only paths, and otherwise calls DOS find-first with directory attributes.

Inventory and Menus

LabelSQ2 addressNotes/evidence
code.inventory.show_selection_actionimage 0x31d8Action handler for 0x7c. Enters a text/list mode, builds the carried-item list through code.inventory.build_selection_list, restores text state, and returns to the caller after acknowledgement or selection.
code.inventory.build_selection_listimage 0x3203Builds a stack-local 8-byte-per-row list from the 3-byte metadata table rooted at data.inventory.table_root; includes only entries with marker byte 0xff.
code.inventory.draw_selection_listimage 0x3346Draws the inventory header, carried item names or fallback text, and optional highlighted row/prompt depending on flag 13.
code.inventory.initialize_metadata_and_objectsimage 0x0fa5Loads and decodes the inventory metadata file, consumes its three-byte header, establishes the item-table end and runtime block length, and allocates/initializes the 43-byte drawable-object table from the header’s maximum object index.
data.inventory.selection_result_bytedata 0x0022Absolute byte written by code.inventory.build_selection_list on interactive Enter/Escape. Since script byte variables begin at data.vars.byte_variables (0x0009), this is script variable 0x19. QEMU inventory_selection_001 validates Enter and Escape effects.
code.menu.add_headingimage 0x911dAction handler for 0x9c; allocates and links an 18-byte menu heading node.
code.menu.add_itemimage 0x91cfAction handler for 0x9d; allocates and links a 14-byte menu item node and stores the item id at node offset +0x0c.
code.menu.finalize_setupimage 0x92baAction handler for 0x9e; finalizes/freezes menu setup.
code.menu.set_item_enabledimage 0x935fShared helper used by 0x9f and 0xa0 to enable or disable menu items by id.
code.menu.interactimage 0x93d1Interactive menu path. Draws the menu, waits for input, and enqueues type-3 events with selected item ids for enabled items; QEMU menu_interaction_001 validates one-item Enter selection and menu_edges_002 validates Escape, disabled Enter, and re-enable behavior.
table.menu.navigation_dispatchimage 0x9526Eight-word movement dispatch table used by code.menu.interact for type-2 events. Values 1..8 branch to previous item, first item, next enabled heading, last item, next item, last heading, previous enabled heading, and root heading.
code.menu.draw_headingimage 0x9557Draws a menu heading and its item list, including highlighting the current item through helpers 0x95d0, 0x9625, and 0x95a9.
code.menu.remember_item_and_restore_rectimage 0x95a9Stores the active item pointer in the heading node at +0x0e, redraws/unhighlights the heading, and restores the saved menu rectangle through helper 0x560c. Used when leaving a heading and when exiting the modal menu.
code.menu.draw_itemimage 0x95d0Positions and draws one menu item using row/column fields from an item node.
code.menu.erase_or_unhighlight_itemimage 0x9625Redraws or clears the item’s saved rectangle/highlight state while navigating menu items.
data.menu.finalizeddata 0x1d2aMenu setup finalization flag set by code.menu.finalize_setup.
data.menu.request_interactiondata 0x1d22Word set by action 0xa1 when flag 14 is set; the input/event path enters code.menu.interact and clears this word after handling menu interaction.
data.menu.heading_rootdata 0x1d2cRoot pointer for the circular menu heading list used by setup and interaction routines.
data.menu.current_headingdata 0x1d2eCurrent/remembered heading pointer persisted by code.menu.interact after movement events and initialized by code.menu.finalize_setup.
data.menu.current_itemdata 0x1d30Current/remembered item pointer persisted by code.menu.interact after movement events and initialized from the current heading’s item root.
data.menu.saved_rect_startdata 0x1d32Packed rectangle coordinate computed by helper 0x968b and consumed by menu save/restore rectangle helpers.
data.menu.saved_rect_enddata 0x1d34Packed rectangle coordinate computed by helper 0x968b and consumed by menu save/restore rectangle helpers.

System, Restart, Trace, Sound, and Files

LabelSQ2 addressNotes/evidence
code.system.dos_terminateimage 0x00aeDOS process termination wrapper. Loads exit code byte argument into AL, sets AH=0x4c, and invokes int 21h.
code.system.exit_with_cleanupimage 0x02aeShared fatal/exit helper. Calls code.system.shutdown_cleanup, then calls code.system.dos_terminate(0). Used by 0x86 confirmed exit/restart, restore read failure, verification failure, and allocation failure paths.
code.system.pause_actionimage 0x0257Action handler for 0x88; enters modal state, resets input/event state, stops sound, displays the pause message, and returns to the following bytecode.
code.system.confirm_exit_actionimage 0x027fAction handler for 0x86; stops sound, either exits immediately when operand byte is 1 or displays the confirmation message at 0x05e3 and exits only on confirmation.
code.restart.confirm_restart_actionimage 0x2472Action handler for 0x80; confirmation-gated in-engine restart. On acceptance it clears input, preserves flag 9, rewinds heap/update-list state, reruns initial object/inventory setup, refreshes display/menu state, optionally reloads trace logic, clears timer words, and returns zero to stop the current logic stream. Escape/cancel returns the following bytecode pointer.
code.system.shutdown_cleanupimage 0x8275Cleanup before DOS termination. Closes the log file if open, restores hooked interrupt vectors/timer state, then sets the BIOS video mode from [0x1807].
code.log.close_if_openimage 0x838cIf data.log.file_handle is not 0xffff, closes it and resets the global to 0xffff. Called by code.system.shutdown_cleanup.
code.system.install_interrupt_hooksimage 0x83acSaves original interrupt vectors and installs interpreter hooks for keyboard/timer/critical-error style services.
code.system.restore_interrupt_hooksimage 0x849fRestores interrupt vectors saved by code.system.install_interrupt_hooks and resets the timer PIT divisor before DOS termination.
code.video.set_modeimage 0x5a5eBIOS video-mode wrapper used by shutdown cleanup; calls int 10h with AH=0 and mode byte argument.
code.room.switch_stateimage 0x1792Shared helper for actions 0x12 and 0x13; stops sound, resets heap/update/input state, seeds selected object fields, resets room caches through code.resource.reset_room_caches, updates room variables, loads destination logic, handles entry-boundary placement, sets flag 5, redraws status/input state, and returns zero. QEMU now validates re-entry/current-room dispatch, current/previous/boundary variables, all four boundary placements, and visible absence of a pre-switch persistent object. Exact object/cache field effects are source-backed from disassembly.
code.restart.initialize_game_tablesimage 0x0fa5Initial object/inventory/setup routine called during startup and accepted in-engine restart. It prepares inventory/object metadata, allocates or clears the object table, resets flags/input/display defaults, clears resource caches/update lists, and seeds room-entry globals.
code.display.show_priority_screen_actionimage 0x731bAction handler for 0x1d; sets data.display.priority_screen_mode, refreshes the screen, waits for an event, refreshes again, then clears the mode. QEMU priority_diag_sound_001 validates return after Enter.
data.display.priority_screen_modedata 0x1755Word tested by the full-screen refresh path to display priority/control nibbles instead of normal visual nibbles.
code.object.display_diagnostics_actionimage 0x72b5Action handler for 0x85; formats object fields into the diagnostic template at data 0x1713 and displays the text. QEMU priority_diag_sound_001 validates return after Enter.
code.trace.enable_window_actionimage 0x8c91Action handler for 0x95; when the trace window is already active, consumes one extra byte, otherwise calls code.trace.enable_window_if_flagged.
code.trace.enable_window_if_flaggedimage 0x8caeEnables and draws the trace window only when flag 10 is set. QEMU trace_window_enable_002 validates the enabled red-border/white-fill/black-text path; system_dialog_001 covers the flag-clear dispatch path.
code.trace.configure_window_actionimage 0x8d3dAction handler for 0x96; stores trace logic/resource, row offset, and height globals, clamping height to at least 2. QEMU validates 0x96(0,1,2) feeding the enabled trace-window draw path.
data.trace.active_statedata 0x1d10Trace-window state word tested by 0x95 and cleared by helper 0x8d79.
data.trace.logic_resourcedata 0x1d12Optional logic resource number used by trace formatting and by restart/room-switch reload paths.
data.trace.row_offsetdata 0x1d08Row offset configured by action 0x96.
data.trace.heightdata 0x1d0aTrace-window height configured by action 0x96, clamped upward to at least 2.
code.sound.start_with_flagimage 0x51d3Action handler for 0x63; stops prior sound, stores/clears completion flag, locates a loaded sound resource, and starts playback. QEMU sound_completion_001 validates load/start/stop dispatch after a prior 0x62.
code.sound.stop_or_clear_stateimage 0x5234Shared stop helper used by 0x64 and before starting another sound. Clears active state and sets the configured completion flag when a sound was active; QEMU priority_diag_sound_001 validates that stop sets flag 77 after 0x63(1,77).
code.sound.find_loaded_resourceimage 0x50d8Looks up a loaded sound cache record by resource number for code.sound.start_with_flag; a zero result triggers an error path.
code.sound.load_resourceimage 0x5126Loads/caches a sound-like payload. Restore-time resource replay calls this for saved sound load events.
code.sound.driver_startimage 0x7f96Hardware/driver-facing sound start helper called after code.sound.start_with_flag finds a loaded sound record.
code.sound.driver_tickimage 0x801cTimer-driven playback tick. Tests flag 9, advances active channel countdowns, consumes duration/tone/control records, and stops/completes playback when all active channels terminate.
code.sound.driver_stopimage 0x80afHardware/driver-facing sound stop helper called by code.sound.stop_or_clear_state when sound state had been active.
code.sound.driver_stop_coreimage 0x80c1Shared low-level stop/completion helper. Silences hardware, clears data.sound.active_state, and sets data.sound.completion_flag.
code.sound.driver_write_toneimage 0x80f3Hardware-specific tone/control output helper called after an event record is consumed and by the stop path for silence writes. Selector 0/8 uses PIT/PC-speaker ports and computes divisor 12 * (((tone_word & 0x3f) << 4) + ((tone_word >> 8) & 0x0f)); other observed selectors write encoded tone bytes to port 0xc0.
code.sound.driver_write_attenuationimage 0x8162Hardware-specific attenuation/envelope output helper called on countdown ticks, event reads, and channel termination. Maintains low-nibble attenuation, applies source envelope/delta state, and writes combined channel/attenuation bytes to port 0xc0 on non-PC-speaker paths.
code.sound.timer_irq_hookimage 0x8521Replacement timer interrupt hook. Calls code.sound.driver_tick while sound state is active, then chains to the original timer interrupt every third invocation through divider byte data.sound.timer_irq_divider.
data.sound.active_statedata 0x1258Word set while sound playback state is active.
data.sound.completion_flagdata 0x126aWord holding the flag number set by code.sound.stop_or_clear_state; 0x63 stores and clears this flag before starting playback.
data.sound.channel_stream_pointersdata 0x1788..0x178fFour current stream pointers copied from a sound cache record by code.sound.driver_start; the playback tick advances them through event records.
data.sound.channel_countdownsdata 0x1790..0x1797Four countdown words initialized to 1 and reloaded from event duration words during playback.
data.sound.channel_active_wordsdata 0x1798..0x179fFour nonzero/zero words used by the playback tick to decide whether a channel is still active.
data.sound.channel_attenuationdata 0x17a8..0x17afPer-channel low-nibble attenuation/control values; 0x0f is the silent value used when a channel terminates.
data.sound.active_channel_byte_limitdata 0x1804Tick-loop byte offset limit. Value 2 advances only channel 0; value 8 advances channels 0 through 3.
data.sound.remaining_active_channelsdata 0x1806Byte decremented as channel terminators are consumed; zero triggers the stop/completion path.
data.sound.timer_irq_dividerdata 0x184fTimer-hook countdown byte. The hook acknowledges the interrupt directly until it reaches zero, then resets it to 3 and chains to the original timer interrupt.
code.log.append_messageimage 0x828fAction handler for 0x90; opens/creates logfile, appends a room/input/message record, closes the handle, and returns. QEMU log_file_contents_001 validates the extracted LOGFILE content for a synthetic message.
data.log.file_handledata 0x1823DOS file handle for the log file; 0xffff means closed/unopened.
data.log.filenamedata 0x1825Zero-terminated log filename, observed as logfile.

Runtime Globals and Data Tables

LabelSQ2 addressNotes/evidence
data.vars.byte_variablesdata 0x0009Byte variable array used by many conditions/actions.
data.vars.current_roomdata 0x0009Byte variable 0. code.room.switch_state writes the destination room here; SQ2 logic 0 later dispatches room logic with call_logic_var(v0) at logic bytecode offset 0x053e.
data.vars.previous_roomdata 0x000aByte variable 1. code.room.switch_state copies the prior current-room byte here before overwriting data.vars.current_room. Many room-entry blocks branch on this value.
data.vars.entry_boundarydata 0x000bByte variable 2. code.room.switch_state uses values 1..4 to place object 0 at a room edge, then clears the byte.
data.input.mapped_status_bytesdata 0x1218..0x1249Fifty transient status bytes tested by condition 0x0c; cleared before each cycle’s timed/input event processing.
data.vars.free_memory_pages_0011data 0x0011Byte variable 8. code.heap.update_free_memory_var stores the high byte of available heap bytes here after successful allocations and dynamic-state resets.
data.motion.global_direction_000fdata 0x000fGlobal direction byte mirrored with object0 byte +0x21 by code.engine.main_cycle; also written by first-object motion helpers.
data.motion.direction_mirror_selector_0139data 0x0139Word selector for the pre-logic object0/global direction mirror. Action 0x83 clears it; action 0x84, room switch, and selected first-object stop/reset helpers set it.
data.flags.packed_flagsdata 0x0109Packed flag bitfield; flag 0 is the high bit of byte 0x0109.
data.timer.tick_count_0129data 0x0129Four-byte tick counter included in profile 2.936 save block 1.
data.motion.horizon_012ddata 0x012dWord horizon baseline used by placement and room reset.
data.save.reserved_word_012fdata 0x012fReserved serialized word with no direct reference in the complete executable scan; canonical bytes are zero and loaded bytes are preserved for interchange.
data.motion.rectangle_left_0131data 0x0131Word left bound for the configured movement rectangle.
data.motion.rectangle_top_0133data 0x0133Word top bound for the configured movement rectangle.
data.motion.rectangle_right_0135data 0x0135Word right bound for the configured movement rectangle.
data.motion.rectangle_bottom_0137data 0x0137Word bottom bound for the configured movement rectangle.
data.picture.prepared_number_013bdata 0x013bWord holding the most recently prepared picture number.
data.motion.rectangle_enabled_013ddata 0x013dWord gate for the configured movement rectangle.
data.save.reserved_word_013fdata 0x013fReserved serialized word with no direct reference in the complete executable scan; canonical value is 0x000f and loaded bytes are preserved for interchange.
data.save.reserved_key_map_tail_01e1data 0x01e1..0x0208Ten inactive four-byte key-map records beyond SQ2’s 39-entry loop bound. GR expands the bound to 49 and consumes this exact range.
data.save.reserved_pre_string_padding_0209data 0x0209..0x020cFour reserved bytes retained before the shared string-slot root. Both promoted profiles serialize them as canonical zero bytes.
data.strings.slotsdata 0x020dFixed 40-byte string slots.
data.save.reserved_string_bank_03eddata 0x03ed..0x05ccTwelve reserved 40-byte records beyond the valid 12-slot string range. GR removes this exact bank and places its text attributes at 0x03ed.
data.strings.normalization_drop_charsdata 0x094bZero-terminated bytes skipped during normalized string comparison.
data.words.parser_separators_0c67data 0x0c67Zero-terminated separator bytes used by code.words.normalize_string_for_parse; SQ2 bytes are ,.?!();:[]{}.
data.words.parser_ignored_0c75data 0x0c75Zero-terminated ignored punctuation bytes dropped by code.words.normalize_string_for_parse; SQ2 bytes are apostrophe, backtick, hyphen, and double quote.
data.words.parsed_idsdata 0x0c7bParsed dictionary word IDs.
data.words.parsed_text_pointersdata 0x0c8fPointers into the normalized parse buffer for recognized words and the first unknown output slot.
data.words.parsed_count_or_error_positiondata 0x0ca3Parsed word count, or one-based error position on parse failure.
data.words.dictionary_basepointer global [0x0ca5]Loaded WORDS.TOK base pointer used by dictionary lookup helper 0x1a6b.
data.words.normalized_parse_bufferdata 0x0ca7Parser normalization output buffer consumed one token at a time by dictionary lookup.
data.words.current_parse_pointer_0cd1pointer global [0x0cd1]Current token pointer inside data.words.normalized_parse_buffer, advanced by dictionary lookup helper 0x1a6b.
data.objects.first_objectpointer global [0x096b]Start of 43-byte object record array.
data.objects.endpointer global [0x096d]End pointer for object record array.
data.inventory.table_rootpointer global [0x0971]Root of 3-byte inventory/object metadata entries.
data.inventory.table_endpointer global [0x0973]First byte after the three-byte item-entry table; initialized from the decoded metadata header’s table-size word.
data.inventory.runtime_block_lengthglobal [0x0975]Decoded inventory metadata file length minus its three-byte header; used as save block 3 length.
data.logic.cache_rootpointer global [0x0977]Root of 10-byte logic cache records.
data.logic.cache_head_recorddata 0x0977Static cache-shaped head whose +0x00 word is the linked-list root. code.logic.serialize_resume_table also emits its zero-valued +0x02 logic byte and +0x06 - +0x04 offset as the first block-5 record.
data.logic.current_recordpointer global [0x0981]Current logic record used by message resolution and resume state.
data.logic.resume_tabledata 0x0985Scratch table serialized as save block 5: four-byte logic-number/relative-resume records followed by a 0xffff record.
data.resource.logic_dirpointer global [0x11b2]Loaded logic directory pointer.
data.resource.view_dirpointer global [0x11b4]Loaded view directory pointer.
data.resource.picture_dirpointer global [0x11b6]Loaded picture directory pointer.
data.resource.sound_dirpointer global [0x11b8]Loaded sound directory pointer.
data.resource.view_cache_rootpointer global [0x0ffa]Root of cached view-like resource records. Cleared by code.view.clear_cache_root during room-switch cache reset.
data.resource.picture_cache_rootpointer/global record [0x120e]Picture-like cache root/static first record used by picture loading. Cleared by code.picture.clear_cache_root during room-switch cache reset.
data.resource.sound_cache_rootpointer/global record [0x125a]Sound-like cache root/static first record used by sound loading. Cleared by code.sound.clear_cache_root during room-switch cache reset.
data.save.description_bufferdata 0x0e72Runtime save-description/path buffer consumed by 0xaa and tested by save/restore handlers after slot/path selection.
data.save.signature_prefix_0002data 0x0002Runtime game signature/save filename prefix. Action 0x8f copies a logic message here and verifies it against the embedded SQ2 signature; save filename formatting and slot summary filtering also consume this string.
data.save.path_buffer_1962data 0x1962Path buffer edited by code.save.prompt_path_if_needed and validated by the generic path helper.
data.save.header_description_buffer_1c6cdata 0x1c6c31-byte save-file description/header buffer written before the length-prefixed state blocks. Filled by the empty-slot save-description prompt when needed.
data.save.filename_buffer_1c8cdata 0x1c8cFully formatted save filename/path used by the save and restore DOS file wrappers after slot selection.
data.display.hardware_kindglobal [0x112e]Display adapter/hardware selector used by setup and display branches.
data.display.modeglobal [0x1130]Display mode selector affected by command-line flags and opcode 0x8c.
data.display.shake_low_basedata 0x1779Base value added to the second byte of each normal-path screen-shake table pair before writing CRT controller register 0x07; 0x6e sets it to 0x70 or 0x38 from hardware selector [0x112e].
data.display.shake_offset_tabledata 0x177aByte pairs consumed by the normal-path 0x6e screen shake loop. The first byte plus [0x1365] is written to CRT register 0x02; the second byte plus data.display.shake_low_base is written to register 0x07.
data.display.shake_high_offsetdata 0x1365Offset added to the first byte of each screen-shake table pair before writing CRT controller register 0x02.
data.display.cga_color_mapAGIDATA.OVL:0x1d36Three-byte-per-color table used by the CGA graphics overlay color mapper. Mode 0 uses byte 0 duplicated; mode 1 uses bytes 1 and 2 as the returned word.
data.heap.current_top_0a55data 0x0a55Current top pointer for the interpreter’s bump heap. Seeded with data.heap.base_0a57 during startup memory allocation.
data.heap.base_0a57data 0x0a57Heap base used by allocation and heap-status display. Startup derives it from the DOS allocated segment relative to the interpreter data segment.
data.heap.room_reset_mark_0a59data 0x0a59Mark used by room switch, restart, and restore cleanup; code.heap.reset_dynamic_state rewinds the heap to this pointer.
data.heap.limit_0a5bdata 0x0a5bHeap limit pointer used by code.heap.allocate and code.heap.update_free_memory_var; startup sets it to data.heap.base_0a57 + requested_runtime_paragraphs * 16.
data.heap.temporary_mark_0a5ddata 0x0a5dOptional one-shot temporary heap mark saved by code.heap.save_temporary_mark and consumed by code.heap.restore_temporary_mark.
data.heap.high_water_0a5fdata 0x0a5fHighest current-top value observed after successful allocations; reported by code.heap.show_status_action.
data.control.priority_tabledata 0x127aRuntime 168-byte row-to-priority/control table.
data.display.logical_buffer_segmentglobal [0x136f]Segment of logical graphics/control buffer.
data.picture.current_payloadglobal [0x1377]Pointer to selected picture payload during decode.
data.picture.draw_stateglobal [0x1369]Active picture low/high nibble draw state.
data.picture.visual_draw_valueglobal [0x136b]Current low-nibble picture draw value.
data.picture.control_draw_valueglobal [0x136c]Current high-nibble picture draw value.
data.picture.odd_y_write_maskglobal [0x136d]Mask selected by code.display.pixel_write for odd Y rows.
data.picture.even_y_write_maskglobal [0x136e]Mask selected by code.display.pixel_write for even Y rows.
data.picture.pattern_bitsdata 0x15f9Bit masks used by patterned picture drawing.
data.picture.pattern_pointer_tabledata 0x1619Pattern pointer table selected by low three bits of picture pattern mode.
data.input.key_event_mapdata 0x0145Up to 39 four-byte key mapping slots populated by code.input.map_key_event; helper 0x4566 consults this table while normalizing events.
data.input.event_queue_basedata 0x11baStart of the 20-record circular raw event queue. Records are 4 bytes: type word, value word.
data.input.event_queue_writedata 0x120aWrite pointer used by code.input.enqueue_event; wraps to data.input.event_queue_base at the queue end.
data.input.event_queue_readdata 0x120cRead pointer used by code.input.dequeue_event; equal read/write pointers mean the queue is empty.
data.input.tracked_scan_enableddata 0x1519Byte table indexed by raw scan code minus 0x47; nonzero entries enable special keyboard IRQ release tracking for selected scan codes.
data.input.tracked_scan_pressed_latchdata 0x1524Byte table indexed like data.input.tracked_scan_enabled; press paths set one latch and clear the others, release paths clear the latch before optionally enqueueing a release event.
data.input.last_irq_scan_codedata 0x152fLast raw scan byte captured by code.input.keyboard_irq_hook before range/latch handling.
data.input.key_release_enqueue_gate_1530data 0x1530Byte gate incremented by action 0xad; nonzero lets code.input.keyboard_irq_hook enqueue a type-2 zero event on selected tracked-key release paths. The source model covers 8-bit wraparound from 0xff to zero.
data.input.menu_direction_event_mapdata 0x16b3Four-byte raw key/event to movement-code table consulted by helper 0x46b6 before type-2 events are enqueued. Observed SQ2 entries map 0x4800, 0x4900, 0x4d00, 0x5100, 0x5000, 0x4f00, 0x4b00, and 0x4700 to movement codes 1..8.
data.input.display_adapter_event_mapdata 0x16d7Four-byte remap table used when display adapter word [0x112e] == 2; observed entries map numeric keypad values 0x38, 0x39, 0x36, 0x33, 0x32, 0x31, 0x34, and 0x37 to movement values 1..8.
data.input.prompt_marker_chardata 0x05d7Prompt/input marker character configured by action 0x6c; code.input.show_prompt_marker and code.input.erase_prompt_marker use it when drawing or erasing the marker.
data.text.foreground_attribute_05cddata 0x05cdDerived foreground text attribute included in profile 2.936 save block 1.
data.text.background_attribute_05cfdata 0x05cfDerived background text attribute included in profile 2.936 save block 1.
data.text.packed_attribute_05d1data 0x05d1Packed current text/window attribute included in profile 2.936 save block 1.
data.text.status_line_enableddata 0x05d9Word flag controlled by actions 0x70 and 0x71; code.text.redraw_status_line only draws the status line when this is nonzero.
data.text.input_line_enableddata 0x05d3Word flag controlled by actions 0x77 and 0x78; input-line redraw/erase helpers test it before updating the visible input area.
data.text.input_row_05d5data 0x05d5Configured input-line text row.
data.save.reserved_text_padding_05d8data 0x05d8Reserved byte aligning the following word state; all 11 local saves contain the canonical zero value.
data.text.status_row_05dbdata 0x05dbConfigured status-line text row.
data.text.display_base_row_05dddata 0x05ddConfigured base row for the text display area.
data.text.display_bottom_row_05dfdata 0x05dfBottom row derived from the configured display base row.
data.text.attr_mode_enableddata 0x1757Byte flag set by action 0x6a and cleared by code.text.leave_attr_mode; text attribute derivation helpers branch on it.
data.text.attribute_stackdata 0x1759Five-entry stack of triples saved/restored by helpers 0x7989 and 0x79c3; count lives at [0x1777].
data.event.pair_capacityglobal [0x0141]Maximum number of two-byte resource/event pairs. Action 0x8e writes this value before resetting the pair buffer.
data.event.pair_countglobal [0x0143]Active count of event/resource pairs.
data.event.saved_pair_countglobal [0x05e1]Saved pair count slot used by actions 0xab and 0xac.
data.event.pair_buffer_baseglobal [0x1707]Base pointer for resource/event pair buffer.
data.event.pair_buffer_writeglobal [0x1709]Current write pointer for appends and replay end pointer.
data.event.pair_buffer_readglobal [0x170b]Replay read cursor used by code.event.next_replay_pair.
data.event.recording_enabledglobal [0x170d]Word gate checked by code.event.record_pair after flag 7.
data.event.pair_high_waterglobal [0x170f]Maximum observed pair count; heap/status display uses it and overflow error reporting passes it as context.
data.motion.direction_tabledata 0x0a85Nine-word direction lookup used by code.motion.compute_direction.

Gold Rush / AGI v3 Address Associations

These are the first non-SQ2 mappings for existing or newly introduced symbolic labels. They are loaded-image offsets in games/GR/AGI unless otherwise noted. The GR executable has the same 0x200-byte MZ header size as the decrypted SQ2 executable, so file offsets are image offsets plus 0x200.

LabelGR addressNotes/evidence
code.logic.action_dispatchimage 0x02bcSame structural role as SQ2 0x02c4, but the v3 max-action check accepts opcodes through 0xb5 and dispatches through a build-specific AGIDATA action table.
table.logic.action_dispatchGR AGIDATA.OVL:0x0440; KQ4D AGIDATA.OVL:0x0620Four-byte v3 action table. tools/disassemble_logic.py now detects this table by the observed argc/meta signature; GR and KQ4D both parse with their build-specific bases.
code.logic.condition_dispatchimage 0x0a31Same structural role as SQ2 0x07e3. The GR dispatcher compares predicate bytes with 0x26, but the observed structured table at AGIDATA.OVL:0x0762 only covers shared entries 0x00..0x12.
table.logic.condition_dispatchGR AGIDATA.OVL:0x0762; KQ4D AGIDATA.OVL:0x0942Four-byte v3 condition table for entries 0x00..0x12, matching SQ2 table 0x08fd by contract and normalized handler snippets. Bytes after entry 0x12 overlap string/data in GR and are not confirmed predicates. Gold Rush scripts observed so far only use conditions through 0x0e.
opcode.action.reserved_noop_v3_0table slot 0xb0, image 0x5286GR-only action slot. Zero operands, generic no-op/return handler.
opcode.action.set_menu_interaction_gatetable slot 0xb1, image 0x970bGR-only action slot. Reads one immediate byte and stores it in data.menu.interaction_gate_0403; QEMU report build/gr-v3-behavior/menu_gate_suite.json validates zero as blocked and nonzero as modal-menu entry.
opcode.action.reserved_noop_v3_2table slot 0xb2, image 0x5286GR-only action slot. Zero operands, generic no-op/return handler.
opcode.action.reserved_noop_v3_4argstable slot 0xb3, image 0x5286GR-only action slot. Four fixed operands are consumed by table-driven scanning, but the handler performs no state change.
opcode.action.reserved_noop_v3_2varargstable slot 0xb4, image 0x5286GR-only action slot. Two variable operands are declared with metadata 0xc0, but the handler performs no state change.
opcode.action.clear_key_release_event_gatetable slot 0xb5, image 0x63b0GR-only action slot. Stores zero in data.input.key_release_enqueue_gate_0405; GR action 0xad stores one in the same byte. Local model tools/agi_input.py covers the set/clear gate.
code.menu.interactimage 0x9724GR address association for the interactive menu path. Tests data.menu.interaction_gate_0403 before drawing/waiting; returns immediately while the gate is zero. QEMU menu_gate_suite confirms that only a nonzero gate lets a later 0xa1 request enter the modal menu.
code.input.keyboard_irq_hookimage 0x63b8GR keyboard interrupt hook. Tests data.input.key_release_enqueue_gate_0405 before enqueueing (type=2, value=0) on selected key-release paths. The tracked-key latch contract matches SQ2 after relocation.
code.input.set_line_configimage 0x7c24GR association for SQ2 action 0x6f. Stores the same input-line row/bounds roles at relocated globals and computes display offset [0x11b1] = arg0 << 3 without SQ2’s display-mode-2 branch.
code.input.edit_stringimage 0x0fb7GR association for SQ2 image 0x0da9; blocking string editor used by GR prompt actions 0x73 and 0x76.
code.input.handle_input_charimage 0x3961GR association for SQ2 image 0x3652; visible/source input buffer character helper.
code.input.erase_input_lineimage 0x3a29GR association for SQ2 action 0x8a. Loops on visible input length [0x0e19]; no SQ2 display-mode-2/input-width skip branch.
code.input.refresh_input_lineimage 0x3a48GR association for SQ2 action 0x89. Calls the relocated source-to-visible helper when input is enabled; no SQ2 alternate display-mode-2/input-width path.
code.input.append_source_to_visibleimage 0x3a5eGR association for SQ2 image 0x37a5; appends source input buffer bytes into the visible buffer.
code.input.show_prompt_markerimage 0x3ab0GR association for SQ2 image 0x37f7; draws prompt marker byte [0x03f7] when marker state [0x0dc3] is clear and the byte is nonzero.
code.input.erase_prompt_markerimage 0x3ad9GR association for SQ2 image 0x382e; clears the prompt-marker visible state and backspaces the marker when configured.
code.input.prompt_marker_visible_stateimage 0x3b00GR association for SQ2 helper 0x3863; returns prompt-marker visible word [0x0dc3].
code.input.set_prompt_marker_charimage 0x3b43GR association for SQ2 action 0x6c; stores the first byte of a resolved message in prompt-marker byte [0x03f7].
code.input.redraw_input_lineimage 0x3b66GR association for SQ2 image 0x38d7; redraws the configured input row, prompt marker, fixed prompt string, and visible input buffer.
code.input.set_width_flag_actionimage 0x5286GR maps SQ2 action 0xa3 to the generic no-op/return handler.
code.input.clear_width_flag_actionimage 0x5286GR maps SQ2 action 0xa4 to the generic no-op/return handler.
code.input.map_key_eventimage 0x4e98GR association for SQ2 action 0x79; same three-operand key/event mapping shape, but scans 0x31 four-byte slots instead of SQ2’s 0x27. QEMU probe build/gr-v3-behavior/key_map_capacity_qemu_pic001_002.json validates slot 48 with a typed-key positive case and no-key control.
code.text.close_window_stateimage 0x21a2GR association for SQ2 action 0xa9. Restores a saved rectangle when data.text.window_active_0b24 is nonzero and clears that active flag; it does not clear a GR input-width flag.
code.text.restore_saved_rectangleimage 0x582bGR association for SQ2 image 0x560c; rectangle restore helper consumed by code.text.close_window_state.
code.room.switch_room_actionimage 0x19d4GR association for SQ2 action 0x12; calls code.room.remap_reserved_room_target before the relocated room-switch helper 0x1a0a.
code.room.remap_reserved_room_targetimage 0x0062GR-only helper used by action 0x12: bytes 0x7e..0x80 return 0x49; all other target bytes pass through unchanged. QEMU probe build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json validates 0x7e, 0x7f, and 0x80 as aliases for 0x49 with matching nonblank captures.
code.inventory.show_selection_actionimage 0x351eGR association for SQ2 action 0x7c; relocated carried-item selector plus temporary word data.inventory.selection_event_gate_0dc1.
code.save.save_game_stateimage 0x29e5GR association for SQ2 action 0x7d; relocated five-block save writer with an XOR pass over the object/inventory chunk before and after save-file writes. QEMU report build/gr-v3-behavior/save_xor_extract_qemu_001.json validates a blank-prefix SG.1 extraction, and build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json validates 0x8f("GR") producing GRSG.1 with first-block prefix GR\0; both have third block length 1811 and round-trip through the modeled XOR transform.
code.save.restore_game_stateimage 0x2792GR association for SQ2 action 0x7e; relocated five-block restore reader. After loading the third block into [0x07d6], the success path calls code.save.object_inventory_xor_range over [0x07d6]..[0x07d6]+[0x07da], refreshes display/resource state, clears the caller return pointer, and returns through restored script state. QEMU report build/gr-v3-behavior/signed_restore_roundtrip_suite.json validates a signed GRSG.1 restore by matching the restored capture to a direct saved-state control and differing from an unrestored control.
code.save.object_inventory_xor_rangeimage 0x07bcGR helper called by save and object/inventory setup paths. XORs bytes in a caller-supplied range with repeating key bytes at 0x072c until a zero key byte.
data.save.object_inventory_block_start_07d6GR data [0x07d6]Pointer to the GR save block that is XOR-transformed before writing and transformed again before returning from action 0x7d.
data.save.object_inventory_block_length_07daGR data [0x07da]Length of the GR save block rooted at data.save.object_inventory_block_start_07d6; save action 0x7d writes this as the third length-prefixed state block.
data.save.object_inventory_xor_key_072cGR data 0x072cZero-terminated repeating key bytes used by code.save.object_inventory_xor_range; observed in AGIDATA.OVL as Avis Durgan. This is a data-segment address, not a main-code image offset.
code.restart.confirm_restart_actionimage 0x26e0GR association for SQ2 action 0x80; records prompt-marker visible state before confirmation, then redraws after accepted restart or after canceled restart only when the marker had been visible. QEMU report build/gr-v3-behavior/restart_prompt_marker_qemu_001.json confirms the canceled branch.
code.resource.load_all_directoriesimage 0x44dev3 combined-directory loader. Formats "%sdir", reads a whole combined directory, derives four section pointers from the first eight bytes, and falls back to separate logdir/picdir/viewdir/snddir loads if the combined open fails.
code.resource.dir_entry_or_nullimage 0x4599v3 shared absent-entry helper. Returns null only for exact ff ff ff, unlike SQ2’s high-nibble 0xf check.
code.resource.logic_dir_entryimage 0x45bcUses v3 logic directory base [0x0fda]; missing-resource string logic at AGIDATA.OVL:0x0f5e.
code.resource.view_dir_entryimage 0x45f0Uses v3 view directory base [0x0fdc]; missing-resource string view at AGIDATA.OVL:0x0f64.
code.resource.picture_dir_entryimage 0x4624Uses v3 picture directory base [0x0fde]; missing-resource string picture at AGIDATA.OVL:0x0f69.
code.resource.sound_dir_entryimage 0x4658Uses v3 sound directory base [0x0fe0]; missing-resource string sound at AGIDATA.OVL:0x0f71.
code.resource.open_volume_handlesimage 0x33c2Opens sixteen possible prefixed volume files with "%svol.%d", explaining observed GRVOL.9 through GRVOL.12 entries.
code.resource.close_volume_handlesimage 0x341cCloses the sixteen v3 handle slots and resets them to 0xffff.
code.resource.read_volume_payload_retryimage 0x30acv3 retry wrapper around code.resource.read_volume_payload_once.
code.resource.read_volume_payload_onceimage 0x30d0v3 generic reader. Decodes 7-byte record headers, allocates expanded length, and selects direct, dictionary, or picture-nibble payload paths.
code.resource.decompress_lzw_likeimage 0x07f4General v3 dictionary decompressor for compressed logic/view/sound records. Uses reset code 0x100, end code 0x101, and 9-to-11-bit codes.
code.resource.decompress_picture_nibbleimage 0x9a5bv3 picture transform that expands packed nibbles after 0xf0/0xf2 into ordinary byte operands and stops after emitting 0xff.
code.view.load_resourceimage 0x3c5bGR address association for SQ2 image 0x39f7; normalized static comparison matches after resource-reader relocation.
code.object.bind_viewimage 0x3d4bGR address association for SQ2 image 0x3ae7; normalized static comparison matches.
code.object.select_groupimage 0x3e1bGR address association for SQ2 image 0x3bb7; normalized static comparison matches.
code.object.select_group_tableimage 0x3e7fGR address association for SQ2 image 0x3c1b; normalized static comparison matches.
code.object.select_frameimage 0x3f2fGR address association for SQ2 image 0x3ccb; normalized static comparison matches.
code.view.discardimage 0x4131GR address association for SQ2 image 0x3ecd; normalized static comparison matches.
code.picture.load_resourceimage 0x4c96GR address association for SQ2 image 0x4a3b; normalized static comparison matches after resource-reader relocation.
code.picture.prepareimage 0x4d2aGR address association for SQ2 image 0x4acf; normalized static comparison matches.
code.picture.overlay_prepareimage 0x4d96GR address association for SQ2 image 0x4b3b; normalized static comparison matches.
code.picture.discardimage 0x4e29GR address association for SQ2 image 0x4bce; normalized static comparison matches.
code.picture.decode_no_clearimage 0x67c2GR address association for SQ2 image 0x6440; differs because GR returns after command scan where SQ2 checks display mode 2 and calls an overlay refresh path.
code.picture.command_scanimage 0x67edGR address association for SQ2 image 0x6475; normalized static comparison matches.
table.picture.command_dispatchAGIDATA.OVL:0x140dGR dispatch table for picture command bytes 0xf0..0xfa; corresponds to SQ2 data table 0x15d6.
code.picture.cmd_set_visual_draw_nibbleimage 0x680cGR address association for SQ2 image 0x6494; normalized static comparison matches.
code.picture.cmd_disable_visual_draw_nibbleimage 0x682dGR address association for SQ2 image 0x64b5; normalized static comparison matches.
code.picture.cmd_set_control_draw_nibbleimage 0x683fGR address association for SQ2 image 0x64c7; normalized static comparison matches.
code.picture.cmd_disable_control_draw_nibbleimage 0x6865GR address association for SQ2 image 0x64ed; normalized static comparison matches.
code.picture.cmd_draw_corner_path_y_firstimage 0x698aGR address association for SQ2 image 0x6612; normalized static comparison matches.
code.picture.cmd_draw_corner_path_x_firstimage 0x697bGR address association for SQ2 image 0x6603; normalized static comparison matches.
code.picture.cmd_draw_absolute_linesimage 0x69beGR address association for SQ2 image 0x6646; normalized static comparison matches.
code.picture.cmd_draw_relative_linesimage 0x69d6GR address association for SQ2 image 0x665e; normalized static comparison matches.
code.picture.cmd_seed_fillimage 0x6a23GR address association for SQ2 image 0x66ab; normalized static comparison matches.
code.picture.cmd_set_pattern_modeimage 0x689cGR address association for SQ2 image 0x6524; normalized static comparison matches.
code.picture.cmd_pattern_plotimage 0x6877GR address association for SQ2 image 0x64ff; normalized static comparison matches.
code.picture.read_coord_pairimage 0x6a30GR address association for SQ2 image 0x66b8; normalized static comparison matches.
code.picture.draw_lineimage 0x6a59GR address association for SQ2 image 0x66e1; normalized static comparison matches.
code.display.fill_buffer_wordimage 0x548aGR address association for SQ2 image 0x5257; the memory fill skeleton matches, but GR omits SQ2’s display-mode-2 overlay refresh call.
code.display.draw_horizontal_lineimage 0x5498GR address association for SQ2 image 0x526f; normalized static comparison matches.
code.display.draw_vertical_lineimage 0x54d4GR address association for SQ2 image 0x52ab; normalized static comparison matches.
code.display.pixel_writeimage 0x5522GR address association for SQ2 image 0x52f9; normalized static comparison matches.
code.picture.seed_fillimage 0x5564GR address association for SQ2 image 0x533b; normalized static comparison matches.
code.display.clear_logical_bufferimage 0x5751GR address association for SQ2 image 0x5528; normalized static comparison matches.
code.display.full_refreshimage 0x576fGR address association for SQ2 image 0x5546; GR omits SQ2’s display-mode-2 overlay refresh branch.
code.object.build_update_list_sortedimage 0x0351GR address association for SQ2 image 0x0358; normalized static comparison matches.
code.object.insert_update_node_headimage 0x0428GR address association for SQ2 image 0x042f; normalized static comparison matches.
code.object.draw_update_list_tail_to_headimage 0x0457GR address association for SQ2 image 0x045e; normalized static comparison matches, but GR calls main-image rectangle save/draw routines instead of SQ2 overlay entries.
code.object.refresh_update_list_saved_posimage 0x0481GR address association for SQ2 image 0x0488; normalized static comparison matches.
code.object.frame_timer_updateimage 0x055cGR address association for SQ2 image 0x0563; differs by sending exactly-four-loop views directly through the four-plus direction table, but gating views with more than four loops on flag 0x14. QEMU report build/gr-v3-behavior/frame_selection_gate_qemu_001.json validates exact-four view 177 selecting group 1 regardless of flag 0x14, and more-than-four view 39 selecting group 1 only when flag 0x14 is set.
code.object.advance_frame_by_modeimage 0x4b0eGR address association for SQ2 image 0x48b3; apparent straight-line differences are embedded jump-table bytes, while manually inspected branch bodies match as relocated skeletons.
code.object.collision_testimage 0x4974GR address association for SQ2 image 0x4719; normalized static comparison matches.
code.object.control_acceptanceimage 0x58e5GR address association for SQ2 image 0x56b8; normalized static comparison matches.
code.object.update_dirty_rectimage 0x598fGR address association for SQ2 image 0x5762; normalized static comparison matches.
code.object.placeimage 0x5cb3GR address association for SQ2 image 0x593a; normalized static comparison matches.
code.object.build_active_update_listimage 0x6d9eGR address association for SQ2 image 0x6a26; normalized static comparison matches.
code.object.build_inactive_partition_listimage 0x6db5GR address association for SQ2 image 0x6a3d; normalized static comparison matches.
code.object.flush_update_lists_restoreimage 0x6dccGR address association for SQ2 image 0x6a54; normalized static comparison matches.
code.object.rebuild_draw_update_listsimage 0x6e06GR address association for SQ2 image 0x6a8e; normalized static comparison matches.
code.object.refresh_update_listsimage 0x6e23GR address association for SQ2 image 0x6aab; normalized static comparison matches.
code.object.clear_root_16ff_membershipimage 0x6ebcGR address association for SQ2 image 0x6b44; normalized static comparison matches.
code.object.set_root_16ff_membershipimage 0x6edaGR address association for SQ2 image 0x6b62; normalized static comparison matches.
code.motion.update_objectsimage 0x1720GR address association for SQ2 image 0x150a; normalized static comparison matches.
code.motion.pre_mode_and_boundary_updateimage 0x0654GR address association for SQ2 image 0x0644; normalized static comparison matches.
code.motion.dispatch_mode_stepimage 0x068aGR address association for SQ2 image 0x067a; GR accepts motion mode 4 and dispatches it to the same target-direction helper as mode 3 before falling through to the same boundary-check tail. Instrumented QEMU report build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.json confirms this internal branch by patching a copied action-0x51 setup byte from mode 3 to mode 4.
code.motion.rectangle_boundary_checkimage 0x06ebGR address association for SQ2 image 0x06d9; apparent straight-line differences are embedded direction jump-table bytes, while manually inspected post-table body matches as a relocated skeleton.
code.motion.seed_first_object_mode4_targetimage 0x1975GR helper-shaped code that writes object 0 byte +0x22 = 4, seeds target X/Y bytes +0x27/+0x28, and preserves the current step in +0x29 when data.motion.direction_mirror_selector_0139 is nonzero. A direct near-call scan found no ordinary call into this address in the local GR main image, so the natural entry path remains unresolved/source-only.
code.object.save_rect_overlay_entryimage 0x5b67GR packages the rectangle-save routine in the main executable image; SQ2 reaches this role through IBM_OBJS.OVL:0x9db0.
code.object.restore_rect_overlay_entryimage 0x5ba6GR packages the rectangle-restore routine in the main executable image; SQ2 reaches this role through IBM_OBJS.OVL:0x9db3.
code.object.draw_overlay_entryimage 0x5be3GR packages the selected-frame draw routine in the main executable image; SQ2 reaches this role through IBM_OBJS.OVL:0x9db6.
data.resource.logic_dirGR data [0x0fda]Loaded v3 logic directory section pointer.
data.resource.view_dirGR data [0x0fdc]Loaded v3 view directory section pointer.
data.resource.picture_dirGR data [0x0fde]Loaded v3 picture directory section pointer.
data.resource.sound_dirGR data [0x0fe0]Loaded v3 sound directory section pointer.
data.menu.interaction_gate_0403GR data [0x0403]Word written by GR-only action 0xb1; code.menu.interact returns immediately while this word is zero. QEMU menu_gate_suite validates zero and nonzero observable branches.
data.input.key_release_enqueue_gate_0405GR data [0x0405]Byte set by GR action 0xad, cleared by GR-only action 0xb5, and tested by code.input.keyboard_irq_hook before selected key-release event enqueue. Local model tools/agi_input.py covers this gate against the shared latch state machine.
data.input.prompt_marker_charGR data [0x03f7]Relocated prompt-marker byte used by GR input-line redraw helpers.
data.input.visible_bufferGR data [0x0dc5]Relocated visible input buffer used by GR code.input.handle_input_char and refresh helpers.
data.input.source_bufferGR data [0x0def]Relocated accepted/source input buffer used by GR refresh and parser paths.
data.input.visible_lengthGR data [0x0e19]Relocated visible input length tested by GR input erase/append helpers.
data.input.display_offsetGR data [0x11b1]Relocated display offset written by GR action 0x6f; computed as arg0 << 3.
data.text.input_line_enabledGR data [0x03f3]Relocated input-line enabled word controlled by GR actions 0x77 and 0x78.
data.text.window_active_0b24GR data [0x0b24]Relocated active text-window state consumed and cleared by GR code.text.close_window_state.
data.text.window_saved_lower_right_0b2aGR data [0x0b2a]Relocated packed saved-window rectangle word passed to GR rectangle restore helper.
data.text.window_saved_upper_left_0b2cGR data [0x0b2c]Relocated packed saved-window rectangle word passed to GR rectangle restore helper.
data.inventory.selection_event_gate_0dc1GR data [0x0dc1]Temporary word set while GR’s interactive carried-item selector waits/handles events and cleared before action 0x7c returns.

KQ4 full game / AGI 3.002.086 Address Associations

These loaded-image offsets come from games/KQ4/AGI. They identify the full game’s earlier 3.002.086 build separately from the KQ4D demo.

LabelKQ4 addressNotes/evidence
code.logic.action_dispatchimage 0x02c4Accepts action bytes through 0xb1; later bytes are invalid action opcodes in this build.
code.logic.load_recordimage 0x13d9Loads a logic resource and conditionally applies the repeating-key XOR to its message region when data.resource.direct_record_flag_0f5e is nonzero.
code.resource.read_recordimage 0x311bReads a v3 record. The direct branch sets data.resource.direct_record_flag_0f5e; dictionary expansion clears it.
data.resource.direct_record_flag_0f5edata [0x0f5e]Nonzero after a direct resource read and zero after dictionary expansion. Logic setup uses it to distinguish encoded direct-record messages from plain expanded messages.
table.logic.action_dispatchAGIDATA.OVL:0x061d178 four-byte records for actions 0x00..0xb1; the count follows from the fixed v3 trailer before the condition table.
table.logic.condition_dispatchAGIDATA.OVL:0x092fNineteen common condition records.
opcode.action.increment_key_release_event_gateaction 0xad, image 0x6468Increments byte [0x15a0], unlike KQ4D’s set-to-one variant.
opcode.action.reserved_noop_v3_0action 0xb0, image 0x5467Consumes one ignored operand and otherwise has no effect.
opcode.action.set_menu_interaction_gateaction 0xb1, image 0x9812Stores its immediate byte as a word in [0x1d9e].
data.input.key_release_enqueue_gate_15a0data [0x15a0]Incremented by action 0xad; outside KQ4’s serialized block 1.
data.menu.interaction_gate_1d9edata [0x1d9e]Written by action 0xb1; outside KQ4’s serialized block 1.
code.save.save_game_stateimage 0x2a30Writes the five-block envelope with first-block length 0x05e1 and XOR-transforms block 3 around the write.
code.save.restore_game_stateimage 0x27ddReads the same five block roles and decodes the restored block-3 range.
code.save.object_inventory_xor_rangeimage 0x07adRepeats the zero-terminated key at data 0x08f9 over a caller-supplied range.
data.save.object_inventory_xor_key_08f9data 0x08f9Zero-terminated Avis Durgan key.
code.view.load_resourceimage 0x3d94Relocation match for KQ4D 0x3daa.
code.object.bind_viewimage 0x3e84Relocation match for KQ4D 0x3e9a.
code.object.select_groupimage 0x3f54Relocation match for KQ4D 0x3f6a.
code.object.select_group_tableimage 0x3fb8Relocation match for KQ4D 0x3fce.
code.object.select_frameimage 0x4068Relocation match for KQ4D 0x407e.
code.view.discardimage 0x426aRelocation match for KQ4D 0x4280.
code.picture.load_resourceimage 0x4e69Relocation match for KQ4D 0x4e7f.
code.picture.prepareimage 0x4efdRelocation match for KQ4D 0x4f13.
code.picture.overlay_prepareimage 0x4f69Relocation match for KQ4D 0x4f7f.
code.picture.discardimage 0x4ffcRelocation match for KQ4D 0x5012.
code.picture.decode_no_clearimage 0x6879Primary full-EGA path matches KQ4D after relocation.
code.picture.command_scanimage 0x68aeAll picture command and raster branch bodies match KQ4D after relocation.
code.display.fill_buffer_wordimage 0x566bPrimary full-EGA path matches KQ4D after relocation.
code.display.draw_horizontal_lineimage 0x5683Relocation match for KQ4D 0x568b.
code.display.draw_vertical_lineimage 0x56bfRelocation match for KQ4D 0x56c7.
code.display.pixel_writeimage 0x570dRelocation match for KQ4D 0x5715.
code.picture.seed_fillimage 0x574fRelocation match for KQ4D 0x5757.
code.display.full_refreshimage 0x595aPrimary full-EGA path matches KQ4D after relocation.
code.object.build_update_list_sortedimage 0x0358Same entry as KQ4D.
code.object.insert_update_node_headimage 0x042fSame entry as KQ4D.
code.object.draw_update_list_tail_to_headimage 0x045eSame entry as KQ4D.
code.object.refresh_update_list_saved_posimage 0x0488Same entry as KQ4D.
code.object.frame_timer_updateimage 0x0563Uses the four-direction loop table for loop counts of four or more, unlike KQ4D’s later greater-than-four gate.
code.object.collision_testimage 0x4b47Relocation match for KQ4D 0x4b5d.
code.object.advance_frame_by_modeimage 0x4ce1Branch bodies match KQ4D; normalized mismatch is embedded jump-table data.
code.object.control_acceptanceimage 0x5ad3Relocation match for KQ4D 0x5ae2.
code.object.update_dirty_rectimage 0x5b7dRelocation match for KQ4D 0x5b8c.
code.object.placeimage 0x5d55Relocation match for KQ4D 0x5d64.
code.object.build_active_update_listimage 0x6e5fRelocation match for KQ4D 0x6e77.
code.object.build_inactive_partition_listimage 0x6e76Relocation match for KQ4D 0x6e8e.
code.object.flush_update_lists_restoreimage 0x6e8dRelocation match for KQ4D 0x6ea5.
code.object.rebuild_draw_update_listsimage 0x6ec7Relocation match for KQ4D 0x6edf.
code.object.refresh_update_listsimage 0x6ee4Relocation match for KQ4D 0x6efc.
code.object.clear_root_membershipimage 0x6f7dRelocation match for KQ4D 0x6f95.
code.object.set_root_membershipimage 0x6f9bRelocation match for KQ4D 0x6fb3.
code.motion.update_objectsimage 0x1751Exact proposed X zero enters the clamp/report-left-edge branch; KQ4D accepts zero without a report.
code.motion.pre_mode_and_boundary_updateimage 0x0644Relocation match for KQ4D 0x065b.
code.motion.dispatch_mode_stepimage 0x067aAccepts modes 1 through 4; branch bodies match KQ4D.
code.motion.rectangle_boundary_checkimage 0x06dbBranch bodies match KQ4D; normalized mismatch is embedded direction-table data.

KQ4D demo / AGI 3.002.102 Address Associations

These loaded-image offsets come from games/KQ4D/AGI. The generic table comparison reports preserve build-specific addresses while relating them to the same role labels used for SQ2 and GR.

LabelKQ4D addressNotes/evidence
code.room.switch_room_actionimage 0x1a1bReads the immediate operand and calls the ordinary room-switch helper directly; unlike GR it has no reserved-room remap call.
code.input.map_key_eventimage 0x5081Uses the shared key-map root and stops at 0x27 four-byte records, matching SQ2’s 39-entry capacity.
code.input.set_width_flag_actionimage 0x3cecAction 0xa3; stores one in KQ4D width word [0x0d58].
code.input.clear_width_flag_actionimage 0x3cfeAction 0xa4; clears KQ4D width word [0x0d58].
code.text.close_window_stateimage 0x21faRestores active window state, then clears both width word [0x0d58] and active-window word [0x0d66], matching SQ2’s two-state cleanup shape.
opcode.action.set_menu_interaction_gatetable slot 0xb1, image 0x9838Stores its immediate byte as a word in [0x05e3]; menu interaction at 0x9851 returns while zero.
opcode.action.clear_key_release_event_gatetable slot 0xb5, image 0x647fClears byte [0x05e5]; shared action 0xad at 0x6477 sets it to one.
code.save.save_game_stateimage 0x2a46Writes the five-block envelope, with first-block range DS:0x0002..0x05e5, and XORs block 3 before and after the write.
code.save.restore_game_stateimage 0x27f3Reads the same five block roles and XORs the restored block-3 range.
code.save.object_inventory_xor_rangeimage 0x07c3Same instruction skeleton as GR 0x07bc; repeats key bytes at data 0x090c across a caller-supplied range.
data.save.object_inventory_xor_key_090cdata 0x090cZero-terminated Avis Durgan key in KQ4D AGIDATA.OVL.
data.save.object_inventory_block_start_09b6data [0x09b6]KQ4D runtime inventory block pointer written after the decoded three-byte metadata header.
data.save.object_inventory_block_length_09badata [0x09ba]Decoded metadata file length minus three; written as block 3 length.
data.menu.interaction_gate_05e3data [0x05e3]KQ4D menu gate appended after the 2.936-shaped save block-1 state.
data.input.key_release_enqueue_gate_05e5data [0x05e5]KQ4D set/clear release gate and final byte of the 0x05e4-byte first save block.
code.view.load_resourceimage 0x3daaRelocation match for GR 0x3c5b.
code.object.bind_viewimage 0x3e9aRelocation match for GR 0x3d4b.
code.object.select_groupimage 0x3f6aRelocation match for GR 0x3e1b.
code.object.select_group_tableimage 0x3fceRelocation match for GR 0x3e7f.
code.object.select_frameimage 0x407eRelocation match for GR 0x3f2f.
code.view.discardimage 0x4280Relocation match for GR 0x4131.
code.picture.load_resourceimage 0x4e7fRelocation match for GR 0x4c96.
code.picture.prepareimage 0x4f13Relocation match for GR 0x4d2a.
code.picture.overlay_prepareimage 0x4f7fRelocation match for GR 0x4d96.
code.picture.discardimage 0x5012Relocation match for GR 0x4e29.
code.picture.decode_no_clearimage 0x6891Primary EGA path matches GR 0x67c2; KQ4D retains the older display-mode-2 refresh branch.
code.picture.command_scanimage 0x68c6Relocation match for GR 0x67ed.
code.picture.cmd_set_visual_draw_nibbleimage 0x68e5Relocation match for GR 0x680c.
code.picture.cmd_disable_visual_draw_nibbleimage 0x6906Relocation match for GR 0x682d.
code.picture.cmd_set_control_draw_nibbleimage 0x6918Relocation match for GR 0x683f.
code.picture.cmd_disable_control_draw_nibbleimage 0x693eRelocation match for GR 0x6865.
code.picture.cmd_pattern_plotimage 0x6950Relocation match for GR 0x6877.
code.picture.cmd_set_pattern_modeimage 0x6975Relocation match for GR 0x689c.
code.picture.cmd_draw_corner_path_x_firstimage 0x6a54Relocation match for GR 0x697b.
code.picture.cmd_draw_corner_path_y_firstimage 0x6a63Relocation match for GR 0x698a.
code.picture.cmd_draw_absolute_linesimage 0x6a97Relocation match for GR 0x69be.
code.picture.cmd_draw_relative_linesimage 0x6aafRelocation match for GR 0x69d6.
code.picture.cmd_seed_fillimage 0x6afcRelocation match for GR 0x6a23.
code.picture.read_coord_pairimage 0x6b09Relocation match for GR 0x6a30.
code.picture.draw_lineimage 0x6b32Relocation match for GR 0x6a59.
code.display.fill_buffer_wordimage 0x5673Primary EGA path matches GR 0x548a; KQ4D retains the older display-mode-2 refresh branch.
code.display.draw_horizontal_lineimage 0x568bRelocation match for GR 0x5498.
code.display.draw_vertical_lineimage 0x56c7Relocation match for GR 0x54d4.
code.display.pixel_writeimage 0x5715Relocation match for GR 0x5522.
code.picture.seed_fillimage 0x5757Relocation match for GR 0x5564.
code.display.full_refreshimage 0x5962Primary EGA path matches GR 0x576f; KQ4D retains the older display-mode-2 refresh branch.
code.object.build_update_list_sortedimage 0x0358Relocation match for GR 0x0351.
code.object.insert_update_node_headimage 0x042fRelocation match for GR 0x0428.
code.object.draw_update_list_tail_to_headimage 0x045eRelocation match for GR 0x0457.
code.object.refresh_update_list_saved_posimage 0x0488Relocation match for GR 0x0481.
code.object.frame_timer_updateimage 0x0563Relocation match for GR 0x055c.
code.object.collision_testimage 0x4b5dRelocation match for GR 0x4974.
code.object.advance_frame_by_modeimage 0x4cf7Branch bodies match GR 0x4b0e; the normalized difference is embedded jump-table data.
code.object.control_acceptanceimage 0x5ae2Relocation match for GR 0x58e5.
code.object.update_dirty_rectimage 0x5b8cRelocation match for GR 0x598f.
code.object.placeimage 0x5d64Relocation match for GR 0x5cb3.
code.object.build_active_update_listimage 0x6e77Relocation match for GR 0x6d9e.
code.object.build_inactive_partition_listimage 0x6e8eRelocation match for GR 0x6db5.
code.object.flush_update_lists_restoreimage 0x6ea5Relocation match for GR 0x6dcc.
code.object.rebuild_draw_update_listsimage 0x6edfRelocation match for GR 0x6e06.
code.object.refresh_update_listsimage 0x6efcRelocation match for GR 0x6e23.
code.object.clear_root_16ff_membershipimage 0x6f95Relocation match for GR 0x6ebc.
code.object.set_root_16ff_membershipimage 0x6fb3Relocation match for GR 0x6eda.
code.motion.update_objectsimage 0x1767Relocation match for GR 0x1720, including collision, control acceptance, placement, and edge reporting.
code.motion.pre_mode_and_boundary_updateimage 0x065bRelocation match for GR 0x0654.
code.motion.dispatch_mode_stepimage 0x0691Branch bodies match GR 0x068a; the normalized difference is embedded jump-table data.
code.motion.rectangle_boundary_checkimage 0x06f2Branch bodies and post-table state changes match GR 0x06eb; the normalized difference is embedded direction-table data.

KQ2 / 2.411 and LSL1 / 2.440 Address Associations

KQ2 addresses refer to build/cross-version/kq2_agi.decrypted.exe; LSL1 addresses refer to the complete MZ image in games/LSL1/LL.COM.

LabelKQ2 / LSL1 addressNotes/evidence
code.logic.action_dispatchimage 0x0291 / 0x0291Both accept actions through 0xa9.
table.logic.action_dispatchdata 0x061b / 0x061b170 four-byte records for 0x00..0xa9.
table.logic.condition_dispatchdata 0x08e3 / 0x08e3Nineteen common condition records.
code.system.confirm_restart_actionimage 0x241f / 0x2435KQ2 always prompts; LSL1 tests f16 before prompting.
code.text.configured_message_action_helperimage 0x1c43 / 0x1c59Reads row, column, and width after the wrapper consumes the message selector.
code.system.heap_diagnostic_actionimage 0x1474 / 0x148aFormats the early three-line diagnostic without the later rm.0, etc. line.
code.save.save_game_stateimage 0x2693 / 0x26b9Writes five blocks with first-block length 0x05df.
code.save.restore_game_stateimage 0x24af / 0x24d5Reads the same early block grammar.
code.resource.load_all_directoriesimage 0x41cf / 0x4201Normalized relocation match.
code.resource.read_volume_payload_onceimage 0x2d60 / 0x2d86Normalized relocation match for direct v2 records.
code.view.load_resourceimage 0x38f5 / 0x3927Normalized relocation match.
code.object.bind_viewimage 0x39e5 / 0x3a17Normalized relocation match.
code.object.select_groupimage 0x3ab5 / 0x3ae7Normalized relocation match.
code.object.select_frameimage 0x3bc9 / 0x3bfbNormalized relocation match.
code.picture.load_resourceimage 0x4905 / 0x4937Normalized relocation match.
code.picture.prepareimage 0x4999 / 0x49cbNormalized relocation match.
code.picture.overlay_prepareimage 0x4a05 / 0x4a37Normalized relocation match.
code.picture.decode_no_clearimage 0x61a3 / 0x61d5Normalized relocation match.
code.picture.command_scanimage 0x61d8 / 0x620aDispatches 0xf0..0xfa through data tables 0x1564 / 0x1552.
code.picture.cmd_set_pattern_modeimage 0x61f5 / 0x62b9KQ2 consumes and ignores the mode; LSL1 stores it.
code.picture.cmd_pattern_plotimage 0x625e / 0x6294KQ2 plots one pixel per pair; LSL1 implements shaped/stippled patterns.
code.picture.read_coord_pairimage 0x6320 / 0x644dNormalized relocation match.
code.picture.draw_lineimage 0x6351 / 0x6476Normalized relocation match.
code.picture.seed_fillimage 0x513d / 0x516fNormalized relocation match.
code.display.full_refreshimage 0x5348 / 0x537aNormalized relocation match.
code.object.frame_timer_updateimage 0x0530 / 0x0530Both apply the four-direction loop table only at exactly four loops.
code.object.advance_frame_by_modeimage 0x477d / 0x47afBranch bodies match; normalized difference is embedded jump-table data.
code.object.collision_testimage 0x45e3 / 0x4615Normalized relocation match.
code.object.control_acceptanceimage 0x549f / 0x54d1Normalized relocation match.
code.object.placeimage 0x5721 / 0x5753Normalized relocation match.
code.object.build_active_update_listimage 0x6696 / 0x67bbNormalized relocation match.
code.object.refresh_update_listsimage 0x671b / 0x6840Normalized relocation match.
code.motion.update_objectsimage 0x14b9 / 0x14cfNormalized relocation match.
code.motion.dispatch_mode_stepimage 0x0647 / 0x0647Both accept modes 1 through 3.
code.motion.rectangle_boundary_checkimage 0x06a6 / 0x06a6Same branch bodies and post-table state.
code.sound.driver_startimage 0x7bc9 / 0x7cecInitializes streams, countdowns, and active words without envelope state.
code.sound.driver_tickimage 0x7c2b / 0x7d4eShared event/countdown scheduler.
code.sound.driver_stop_coreimage 0x7cca / 0x7dedShared early silence/completion core.
code.sound.driver_write_eventimage 0x7ce9 / 0x7e0cKQ2 always writes the low tone byte; LSL1 conditionally suppresses it. Both emit direct event attenuation without envelopes.
code.sound.timer_irq_hookimage 0x809a / 0x81c3Calls the relocated early sound tick while playback is active.

KQ1 / AGI 2.917 Address Associations

These loaded-image offsets come from the KQ1-key-decoded image at build/cross-version/kq1_agi.decrypted.exe. The complete normalized role pass is recorded in the evidence notes; this table preserves its principal anchors and the one observable renderer/object delta.

LabelKQ1 addressNotes/evidence
code.logic.action_dispatchimage 0x02c4Accepts action bytes through 0xad; otherwise matches SQ2’s dispatcher skeleton.
table.logic.action_dispatchAGIDATA.OVL:0x061d174 four-byte records for 0x00..0xad.
table.logic.condition_dispatchAGIDATA.OVL:0x08f5Nineteen common condition records.
code.resource.load_all_directoriesimage 0x4305Normalized match for SQ2 0x4305.
code.resource.read_volume_payload_onceimage 0x2e56Normalized match for SQ2 0x2e56.
code.view.load_resourceimage 0x39f7Normalized match for SQ2 0x39f7.
code.object.bind_viewimage 0x3ae7Normalized match for SQ2 0x3ae7.
code.object.select_groupimage 0x3bb7Normalized match for SQ2 0x3bb7.
code.object.select_group_tableimage 0x3c1bNormalized match for SQ2 0x3c1b.
code.object.select_frameimage 0x3ccbNormalized match for SQ2 0x3ccb.
code.view.discardimage 0x3ecdNormalized match for SQ2 0x3ecd.
code.picture.load_resourceimage 0x4a3bNormalized match for SQ2 0x4a3b.
code.picture.prepareimage 0x4acfNormalized match for SQ2 0x4acf.
code.picture.overlay_prepareimage 0x4b3bNormalized match for SQ2 0x4b3b.
code.picture.discardimage 0x4bceNormalized match for SQ2 0x4bce.
code.picture.decode_no_clearimage 0x6379Relocation match for SQ2 0x6440.
code.picture.command_scanimage 0x63aeRelocation match for SQ2 0x6475; all command handlers share the same displacement band.
code.display.fill_buffer_wordimage 0x518fRelocation match for SQ2 0x5257.
code.display.pixel_writeimage 0x5231Relocation match for SQ2 0x52f9.
code.picture.seed_fillimage 0x5273Relocation match for SQ2 0x533b.
code.display.full_refreshimage 0x547eRelocation match for SQ2 0x5546.
code.object.frame_timer_updateimage 0x0563Differs from SQ2 only at the loop-count-4 branch: equality in 2.917 versus four-or-more in 2.936.
code.object.advance_frame_by_modeimage 0x48b3Normalized match for SQ2 0x48b3.
code.object.collision_testimage 0x4719Normalized match for SQ2 0x4719.
code.object.control_acceptanceimage 0x55f0Relocation match for SQ2 0x56b8.
code.object.update_dirty_rectimage 0x569aRelocation match for SQ2 0x5762.
code.object.placeimage 0x5872Relocation match for SQ2 0x593a.
code.object.build_active_update_listimage 0x695fRelocation match for SQ2 0x6a26; the remaining list helpers share this displacement band.
code.motion.update_objectsimage 0x150aNormalized match for SQ2 0x150a.
code.motion.pre_mode_and_boundary_updateimage 0x0644Normalized match for SQ2 0x0644.
code.motion.dispatch_mode_stepimage 0x067aNormalized match for SQ2 0x067a.
code.motion.rectangle_boundary_checkimage 0x06d9Normalized match for SQ2 0x06d9.
code.save.save_game_stateimage 0x2753Writes the common five-block envelope with first-block length 0x05e1.
code.save.restore_game_stateimage 0x2512Reads the same five block roles and rebuilds restored references.

Same-version SQ1.22, PQ1, and KQ3 Associations

SQ1.22/2.917 and PQ1/2.917 use the same loaded-image addresses as KQ1/2.917 for every mapped role above. Each loaded image differs only at three bytes in the expected-game signature literal. The SQ1.22 controller dynamically confirmed code.engine.main_cycle image 0x0150 with its repeated-cycle hook at 0x015b, code.input.edit_string image 0x0da9 with visible loop 0x0df2 and wait return 0x0df8, and code.text.display_string image 0x1ce8 with modal loop 0x1d1b and wait return 0x1d25. KQ3/2.936 likewise uses every existing SQ2/2.936 address; its loaded image differs from SQ2 at only two signature bytes. These exact same-address matches are stronger than relocation matches and require no new role labels.

Additional Build Associations

These associations preserve the new-build addresses without treating an address as part of the portable behavior.

LabelBuild associationNotes/evidence
table.logic.action_dispatchSQ1 2.089 data 0x03e7; XMAS.230 2.230 data 0x03e7; XMAS 2.272 data 0x0417Geometry yields 155, 155, and 161 action entries respectively.
table.logic.condition_dispatchSQ1 2.089 data 0x0679; XMAS.230 2.230 data 0x0673; XMAS 2.272 data 0x06bbNineteen condition entries in all three builds.
code.system.confirm_exit_actionSQ1/XMAS image 0x01e8SQ1 has no operand and exits unconditionally; XMAS consumes the later selector and confirmation branch.
code.system.confirm_exit_actionXMAS.230 image 0x01e8Same zero-operand unconditional exit as SQ1.
opcode.action.set_object_posSQ1 image 0x7141; XMAS.230 image 0x70fa; XMAS image 0x7196SQ1 erases drawn old state before snapshot update; both XMAS builds write current/snapshot coordinates directly.
opcode.action.set_object_pos_varSQ1 image 0x7199; XMAS.230 image 0x7137; XMAS image 0x71d3Variable-coordinate form of the same ordering difference.
code.view.select_loopXMAS.230 image 0x350dStores loop_header & 0x0f as cel count and applies packed loop orientation bits 0xc0.
code.view.mirror_loop_rowsXMAS.230 image 0x50afMirrors every cel row in the selected packed loop, iterating by the header low nibble.
opcode.action.get_object_resource_loop_countXMAS.230 image 0x36e2Action 0x31 masks the selected loop header with 0x0f before subtracting one.
code.menu.operand_advance_stubsXMAS image 0x8400..0x8404Actions 0x9c..0xa0 consume declared operands and otherwise return without menu state.
code.picture.decode_with_clearSQ1 image 0x5b84; XMAS.230 image 0x5b7f; XMAS image 0x5c0dEarly decoders clear state before entering relocated command scanners.
code.picture.command_scanSQ1 image 0x5baf; XMAS.230 image 0x5baa; XMAS image 0x5c38All dispatch only picture commands 0xf0..0xf8.
code.picture.read_coord_pairSQ1 image 0x5ce8; XMAS.230 image 0x5ce3; XMAS image 0x5d71Relocated guarded coordinate reader.
code.picture.draw_lineSQ1 image 0x5d19; XMAS.230 image 0x5d14; XMAS image 0x5da2Relocated line rasterizer.
code.picture.seed_fillSQ1 image 0x4ad4; XMAS.230 image 0x4a3a; XMAS image 0x4aa6Relocated four-connected fill path.
code.object.frame_timer_updateSQ1 image 0x05cc; XMAS.230 image 0x049a; XMAS image 0x04c6Automatic direction-loop selection has no cadence-countdown gate.
code.object.rebuild_draw_update_listsSQ1 image 0x0524; XMAS image 0x6151SQ1 draws the 0x0010-clear partition in object-table order and then selection-sorts the set partition; XMAS builds and selection-sorts both partitions before drawing them in the same partition order.
code.object.flush_update_lists_restoreSQ1 image 0x0515; XMAS image 0x6117Restores prior rectangles for both builds using their respective array/list representations before composition.
code.object.refresh_update_listsSQ1 image 0x0533; XMAS image 0x616eUpdates dirty regions and saved positions for both partitions after drawing.
code.object.flush_update_lists_restoreXMAS.230 image 0x6081Restores both drawing-key-sorted partitions; relocated XMAS 2.272 role.
code.object.rebuild_draw_update_listsXMAS.230 image 0x60bbBuilds and sorts both partitions.
code.object.refresh_update_listsXMAS.230 image 0x60d8Refreshes both partitions after composition.
code.motion.update_objectsSQ1 image 0x13c7; XMAS image 0x13b7The movement body beginning at SQ1 0x13d2 / XMAS 0x13bf matches after relocation; SQ1 additionally brackets it with its older array-based restore/rebuild helpers.
code.motion.update_objectsXMAS.230 image 0x1317Relocated XMAS 2.272 movement core.
opcode.action.show_picture_likeSQ1 image 0x43e7; XMAS image 0x43ccBoth refresh and mark the picture shown without the later f15 clear/text-window close prefix.
opcode.action.show_picture_likeXMAS.230 image 0x436eSame retained-flag/window behavior.
opcode.action.object_distance_to_varSQ1 image 0x4061; XMAS image 0x4043Both store the low byte of the active-object distance without the later saturation to 0xfe.
opcode.action.object_distance_to_varXMAS.230 image 0x3fe5Same low-byte wrapping behavior.
opcode.action.clear_object_fields_21_22SQ1 image 0x652e; XMAS.230 image 0x64e7; XMAS image 0x657dSQ1 and XMAS.230 clear direction only; XMAS clears direction and autonomous-motion mode. All apply the object-0 direction-coupling side effects.
opcode.action.clear_object_field_22_and_globalSQ1 image 0x6567; XMAS.230 image 0x6520; XMAS image 0x65baSQ1 and XMAS.230 leave autonomous-motion mode untouched and change only object-0 coupling/navigation globals; XMAS clears autonomous-motion mode for every selected object.
opcode.action.move_object_toSQ1 image 0x6358; XMAS image 0x63a7Stores target state but does not invoke the target-direction/completion helper added by 2.411.
opcode.action.move_object_to_varSQ1 image 0x63ce; XMAS image 0x641dVariable-target form of the same deferred first-update rule.
opcode.action.move_object_toXMAS.230 image 0x6311Relocated deferred-start target-motion action.
opcode.action.move_object_to_varXMAS.230 image 0x6387Variable-target form.
opcode.action.show_inventory_selectionSQ1 image 0x2e89; XMAS image 0x2e87Early acknowledgement-only carried-item display; it has no interaction-flag branch and does not write v25.
opcode.action.show_inventory_selectionXMAS.230 image 0x2dcfSame acknowledgement-only behavior.
opcode.condition.input_word_sequenceSQ1 image 0x0965; XMAS.230 image 0x0851; XMAS image 0x087dSQ1 is exact-count only; both XMAS builds recognize the 0x270f tail terminator.
opcode.action.parse_stringSQ1 image 0x1817; XMAS.230 image 0x1759; XMAS image 0x17f7All accept six string-slot selectors.
code.sound.driver_startSQ1 image 0x74f5; XMAS.230 image 0x7478; XMAS image 0x7514Early one-versus-four-channel initialization.
code.sound.driver_tickSQ1 image 0x7557; XMAS.230 image 0x74da; XMAS image 0x7576Relocated event/countdown scheduler without envelopes.
code.sound.driver_write_eventSQ1 image 0x7615; XMAS image 0x7634Both apply the device-2 +3 helper; SQ1 then emits directly, while XMAS applies whole-byte adjustment and signed clamp.
code.sound.driver_write_eventXMAS.230 image 0x7598Exact relocated match for the SQ1 direct-output rule.
code.save.save_game_stateSQ1 image 0x2501; XMAS image 0x24f5SQ1 writes four blocks; XMAS writes five.
code.save.restore_game_stateSQ1 image 0x2335; XMAS image 0x2315Reads each build’s corresponding envelope.
code.save.save_game_stateXMAS.230 image 0x2447Relocated XMAS 2.272 five-block writer.
code.save.restore_game_stateXMAS.230 image 0x2279Relocated XMAS 2.272 five-block reader.
code.inventory.initialize_metadata_and_objectsXMAS.230 image 0x0e50Reads the plain OBJECT header, increments its third byte, and allocates that many 0x2b-byte runtime records; relocated XMAS 2.272 role at image 0x0e7c.
data.save.early_block1SQ1/XMAS.230/XMAS DS:0x0002..0x03dcComplete 2.089/2.230/2.272 global-state partition; block-relative fields are specified in the persistence chapter.
data.motion.rectangleSQ1/XMAS DS:0x0133..0x0141Bounds, direction-coupling, prepared-picture, coupled-direction, and enable state used by corresponding script/runtime paths.
data.replay.capacity_countSQ1/XMAS DS:0x0145 / DS:0x0147Configured pair capacity and active pair count; block 4 serializes capacity pairs.
code.object.initialize_metadataMG image 0x0fa5Decodes current OBJECT, increments header byte 0x5a, and allocates 91 records.
code.save.save_game_stateMG image 0x2751Current source-derived blocks begin 0x05e1, 0x0f49, 0x0005; bundled older save differs.
table.logic.action_dispatchBC 2.439 data 0x061b; MG 2.915 data 0x061d; replacement MH1 3.002.102 data 0x0620; MH2 3.002.149 data 0x0440Counts are 170, 174, 182, and 182. The prior MH1 3.002.107 copy used the same data offset.
table.logic.condition_dispatchBC data 0x08e3; MG data 0x08f5; MH1 data 0x0942; MH2 data 0x0762Nineteen entries in each build.
code.room.switch_room_actionMH2 image 0x19d4Reads the immediate room operand and calls ordinary room switch directly.
code.room.remap_reserved_room_targetGR image 0x0062; absent from MH2Gold Rush build helper mapping 0x7e..0x80 to 0x49; not a universal 3.002.149 role.

Cross-Version Picture Brush Associations

These associations were found structurally from each interpreter’s picture scanner and the referenced brush-table signature. They supersede use of SQ2’s data offsets as universal constants.

Build grouptable.picture.command_dispatchdata.picture.pattern_bitsdata.picture.pattern_pointer_tablecode.picture.cmd_set_pattern_modecode.picture.cmd_pattern_plot
BC 2.4390x15520x15750x1595image 0x62b1image 0x628c
LSL1 2.4400x15520x15750x1595image 0x62b9image 0x6294
MG 2.9150x15cc0x15ef0x160fimage 0x6449image 0x6424
KQ1/SQ1.22/PQ1 2.9170x15cc0x15ef0x160fimage 0x645dimage 0x6438
SQ2/KQ3 2.9360x15d60x15f90x1619image 0x6524image 0x64ff
KQ4 3.002.0860x16460x16690x1689image 0x695dimage 0x6938
KQ4D/replacement MH1 3.002.102; prior MH1 3.002.1070x16580x167b0x169bimage 0x6975image 0x6950
GR/MH2 3.002.1490x140d0x14300x1450image 0x689cimage 0x6877

SQ1 2.089, XMAS.230 2.230, and XMAS 2.272 dispatch only through 0xf8, so the pattern labels do not apply. KQ2 2.411 dispatches 0xf9 at image 0x61f5 and 0xfa at 0x625e, but has no shaped-brush tables: the former consumes and ignores one byte and the latter plots coordinate pairs as ordinary pixels.

Cross-Version Workflow

This project maps several local interpreter profiles. Additional AGI games or releases are new local inputs, not external documentation. The goal of a cross-version pass is to map the same symbolic labels to new addresses, record behavioral deltas, and run the compatibility suite against the new original engine when useful.

Evidence Package

For each new interpreter/game pair, preserve these local observations under a version-specific build/ subdirectory:

ArtifactPurpose
Decrypted executable imageStable bytes for disassembly and function matching.
Full disassemblySearchable baseline for routines, dispatch tables, and call sites.
Directory/resource censusCounts and edge cases for logic, picture, view, sound, words, and object data.
Symbolic label map deltaAddress associations from existing labels to the new build.
Compatibility reportLocal suite and selected QEMU smoke/broad results.

Start each broad comparison pass with the read-only census tool:

python3 -B tools/game_census.py --games-root games \
  --format markdown \
  --output build/cross-version/game_census.md

or pass selected inputs explicitly:

python3 -B tools/game_census.py \
  --game-dir games/SQ2 \
  --game-dir games/GR \
  --format json \
  --output build/cross-version/selected_census.json

The tool requires explicit paths, treats game files as read-only evidence, detects split v2 and combined v3 resource layouts, records version strings from local interpreter data, counts directory entries and readable records, and keeps per-record errors in the generated report. Treat record errors as hypotheses for later source inspection, not as engine semantics.

Do not collapse these observations into the SQ2 notes without naming the source build. Addresses are build-specific; labels and behavior are the portable concepts.

Label Mapping

Start with labels in Symbolic Labels. For each label, look for structural anchors rather than exact byte offsets:

Label TypeMatching Anchors
Dispatch tablesOpcode count, entry stride, handler order, nearby table references.
Resource loadersDirectory accessor calls, cache-record layout, volume-reader call, event-record pair.
Parser helpersSeparator/ignored-character tables, WORDS.TOK lookup pattern, parsed ID/count globals.
Picture decoderCommand dispatch table, scanner loop, raw operand reads, coordinate-reader carry behavior.
Object/view pipeline43-byte object record accesses, view frame pointer selection, overlay entry jumps.
Save/restoreFive length-prefixed blocks, selector strings, filename-format helper, resource-event replay.
Sound driverChannel pointer table, countdown loop, port-output helpers, completion flag path.

Use the normalized role matcher for first-pass relocation triage:

python3 -B tools/match_interpreter_roles.py \
  --reference-label SQ2-2.936 \
  --reference-game-dir games/SQ2 \
  --reference-exe build/cleanroom/SQ2_AGI.decrypted.exe \
  --target-label KQ3-2.936 \
  --target-game-dir games/KQ3 \
  --target-exe build/cross-version/kq3_agi.decrypted.exe \
  --sq2-subsystems \
  --output build/cross-version/kq3_sq2_role_matches.md

The matcher searches direct call/jump targets, dispatch-table handlers, same-address candidates, and explicit --candidate addresses. A unique exact normalized match is a relocation hypothesis. Multiple matches commonly mean a short shared helper; no match identifies a manual-review target. Neither result replaces source inspection, especially for indirect dispatch entries and embedded jump tables.

When a routine matches, add the new address to the working notes and keep the symbolic label unchanged. When behavior differs, record the difference as a version-specific observation before deciding whether the portable spec needs a variant rule.

  1. Decrypt or otherwise prepare the executable image using only local tooling.
  2. Generate a whole-image disassembly and identify the action/condition dispatch tables.
  3. Map core labels first: main cycle, logic interpreter, resource loaders, heap helpers, room switch, picture scanner, object overlay, parser, save selector, and sound tick.
  4. Run static resource parsers over the new game data and record counts, missing resources, invalid directory entries, and unusually large payloads.
  5. Run python3 -B tools/compatibility_suite.py --dry-run --include-qemu-smoke and decide which smoke batches apply without modification.
  6. Add version-specific fixture support only after the static label map explains the relevant routine.
  7. Run QEMU smoke tests, then broad picture/view carousel sweeps if the fixture format and interpreter behavior match the SQ2 assumptions.
  8. Update PROGRESS.md, docs/src/symbolic_labels.md, and the clean-room notes with both matches and differences.

Compatibility Tiers

Use the suite runner as the common entry point:

python3 -B tools/compatibility_suite.py --dry-run
python3 -B tools/compatibility_suite.py --dry-run --include-qemu-smoke
python3 -B tools/compatibility_suite.py --dry-run --include-qemu-broad

The default local tier should pass before any dynamic comparison. QEMU smoke tests are for behavior already explained by disassembly. Broad sweeps are for renderer/resource parity once loaders, paths, and fixture assumptions have been mapped for the new version.

Delta Recording

Each cross-version observation should answer four questions:

QuestionExample
Which symbolic label or subsystem changed?code.picture.command_scan moved and gained one dispatch entry.
What local evidence proves the change?Disassembly slice, table bytes, QEMU capture, or parser census.
Is this behavior observable for valid resources?Yes for a new picture opcode; no for an unreachable error string.
Does the portable spec need a variant?Add variant only when valid data can observe the difference.

If a difference can only be reached through malformed data that makes the original engine interpret unrelated memory, treat it as out of scope for the behavioral spec unless the user explicitly asks for security research.

Versions

This chapter tracks observed differences between local interpreter/game inputs. The goal is to keep version-specific facts separate from the portable AGI behavioral spec. Entries here are evidence from local files and local tools, not external AGI documentation.

Observed Inputs

Local inputVersion evidenceExecutable formResource containerFixture status
games/SQ1AGIDATA.OVL string Version 2.089SQ.EXE is an MZ executableSplit direct v2 resourcesFull-EGA gameplay profile promoted; selected save dimensions and semantic block map recorded
games/XMAS.230AGIDATA.OVL string Version 2.230AGI.EXE is an MZ executableSplit direct v2 resources; selected copy contains VOL.0 onlyFull-EGA gameplay profile promoted; packed loop-header mirroring and selected save dimensions recorded
games/XMASAGIDATA.OVL string Version 2.272AGI.EXE is an MZ executableSplit direct v2 resources in a multi-disk distribution layoutFull-EGA gameplay profile promoted; menu stubs, exit selector, and selected save layout recorded
games/BCAGIDATA.OVL string Version 2.439AGI is decoded with the selected game’s SIERRA.COM keySplit direct v2 resourcesMapped full-EGA core matches 2.440 after relocation; selected save dimensions observed
games/KQ2AGIDATA.OVL string Version 2.411AGI is decoded with the selected game’s SIERRA.COM key before disassemblySplit direct v2 resourcesFull-EGA resource, logic, input, persistence, renderer, object, and sound profile is promoted; KQ2 save dimensions are mapped
games/LSL1AGIDATA.OVL string Version 2.440LL.COM is already a complete MZ interpreter despite its extensionSplit direct v2 resourcesFull-EGA profile is promoted; LSL1 save dimensions are mapped
games/MGAGIDATA.OVL string Version 2.915AGI is an MZ executableSplit direct v2 resourcesMapped full-EGA core matches 2.917 after relocation; selected save dimensions observed
games/KQ1AGIDATA.OVL string Version 2.917AGI is decrypted with the selected game’s SIERRA.COM key before disassemblySplit LOGDIR, PICDIR, VIEWDIR, SNDDIR; direct VOL.N recordsFull-EGA resource, logic, input, persistence, renderer, and object profile is promoted; KQ1 save dimensions are mapped
games/SQ1.22AGIDATA.OVL string Version 2.917AGI is decoded with the selected game’s SIERRA.COM keySplit direct v2 resourcesLoaded interpreter differs from KQ1 at only three signature bytes; selected save dimensions observed
games/PQ1AGIDATA.OVL string Version 2.917AGI is already an MZ executableSplit direct v2 resourcesSame-version executable cross-check; PQ1 save dimensions are mapped
games/SQ2AGIDATA.OVL string Version 2.936AGI is decrypted from the loader-managed local bytes before disassemblySplit LOGDIR, PICDIR, VIEWDIR, SNDDIR; VOL.N files; 5-byte record headers; direct resource payloadsCurrent generated QEMU fixtures target this v2 split layout
games/KQ3AGIDATA.OVL string Version 2.936AGI is decoded with the selected game’s SIERRA.COM key before disassemblySplit direct v2 resourcesSame-version executable cross-check; KQ3 save dimensions are mapped
games/KQ4AGIDATA.OVL string Version 3.002.086AGI is an MZ executableCombined KQ4DIR; prefixed KQ4VOL.N files; v3 transformsFull-game full-EGA resource, logic, input, persistence, renderer, and object profile is promoted
games/KQ4DAGIDATA.OVL string Version 3.002.102AGI is an MZ executableCombined DMDIR; prefixed DMVOL.N files; v3 transformsFull-EGA resource, logic, input, persistence, renderer, and object profile is promoted
games/MH1AGIDATA.OVL string Version 3.002.102AGI is an MZ executableCombined MHDIR; prefixed MHVOL.N files; v3 transformsReplacement copy is resource-complete for valid gameplay; AGIDATA matches KQ4D byte-for-byte and AGI differs only in two signature bytes
games/GRAGIDATA.OVL string Version 3.002.149AGI is already an MZ executableCombined GRDIR; prefixed GRVOL.N files; 7-byte record headers; dictionary and picture-nibble transformsDecoding/parsing is implemented locally; generated fixtures can patch copied v3 directories/volumes with direct logic/view records and picture-nibble picture records under build/
games/MH2AGIDATA.OVL string Version 3.002.149AGI is an MZ executableCombined MH2DIR; prefixed MH2VOL.N files; v3 transformsSame-version control proves the Gold Rush room aliases are build-specific

Local census snapshot

The read-only census tool tools/game_census.py now provides a repeatable starting point for comparing the private local games without modifying them. The current command used was:

python3 -B tools/game_census.py --games-root games \
  --format markdown \
  --output build/cross-version/game_census.md

The local snapshot found these version/layout groups:

Local inputVersion stringLayoutNotes
games/SQ1Version 2.089v2 splitDirect records; 155 actions 0x00..0x9a.
games/XMAS.230Version 2.230v2 splitDirect records; 155 actions 0x00..0x9a; selected copy contains only VOL.0.
games/XMASVersion 2.272v2 splitOriginal multi-disk package records three disk numbers while selecting a single VOL.0; generic installed-layout record errors are packaging artifacts.
games/BCVersion 2.439v2 splitDirect records.
games/KQ2Version 2.411v2 splitDirect VOL.N records.
games/LSL1Version 2.440v2 splitDirect VOL.N records.
games/MGVersion 2.915v2 splitDirect records.
games/KQ1Version 2.917v2 splitDirect records for most resources; four sound entries currently fail the generic v2 header check and need source inspection before modeling.
games/SQ1.22Version 2.917v2 splitDirect records; same loaded interpreter core as KQ1.
games/PQ1Version 2.917v2 splitDirect VOL.N records.
games/KQ3Version 2.936v2 splitDirect VOL.N records.
games/SQ2Version 2.936v2 splitTwo known out-of-range end entries remain record errors in the generic census.
games/KQ4Version 3.002.086v3 combinedFull game; combined KQ4DIR/KQ4VOL.N. The selected copy lacks volumes 6 and 7, leaving pictures 150..151 and views 198..199 unreadable.
games/KQ4DVersion 3.002.102v3 combinedCombined DMDIR/DMVOL.N; dispatch tables are at AGIDATA offsets 0x0620/0x0942. Decoded scripts reference clean sounds 70..79; addressable tail anomalies are unreferenced, and triples beyond index 255 are inaccessible.
games/MH1Version 3.002.102v3 combinedReplacement copy: all 66 present logics, 237 pictures, and 138 views decode; all 99 script-addressable sounds decode. Two unreferenced sound-tail entries are inert anomalies.
games/GRVersion 3.002.149v3 combinedCombined GRDIR/GRVOL.N; all present records expand with the current v3 reader.
games/MH2Version 3.002.149v3 combinedReplacement copy: all 96 present logics, 248 pictures, and 181 views decode; all 193 script-addressable sounds decode. Two unreferenced sound-tail entries select absent volume 6.

This is an inventory for planning. The portable spec should only gain a version-specific rule when disassembly or a valid-resource dynamic check shows that game scripts can observe the behavior.

KQ2 / AGI 2.411 and LSL1 / AGI 2.440

KQ2’s transformed AGI was decoded with its local loader key:

python3 -B tools/decrypt_agi.py --game-dir games/KQ2 \
  --output build/cross-version/kq2_agi.decrypted.exe

LSL1 packages its complete 38 KiB MZ image as LL.COM; no separate AGI or SIERRA.COM is present. Both builds have action table AGIDATA.OVL:0x061b, condition table 0x08e3, and a geometry-derived count of 170 actions 0x00..0xa9 plus 19 conditions 0x00..0x12. Their dispatchers at image 0x0291 independently compare the action byte with 0xa9.

The table and subsystem reports are:

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ2-2.411 --left-game-dir games/KQ2 \
  --left-exe build/cross-version/kq2_agi.decrypted.exe \
  --right-label LSL1-2.440 --right-game-dir games/LSL1 \
  --right-exe games/LSL1/LL.COM \
  --output build/cross-version/kq2_lsl1_table_comparison.md

All 170 action table records and all 19 condition records match. All condition handlers match after normalization. The only action-handler difference is restart 0x80:

  • KQ2 image 0x241f always enters confirmation.
  • LSL1 image 0x2435 tests f16 and accepts restart without prompting while it is set, matching the later 2.917 behavior.

Both builds’ configured-message handlers physically consume message, row, column, and width. Their AGIDATA records for 0x97/0x98 report count 3, which initially caused the local linear disassembler to drift after those actions. Handler helpers KQ2 0x1c43 and LSL1 0x1c59 each read all three bytes following the already-consumed message selector. The disassembler now uses an effective width of four while preserving the raw table observation.

Compared with 2.917, both early builds omit actions 0xaa..0xad. Their screen shake lacks an alternate display-mode-4 branch outside the current target. Their heap diagnostic formats only heapsize, now/max, and max script; the later rm.0, etc. line is absent.

The 55-role KQ2/LSL1 renderer/object report covers resource and view loading, picture lifecycle, every command/raster helper, update lists, collision, placement, animation, and motion. The view, line, fill, composition, and motion paths match after relocation. Both builds use automatic direction-loop selection only for exactly four loops and accept object motion modes 1 through 3.

Picture-command differences are concentrated in the pattern commands:

  • KQ2 0xf9 at image 0x61f5 consumes its byte and returns without storing it. KQ2 0xfa at 0x625e repeatedly reads coordinate pairs and calls the ordinary pixel writer once per pair.
  • LSL1 0xf9 at 0x62b9 stores the mode, and its pattern implementation from 0x6294 through 0x6397 normalizes fully with the 2.917 implementation.
  • KQ2 uses byte operations for the low draw-state byte in 0xf0..0xf3, while LSL1 uses word operations. The upper byte is initialized but never read; raster helpers read only the low byte, so this is not a full-EGA output difference.

The selected KQ2 pictures do not use 0xf9 or 0xfa. Five selected LSL1 pictures use 0xfa but never use 0xf9, so they retain zero-radius single pixel plots. The source difference remains valid for other valid picture data.

Cross-version brush tables and edge rule

A later all-build audit distinguishes relocated addresses from behavioral changes. BC 2.439 through KQ3 2.936 share one complete v2 brush family. All observed 3.002 builds share a second family. Their eight column masks and every shape except radius 1 are identical. Radius 1 changes from three filled rows e000 e000 e000 in v2 to 4000 e000 4000 in v3. Because 4000 overlaps neither radius-one logical-column mask, normal masked output changes from a 2 by 3 block to two adjacent pixels on the center row.

The full v2 plotters clamp doubled X against 0x0140 - 2r; all observed v3 plotters use 0x013e - 2r. Thus the documented v2 linear X=160 write into the next row is not reachable from valid v3 pattern geometry. A fixed-size normalized disassembly comparison found this immediate to be the only code difference in the complete brush routines; mode bits, stipple evolution, iteration order, and pixel writes otherwise match.

BuildsDispatchMask tablePointer table
BC/LSL10x15520x15750x1595
MG/KQ1/SQ1.22/PQ10x15cc0x15ef0x160f
SQ2/KQ30x15d60x15f90x1619
KQ40x16460x16690x1689
KQ4D/MH10x16580x167b0x169b
GR/MH20x140d0x14300x1450

SQ1 2.089, XMAS.230 2.230, and XMAS 2.272 have no such tables because their scanners end at 0xf8. KQ2 2.411 has 0xf9/0xfa but no shaped-brush tables: its mode byte is ignored and each coordinate pair produces one ordinary pixel write. The generated all-build report is reproducible with tools/brush_table_audit.py and explicit --game-dir arguments.

The early sound drivers share event parsing and countdown scheduling but have no later attenuation-envelope arrays. Driver start initializes stream pointers, countdowns, and active words only. Event output adds the global attenuation adjustment directly to the control low nibble and clamps it to 0x0f; no attenuation output occurs on intervening countdown ticks. Selector zero uses one PC-speaker channel, while every nonzero selector uses four channels.

KQ2 tone helper 0x7ce9 always writes both non-PC tone bytes. LSL1 helper 0x7e0c writes the high byte and suppresses the low byte when the high byte’s top three bits are set. Their driver starts are 0x7bc9/0x7cec, ticks are 0x7c2b/0x7d4e, stop cores are 0x7cca/0x7ded, and timer hooks are 0x809a/0x81c3.

Both save writers use the common five-block grammar but write only 0x05e1 - 2 = 0x05df bytes from the block-1 base. Relative to 2.917, this is the common prefix through display-bottom-row state and omits the final two-byte saved replay checkpoint.

Selected gameBlock 1Block 2Block 3Block 4Block 5
KQ20x05df17 records, 0x02db85 items, 0x025660 pairs, 0x0078Variable
LSL10x05df17 records, 0x02db21 items, 0x0134144 pairs, 0x0120Variable

Two existing KQ2 saves and three LSL1 saves have exactly these first four block lengths. Their block-3 bytes are stored directly without a transform.

This evidence promotes separate 2.411 and 2.440 full-EGA profiles and selected-game binary-save dimensions.

KQ1 / AGI 2.917

KQ1’s AGI file uses the same loader-managed evolving-key transform previously observed for SQ2, but with the key bytes from KQ1’s own SIERRA.COM. The local transform command was:

python3 -B tools/decrypt_agi.py --game-dir games/KQ1 \
  --output build/cross-version/kq1_agi.decrypted.exe

Comparing the still-transformed file produced nonsense at every handler entry. The transformed-state report was discarded. The decoded MZ image yields a clean table comparison:

  • the action table starts at AGIDATA.OVL:0x061d and has 174 entries 0x00..0xad;
  • the condition table starts at 0x08f5 and has the common 19 entries;
  • every shared action/condition operand contract and normalized handler body matches 2.936; and
  • the action dispatcher at image 0x02c4 accepts through 0xad, while 2.936 accepts through 0xaf.

The v2 table count is derived from the fixed 0x20-byte data trailer between the action and condition tables. Across current local inputs this yields 170 actions for 2.411/2.440, 174 for 2.917, and 176 for 2.936.

A 52-role subsystem comparison found relocation matches for resource loading, views, picture lifecycle and command/raster helpers, update lists, placement, collision, control acceptance, dirty rectangles, motion, and cel-mode branch bodies. The one observable branch difference is in frame-timer helper 0x0563:

  • 2.917 applies the four-direction loop table only when loop count equals four;
  • 2.936 applies it when loop count is four or greater.

The selected KQ1 save writer uses the common five-block envelope and the same 0x05e1 block-1 partition as 2.936. Its decoded OBJECT metadata defines 18 object records and a 0x0148-byte inventory block containing 27 entries. Logic 0 calls action 0x8e(100), so replay block 4 remains 0x00c8 bytes. The resulting block dimensions are 0x05e1, 0x0306, 0x0148, 0x00c8, and a variable logic-resume block.

KQ1 sound directory entries 34 through 37 decode normally as packed v2 locations in VOL.2, but their offsets 0x20e2f, 0x20e8f, 0x20eed, and 0x2126b are all beyond the selected file’s 0x1630b-byte length. The 2.917 volume reader is a normalized exact match for the 2.936 direct-record reader and has no alternate sound-record interpretation. These are therefore stale or out-of-package directory locations, not evidence of another sound format. The script-visible reference audit finds no immediate logic reference to them, so they remain outside the promoted valid-data contract.

This evidence promotes a 2.917 full-EGA gameplay profile and KQ1-specific binary-save interchange dimensions.

Same-version cross-checks: PQ1 and KQ3

PQ1 supplies a second 2.917 build and KQ3 supplies a second 2.936 build. KQ3 was decoded with its local loader key; PQ1’s AGI is already an MZ image.

python3 -B tools/decrypt_agi.py --game-dir games/KQ3 \
  --output build/cross-version/kq3_agi.decrypted.exe

python3 -B tools/compare_interpreter_tables.py \
  --left-label PQ1-2.917 --left-game-dir games/PQ1 \
  --left-exe games/PQ1/AGI \
  --right-label KQ1-2.917 --right-game-dir games/KQ1 \
  --right-exe build/cross-version/kq1_agi.decrypted.exe \
  --output build/cross-version/pq1_kq1_table_comparison.md

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ3-2.936 --left-game-dir games/KQ3 \
  --left-exe build/cross-version/kq3_agi.decrypted.exe \
  --right-label SQ2-2.936 --right-game-dir games/SQ2 \
  --right-exe build/cleanroom/SQ2_AGI.decrypted.exe \
  --output build/cross-version/kq3_sq2_table_comparison.md

PQ1/KQ1 have identical 174 action records, action handlers, 19 condition records, and condition handlers. Their loaded images are both 38,912 bytes and differ at only three bytes in the embedded expected-game signature. Every one of the 55 renderer/object/resource roles matches at the same address.

KQ3/SQ2 likewise have identical 176 action and 19 condition contracts and handlers. Their loaded images are 38,912 bytes and differ at only two bytes, changing the embedded signature from S2 to K3. All subsystem roles remain at the same address and normalize identically.

The engines therefore provide strong same-version evidence for the promoted behavioral profiles. Their game-data save dimensions differ:

Selected gameProfileBlock 1Block 2Block 3Block 4Block 5
PQ12.9170x05e120 records, 0x035c25 items, 0x016e250 pairs, 0x01f4Variable
KQ32.9360x05e117 records, 0x02db55 items, 0x0307127 pairs, 0x00feVariable

Two structurally valid local PQ1 saves confirm the PQ1 first-four block lengths. A third file named SQSG.1 is truncated before block 4 and is not used as valid interchange evidence. KQ3 has no local save fixture; its dimensions come from decoded OBJECT metadata, the identical save writer, and logic 0 action 0x8e(127).

Script-visible resource reference audit

The helper tools/resource_reference_audit.py compares immediate resource numbers used by decoded logic bytecode against the selected game’s readable and unreadable directory entries. It is intentionally conservative: variable-based resource numbers are not counted as fixed script references.

The current focused command was:

python3 -B tools/resource_reference_audit.py \
  --game-dir games/KQ1 \
  --game-dir games/KQ4 \
  --game-dir games/KQ4D \
  --output build/cross-version/resource_reference_audit_profiles.json

Current result:

Local inputResource familyImmediate script referencesUnreadable directory entriesReferenced unreadable entries
games/KQ1sound21 entries, max 2134, 35, 36, 37 (all point beyond VOL.2)none
games/KQ4picturenone immediate; picture selection is variable-based150, 151 (missing KQ4VOL.6)none immediate
games/KQ4view241 entries, max 255198, 199 (missing KQ4VOL.7)none
games/KQ4Dsound10 entries, max 79221, 223..236; entry 198 aliases a picture recordnone

Conclusion: these unreadable sound-directory entries remain cross-version planning evidence, not valid-resource behavior for the clean spec. If a future game script computes those numbers through variables, or if another interpreter version accepts the same directory bytes through a source-mapped path, inspect that case directly before promoting a rule.

The KQ4 picture result needs the strongest caveat: its scripts select pictures through variables, so the absence of an immediate reference does not prove the missing-volume entries are unreachable. They are excluded from the valid-data profile because their selected volume files are absent.

Save reserved-state comparison

The SQ2 and Gold Rush block-1 layouts resolve the ranges that were previously recorded only as opaque bytes:

Serialized roleSQ2 / 2.936Gold Rush / 3.002.149Conclusion
Key-map tailTen zeroed four-byte records follow SQ2’s 39 active slots.The active capacity grows to 49 and consumes those ten records.Inactive profile capacity, not hidden runtime state.
Pre-string bytesFour zero bytes precede the common string root.The same four zero bytes remain after the expanded key map.Reserved serialization padding.
Post-string bankTwelve zeroed 40-byte records follow the 12 valid SQ2 slots.The bank is absent; text state immediately follows the 12 valid slots.Reserved legacy capacity, not 12 additional valid slots.
Standalone wordsCanonical 0000 and 0f00; no direct references in the complete code scan.Same serialized positions and canonical values; no direct references in the complete code scan.Reserved words preserved for save interchange.
Prompt/status gapOne canonical zero byte between byte and word state.The same one-byte boundary exists at the relocated text-state tail.Reserved alignment byte.

Valid string actions remain limited to the twelve specified string slots, and the key-map action’s source loop bounds define the profile capacities. Restoring and re-saving an existing file preserves supplied reserved bytes; a newly initialized state uses the canonical bytes above. No separate game-visible field is assigned to these ranges.

KQ4 full game / AGI 3.002.086

The newly selected games/KQ4 input is the full game, not the KQ4D demo. Its embedded version is 3.002.086, earlier than KQ4D’s 3.002.102. The combined directory prefix is KQ4, and the census finds 177 readable logic resources, 148 present/146 readable pictures, 243 present/241 readable views, and 96 readable sounds. Pictures 150–151 and views 198–199 refer to absent local volumes. Most present records use the v3 dictionary transform; three logic records and one sound record are direct.

Logic-message encoding follows the record transform. KQ4’s 174 dictionary- expanded logic records expose plain message bytes after expansion. Direct logic records 97, 100, and 131 retain repeating-key encoding. The executable’s resource reader sets [0x0f5e] only for a direct read and clears it after dictionary expansion; logic setup conditionally decrypts the message region when that word is nonzero. This differs from treating all v3 messages as uniformly plain or uniformly encrypted.

The first generic comparison falsely assigned KQ4 the later 182-action v3 table and interpreted trailer bytes as opcodes. The v3 table geometry has a fixed 0x4a-byte trailer between the action and condition tables. Computing (condition_base - action_base - 0x4a) / 4 gives 178 actions for KQ4 and 182 for KQ4D and Gold Rush. The generic detector now derives this count.

KQ4’s action table begins at AGIDATA.OVL:0x061d, contains entries 0x00..0xb1, and is followed by the condition table at 0x092f. Its action dispatcher accepts through 0xb1. All 178 shared action operand contracts and all 19 condition contracts match KQ4D. The only shared action-handler differences are:

OpcodeKQ4 3.002.086KQ4D 3.002.102
0xadIncrement the key-release gate byte modulo 256.Set the gate to one.
0xb0Consume one ignored operand and otherwise do nothing.Consume no operand and otherwise do nothing.

Action 0xb1 consumes one byte and stores it as the menu interaction gate. The later 0xb2..0xb5 slots do not exist in this profile. Compared with SQ2, KQ4 already has the v3 inventory-selector temporary state, block-3 save XOR, restart prompt-marker behavior, and motion-mode-4 preservation. It retains SQ2’s direct room destinations, input-width actions, 39-entry key map, and increment-style 0xad release gate.

A 52-role KQ4/KQ4D source comparison covers view loading and cel selection, picture lifecycle and every command/raster helper, object-list construction, composition, placement, collision, control acceptance, dirty rectangles, motion, and animation. All primary full-EGA roles match after relocation except two observable edge branches:

  • KQ4 applies automatic four-direction loop selection to every view with four or more loops, matching 2.936. KQ4D uses the later rule in which exactly four loops always qualify and larger counts require f20.
  • KQ4 clamps a due proposed object X coordinate of exactly zero to zero and reports left-boundary code 4. KQ4D accepts exact zero without a boundary report. Both clamp negative proposals to zero and report code 4.

The KQ4 save and restore paths use the common five-block envelope. Block 1 is the 2.936-shaped 0x05e1 bytes; KQ4’s menu and release gates lie outside that serialized range. The object/inventory block is XOR-transformed with the same observed repeating key as the later v3 profiles. Decoded full-game metadata and logic 0 establish these selected-game dimensions:

BlockFull KQ4 dimension
10x05e1 bytes
226 object records: 26 * 0x2b = 0x045e bytes
345 inventory entries and name pool: 0x02c6 bytes on decoding
4250 replay pairs: 0x01f4 bytes
5Variable common logic-resume grammar

This evidence promotes full KQ4/3.002.086 as a distinct full-EGA gameplay profile and defines binary-save interchange for the mapped full-game dimensions. It must not be conflated with the later KQ4D demo build.

KQ4D demo / AGI 3.002.102

The generic table comparator was run in both directions:

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ4D-3.002.102 --left-game-dir games/KQ4D \
  --left-exe games/KQ4D/AGI \
  --right-label GR-3.002.149 --right-game-dir games/GR \
  --right-exe games/GR/AGI \
  --output build/cross-version/kq4d_gr_table_comparison.md

python3 -B tools/compare_interpreter_tables.py \
  --left-label KQ4D-3.002.102 --left-game-dir games/KQ4D \
  --left-exe games/KQ4D/AGI \
  --right-label SQ2-2.936 --right-game-dir games/SQ2 \
  --right-exe build/cleanroom/SQ2_AGI.decrypted.exe \
  --output build/cross-version/kq4d_sq2_table_comparison.md

All 182 KQ4D/GR action parser contracts and all 19 condition contracts match. The condition handler entries also normalize identically. Twelve shared action handlers differ between KQ4D and GR:

12 6f 73 76 77 78 79 89 8a a3 a4 a9

Focused disassembly assigns these differences to three observable clusters:

ClusterKQ4D 3.002.102GR 3.002.149
Room action 0x12Passes the immediate room byte directly to the room-switch helper.Remaps 0x7e..0x80 to 0x49.
Input actions 0x6f, 0x73, 0x76..0x78, 0x89, 0x8a, 0xa3, 0xa4, 0xa9Retains the SQ2 display-mode/input-width branches; 0xa3/0xa4 set/clear the width word and 0xa9 clears it.Uses the normal EGA path, no-ops 0xa3/0xa4, and has no width word to clear.
Key map 0x79Stops after 0x27 (39) records.Stops after 0x31 (49) records.

Direct KQ4D/SQ2 comparison finds only five shared handler-entry differences: 0x7c, 0x7d, 0x80, 0x84, and 0xad. KQ4D shares GR’s inventory temporary-state branch, block-3 save XOR, restart prompt-marker branch, motion-mode-4 preservation, and set-style release gate. Its six v3-only slots 0xb0..0xb5 have the same contracts and normalized handlers as GR, including the menu interaction gate and release-gate clear action.

The KQ4D save writer serializes block 1 from the common start through data address 0x05e5, yielding length 0x05e4. This is exactly the 2.936 block-1 partition plus menu-gate word [0x05e3] and release-gate byte [0x05e5]. The intended decoded eight-byte inventory metadata is:

03 00 0f 03 00 00 3f 00

It defines one three-byte inventory entry, name pool ?\0, and maximum object index 15, therefore 16 object records. Demo logic 0 sets replay capacity to one. The source-derived save dimensions are consequently block 1 0x05e4, block 2 16 * 0x2b = 0x02b0, block 3 5, block 4 2, and a variable block 5.

The selected local OBJECT file already contains the intended decoded bytes, while the interpreter source applies the repeating Avis Durgan XOR to the file buffer. A copied-fixture QEMU experiment compared the local bytes unchanged with an XOR-encoded copy. Both reached the same intro frame after advancing the Sierra loader; the demo had not yet exercised inventory metadata there. This is recorded as a local packaging anomaly, not as valid dual-encoding behavior. The clean profile follows the source reader’s XOR-decoded valid-data contract.

The renderer/object source pass compared 52 symbolic roles with GR 3.002.149. Core helpers normalize identically after relocation; the few wrapper-level differences are classified below the table.

ClusterCompared rolesResult
View loading and selection6Resource load/discard, view bind, loop table, loop selection, and cel selection match.
Picture lifecycle4Load, prepare, overlay prepare, and discard match.
Picture scanner and commands15Decode completion, scanner, channel controls, pattern controls, both corner paths, absolute/relative lines, fill, coordinate reader, and line raster entry were compared.
Logical display and raster6Buffer fill, horizontal/vertical lines, pixel write, seed fill, and full refresh were compared.
Object lists and composition15Sorted-list construction, draw/refresh ordering, collision, control acceptance, dirty rectangles, placement, partitions, and list rebuild/refresh match.
Motion and animation6Frame timer, frame-mode bodies, object movement, pre-mode pass, mode dispatch bodies, and rectangle-boundary bodies match.

Three display wrappers retain KQ4D’s older display-mode-2 branches: picture decode completion, buffer fill, and full refresh. Their primary EGA paths match GR, and alternate display modes are outside the current conformance target. The two reported motion/frame differences occur inside embedded jump-table bytes; manual disassembly confirms that the selected branch bodies and their post-table state transitions are relocation matches.

This closes the independent renderer/object blocker. The clean 3.002.102 profile now supports a full-EGA valid-data gameplay claim, plus binary save interchange for the mapped KQ4D demo dimensions.

SQ2 / AGI 2.936

SQ2 remains the main behavioral evidence target for the current spec. The resource directory files are split by family. Directory entries are 3-byte packed volume/offset records, and absent resources are identified by a high volume nibble of 0xf.

The volume record header is:

12 34 volume length_lo length_hi payload...

The current generated original-engine fixtures patch writable copies of LOGDIR, PICDIR, VIEWDIR, and VOL.3 to point at synthetic logic, picture, and view records. These fixture builders copy the selected game input to a generated destination first; games/ is treated as read-only evidence.

Gold Rush / AGI 3.002.149

Gold Rush is the first observed AGI v3 input. It keeps the decoded resource families recognizable, but changes the container and resource-reader path.

The combined GRDIR header contains four little-endian section offsets:

SectionHeader wordOffsetPresent entries
logic+00x0008182
picture+20x02e7186
view+40x05c6247
sound+60x08c644

The v3 volume record header is:

12 34 metadata expanded_len_lo expanded_len_hi stored_len_lo stored_len_hi stored...

Observed transform selection:

Header conditionTransform
metadata bit 0x80 setpicture nibble expansion
expanded length equals stored lengthdirect read
expanded length differs from stored lengthdictionary expansion

The v3 action table is larger than SQ2’s v2 table. The Gold Rush interpreter accepts action opcodes through 0xb5, but the decoded local Gold Rush scripts observed so far only use action opcodes through 0xa9.

Source-backed GR-only action slot notes:

OpcodeLocal labelEvidence-backed effect
0xb0reserved_noop_v3_0Table entry has zero operands and routes to the generic no-op/return handler at image 0x5286.
0xb1set_menu_interaction_gateReads one immediate byte, zero-extends it, and stores it in word [0x0403]. GR code.menu.interact at image 0x9724 returns immediately while [0x0403] == 0, so nonzero values enable the menu interaction path after the usual menu-request flag is set. QEMU menu_gate_suite validates zero as blocked and nonzero as modal-menu entry.
0xb2reserved_noop_v3_2Table entry has zero operands and routes to the generic no-op/return handler at image 0x5286.
0xb3reserved_noop_v3_4argsTable entry declares four fixed operands, but the handler is the generic no-op/return handler.
0xb4reserved_noop_v3_2varargsTable entry declares two variable operands via metadata 0xc0, but the handler is the generic no-op/return handler.
0xb5clear_key_release_event_gateStores zero in byte [0x0405]. GR action 0xad sets the same byte to one, and the keyboard IRQ hook tests it before enqueueing a type-2 zero event on selected key-release paths. Local source model tools/agi_input.py covers this set/clear gate with the shared tracked-key latch semantics.

Source-backed shared action deltas, relative to SQ2 / AGI 2.936:

AreaOpcodesObserved GR / AGI 3.002.149 difference
Input line and prompts0x6f, 0x73, 0x76, 0x77, 0x78, 0x89, 0x8a, 0xa3, 0xa4, 0xa9GR keeps the normal EGA-style input-line model but removes SQ2’s display-mode-2/input-width branches. It computes the display offset as arg0 << 3, always uses the normal prompt/editor path for string and number prompts, maps the SQ2 input-width set/clear actions to no-op, and closes active text-window state without clearing a width flag.
Key and menu events0x79, 0xad, 0xb1, 0xb5GR expands the script key-map table from 0x27 to 0x31 four-byte slots. A QEMU fixture validates slot 48 by filling 48 dummy slots, mapping typed x in the final slot, and comparing against a direct nonblank picture draw; the no-key control remains blank. GR also replaces SQ2’s incrementing key-release byte [0x1530] with set/clear byte [0x0405], and adds menu interaction gate word [0x0403]. The source-modeled IRQ latch helper covers the key-release gate change; the menu-gate QEMU fixture validates that 0xb1(0) makes a requested menu match the blocked control, while 0xb1(1) yields a distinct modal-menu capture.
Room and state actions0x12, 0x7c, 0x7d, 0x7e, 0x80, 0x84GR remaps immediate room targets 0x7e..0x80 to 0x49 before the ordinary room-switch helper; the decoded local GR scripts do contain those operands. The carried-item selector sets temporary word [0x0dc1] while handling a flag-13 input path and clears it on return. Save wraps the object/inventory chunk in an XOR pass before and after writing the save envelope; restore applies the same repeating Avis Durgan XOR transform after reading that block. The observed transform key is data at DS:0x072c, is modeled by gr_v3_object_inventory_save_xor(), and is QEMU-validated against blank-prefix SG.1, signed GRSG.1, and a signed restore round trip with block lengths 1028, 989, 1811, 100, and 12. Restart records prompt-marker visibility before confirmation; accepted restart redraws the marker, and canceled restart redraws only if it had been visible. The canceled branch is QEMU-validated by hidden/visible prompt-row captures. Action 0x84 preserves object 0 motion mode 4 instead of always clearing byte +0x22.
Object animation and motionframe timer, motion dispatchGR uses the four-plus direction table immediately for exactly-four-loop views, but gates views with more than four loops on flag 0x14. QEMU frame_selection_gate_qemu_001 validates exact-four view 177 selecting group 1 regardless of flag 0x14, and more-than-four view 39 selecting group 1 only when flag 0x14 is set. GR also dispatches motion mode 4 to the same target-direction helper used by mode 3; that branch is instrumented-QEMU-validated by patching only the copied GR action-0x51 mode seed from 3 to 4, while ordinary bytecode still has no observed direct setter for mode 4.

The first dynamic v3 behavior probe is tools/gr_v3_behavior_probe.py. The room-remap probe patches a copied GR game so logic 0 switches once and then continues dispatching call_logic_var(v0), while logic 0x49 draws a fixed picture. Under QEMU, direct target 0x49 and alias target 0x7e produced identical nonblank captures first; the expanded run then validated alias targets 0x7e, 0x7f, and 0x80 against direct target 0x49 in build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json.

The same probe tool now includes --probe key-map-capacity. The promoted run build/gr-v3-behavior/key_map_capacity_qemu_pic001_002.json builds three copied GR fixtures: a direct picture draw, a slot-48 key-map fixture that sends x, and the same slot-48 fixture without a key. QEMU reports the keyed capture matching the direct draw and the no-key capture not matching, with the no-key capture blank. This confirms the observed 0x31 slot loop bound has an observable event-mapping consequence in the original GR interpreter.

The probe tool also includes --probe menu-gate. The promoted suite run build/gr-v3-behavior/menu_gate_suite.json builds a blocked control plus two requested-menu fixtures. The 0xb1(0) fixture matches the blocked control, while the 0xb1(1) fixture differs from both the control and the zero-gate case. That confirms that [0x0403] is not part of menu construction; it gates whether a later 0xa1 menu request may enter the modal menu path.

The probe tool also includes --probe save-xor-extract. The promoted run build/gr-v3-behavior/save_xor_extract_qemu_001.json omits verify_game_signature, so the original interpreter writes a blank-prefix SG.1 save. The extracted file has five blocks with lengths 1028, 989, 1811, 100, and 12; block 3 changes when passed through gr_v3_object_inventory_save_xor() and a second pass restores the emitted bytes. This validates the source-mapped v3 object/inventory save transform without relying on GR’s verifier/save-prefix path.

The signed run build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json corrects the fixture message encoding to use the normal encrypted logic-message text, executes verify_game_signature("GR"), and validates that the original interpreter writes GRSG.1. The first saved-state block begins with bytes 47 52 00, and the same five block lengths and third-block XOR hashes match the blank-prefix run.

The signed restore run build/gr-v3-behavior/signed_restore_roundtrip_suite.json uses the original GR interpreter to generate GRSG.1, then restores it in a second generated fixture. The restored capture matches a direct saved-state control and differs from an unrestored control, confirming that GR action 0x7e decodes the object/inventory block and resumes through restored state for valid signed saves.

The restart prompt-marker run build/gr-v3-behavior/restart_prompt_marker_suite.json builds hidden and visible prompt-marker controls plus matching Escape-cancel restart cases. The hidden cancel capture matches the hidden control with 0 prompt-row foreground pixels; the visible cancel capture matches the visible control with 8 foreground pixels. This validates the source-mapped canceled branch of GR action 0x80 without relying on whole-screen text semantics.

The GR condition dispatcher compares predicate bytes with 0x26, matching the loose bound shape also seen in SQ2, but only the first 19 entries 0x00..0x12 are structured table records in the observed AGIDATA.OVL. Bytes after that overlap punctuation/filename/string data and zeros, so they are treated as reserved/unconfirmed rather than implemented predicates. Local GR scripts observed so far use conditions only through 0x0e.

Generated v3 logic fixtures can be built with:

python3 -B tools/qemu_fixture.py v3-logic payload.bin \
  --game-dir games/GR \
  --logic 0 \
  --output build/qemu-fixtures/gr_logic_000

This copies the selected v3 game to the generated output directory, appends a direct/uncompressed v3 record to the existing prefixed volume for the selected logic resource, and patches that logic entry inside the combined directory.

Generated v3 picture fixtures can be built with:

python3 -B tools/qemu_fixture.py v3-synthetic-picture payload.pic \
  --game-dir games/GR \
  --picture 0 \
  --volume 1 \
  --output build/qemu-fixtures/gr_picture_000

The picture payload is the expanded picture command stream ending with 0xff; the fixture writer stores it through the observed v3 picture-nibble transform and patches the selected picture directory entry. Passing --volume is useful when replacing a previously absent resource; otherwise the existing directory entry volume can be reused.

Generated v3 picture/view fixtures can be built with:

python3 -B tools/qemu_fixture.py v3-synthetic-picture-view payload.pic \
  0 0 0 0 20 80 15 \
  --view-payload payload.view \
  --game-dir games/GR \
  --volume 1 \
  --output build/qemu-fixtures/gr_picture_view_000

The optional view payload is appended as a direct v3 record. This is sufficient for controlled synthetic probes that need a known view body, but it is not a general dictionary-compression writer. Original compressed GR resources remain decoded through tools/agi_resources.py.

The generated v3 picture/view path is QEMU-validated by build/gr-v3-behavior/synthetic_picture_view_suite.json. That run builds a blank control, a generated picture-only fixture, and a generated picture-plus-view fixture. The original GR interpreter renders the picture-nibble fixture differently from blank and renders the direct view record differently from the picture-only fixture.

The static GR/SQ2 comparison report currently lives at build/gr-sq2-static/opcode_static_report.md. Source-level comparison found identical parser contracts for all shared actions and conditions, 17 changed shared action entry snippets, six GR-only action slots, unchanged ordinary view and picture command skeletons after resource expansion, and a small set of GR object/display-path deltas to test later.

Additional local builds: 2.089 through 3.002.149

The expanded census added seven independent interpreter inputs. The generated snapshot is build/cross-version/game_census_expanded.md. Table comparison reports under build/cross-version/ use geometry-derived action counts; early v2 builds required recognizing the observed 0x26-byte trailer in addition to the later 0x20-byte trailer.

SQ1 2.089, XMAS.230 2.230, and XMAS 2.272

SQ1 and XMAS.230 each accept 155 actions 0x00..0x9a; XMAS 2.272 accepts 161 actions 0x00..0xa0. All three retain 19 conditions. XMAS.230’s action table begins at data offset 0x03e7, its condition table at 0x0673, and the usual 0x20-byte trailer separates the tables. Direct disassembly establishes the following early profile selections:

BehaviorSQ1 2.089XMAS.230 2.230XMAS 2.272
Action 0x86No operands; stops sound, performs shutdown cleanup, and terminates.Same zero-operand exit.One selector byte; 1 exits directly, while other values ask for confirmation.
Action 0x9bUnavailable.Unavailable.Consumes two bytes and has no other effect.
Actions 0x9c..0xa0Unavailable.Unavailable.Dispatch to operand-advance stubs at image 0x8400..0x8404; no menu state is constructed.
Position actions 0x25/0x26Write current coordinates, remove old rendered state if drawn, then update the previous-position snapshot.Write current and previous coordinates together without first removing rendered state.Same as 2.230.
Object compositionEarlier partition uses object-number order; later partition is drawing-key sorted.Both partitions are drawing-key sorted.Both partitions are drawing-key sorted.
View orientation encodingOrdinary loop count; selected data has no orientation flags.Packed loop-header count and mutable orientation; see below.Ordinary loop count; mirror state is in each cel control byte.
Picture scannerImage 0x5baf accepts 0xf0..0xf8; 0xf9/0xfa are not dispatched.Image 0x5baa has the same boundary.Image 0x5c38 has the same boundary.
Show pictureRefreshes and marks shown without clearing f15 or closing an active text window.Same.Same.
Automatic loop selectionImage 0x05cc selects direction loops on every eligible pass, without testing cadence countdown object[1].Image 0x049a has the same cadence-independent rule.Image 0x04c6 has the same rule.
String slotsParse action at image 0x1817 accepts selectors below 6.Image 0x1759 accepts the same six slots.Image 0x17f7 accepts the same six slots.
Word-sequence predicateImage 0x0965 requires exact parser/pattern counts; 0x270f is an ordinary identifier.Image 0x0851 implements the 0x270f tail terminator.Image 0x087d implements the same terminator.
Object distanceStores the low byte and wraps sums above 255.Same.Same.
Target-motion startDefers direction/completion until the next eligible target update.Same.Same.
Actions 0x4d/0x4eLeave autonomous mode active; 0x4d clears direction and 0x4e applies only object-0 globals.Same as 2.089.Clear autonomous mode; 0x4d also clears direction.
Inventory displayAcknowledgement-only; does not write v25.Same.Same.
Sound control outputDevice 2 adjusts low attenuation values; no global whole-byte adjustment.Exact relocated match for 2.089.Adds the global whole-byte adjustment and signed clamp.
Save envelopeImage 0x2501 writes four blocks and no logic-resume block.Image 0x2447 writes five blocks.Image 0x24f5 writes five blocks.

The 2.230 and 2.272 condition tables are exact normalized matches. Their shorter string-equality entry delegates to a normalization helper that removes ignored characters, normalizes case, and compares normalized strings. It is not an observable predicate difference from 2.089.

All three games store OBJECT metadata directly rather than through the later repeating-key XOR. SQ1’s plain header is 4e 00 11, defining 26 inventory entries. Its startup path uses the third header byte directly as the runtime record count, so it allocates 17 records. XMAS.230 and XMAS have byte-identical OBJECT files with header 03 00 11, defining one inventory entry; their startup paths increment the third byte and therefore allocate 18 records. A format-only parser still exposes the shared header byte as maximum_object_index, but that name must not be used to infer SQ1 runtime capacity.

XMAS.230 has a distinct view-loop encoding. Its loop-binding helper at image 0x350d masks the loop header with 0x0f before storing the cel count. When header bit 0x80 is set and bits 0x30 do not match the selected loop, it calls row-mirroring helper 0x50af if bit 0x40 is set, then rewrites bits 0x30 with the selected loop. The helper mirrors every cel in the loop and uses the low nibble as its iteration count. Action 0x31 at 0x36e2 independently applies the same and ax,0x000f before subtracting one.

The selected VOL.0 supplies direct data evidence for this source rule. Views 10, 11, and 14 contain two references to a shared four-cel loop headed 0xc4; views 16, 17, and 18 contain two references to a shared six-cel loop headed 0xc6. Their corresponding 2.272 resources replace 0xc4/0xc6 with ordinary counts 0x04/0x06 and move bit 0x80 into each cel control byte. Across those six resources, lengths and offsets remain equal and only the loop orientation markers and matching cel-control markers change. This makes the packed loop-header rule observable for valid selected data rather than a dead decoder branch.

The picture scanners are relocated copies of one another. SQ1 scanner 0x5baf, XMAS.230 scanner 0x5baa, and XMAS scanner 0x5c38 subtract 0xf0 and accept only results through 8, proving a command vocabulary of 0xf0..0xf8. XMAS.230’s decode-with-clear 0x5b7f, coordinate reader 0x5ce3, line rasterizer 0x5d14, and fill 0x4a3a are relocated matches for the corresponding 2.272 roles.

All three sound drivers initialize four streams and countdowns and select one channel only when the hardware selector is zero. XMAS.230 start 0x7478 and tick 0x74da match 2.272 after relocation, while event output 0x7598 is an exact relocated match for SQ1 0x7615. Both always write both tone bytes. For device 2, SQ1 helper 0x7657 increases control low nibbles below 8 by 3 before output; otherwise it emits CL unchanged, and XMAS.230 follows the same rule. XMAS 2.272 event output 0x7634 uses corresponding helper 0x7680, then adds the global adjustment to the entire control byte and applies its signed greater-than-15 clamp. None of the three builds has the later per-tick attenuation envelopes.

SQ1’s save writer serializes block lengths 0x03db, 17 * 0x2b = 0x02db, the selected metadata runtime length 0x0153, and twice the configured replay capacity. It then closes the file. XMAS.230 and XMAS serialize 0x03db, 18 * 0x2b = 0x0306, metadata length 0x000f, twice the configured replay capacity, and a variable logic-resume block. The selected XMAS.230 logic 0 configures 200 replay pairs, giving block 4 length 0x0190. Save/restore roles 0x2447/0x2279 are relocated matches for XMAS 2.272. The three restore paths read their corresponding four- or five-block forms.

XMAS also retains three installation methods. ORIGINAL.BAT selects VOL.ORG, LOGDIR.ORG, and PICDIR.ORG; LOGMETH.BAT and VOLMETH.BAT select modified alternatives. The active local files match the original set. Its directory volume nibbles represent three distribution disks while the selected payload is named VOL.0, so an installed-layout census incorrectly reports missing VOL.1/VOL.2. This is packaging evidence, not malformed resource semantics.

The selected XMAS.230 copy also contains only VOL.0, while its directories contain present entries naming volumes 1 and 2. The readable VOL.0 resources are valid direct records. Missing-file errors for the other entries describe an incomplete selected package and are excluded from the valid-data profile.

The remaining differing action entries were reduced against KQ2’s 2.411 handlers. Ordinary EGA input configuration, number entry, input-line disable/enable/refresh/erase, restart acceptance, and successful view-preview output have the same portable effects. Their source differences are alternate display-mode branches, interrupt-timing guards, resource/file cleanup, temporary backing-storage organization, and joystick calibration. Together with the mapped resource, renderer, object, motion, sound, and save paths, this supports full-EGA gameplay profiles for all three builds. Canonical initial reserved save bytes remain a separate binary-synthesis requirement.

BC 2.439, MG 2.915, and SQ1.22 2.917

BC’s loader-transformed executable was decoded with its own SIERRA.COM. Its 170 action and 19 condition table records match LSL1 2.440 exactly. Of 38 principal symbolic roles, 33 uniquely relocate, two list wrappers are ambiguous, and three short embedded entries are not uniquely searchable. Manual source inspection confirms that the shaped/stippled pattern code and timer-driven sound path are relocated byte matches. The mapped full-EGA core therefore follows the 2.440 behavior.

MG 2.915 has the same 174 action and 19 condition contracts as KQ1 2.917. Twenty-nine of 32 principal roles uniquely relocate and match; two wrappers are ambiguous and the embedded frame jump table requires manual mapping. The mapped full-EGA core follows 2.917.

SQ1.22’s loaded interpreter image and KQ1’s are the same length and differ at only three bytes in the embedded game-signature region. All 34 mapped roles match at the same addresses. It is a second 2.917 build, not a new behavioral profile.

Original saves supply these historical artifact dimensions:

Selected gameProfileBlock 1Block 2Block 3Block 4Block 5
BC2.439/2.440 rules0x05df17 records, 0x02db26 items, 0x0135127 pairs, 0x00feVariable
MG2.915/2.917 rules0x05df21 records, 0x0387one item, 0x0005110 pairs, 0x00dcVariable
SQ1.222.9170x05e118 records, 0x030625 items, 0x014850 pairs, 0x0064Variable

The MG discrepancy is an artifact-version mismatch, not an alternate header meaning in the selected interpreter. The bundled save is dated 5 November 1987; the bundled interpreter is dated 16 November 1987. The current interpreter’s startup at image 0x0fa5 loads and decodes OBJECT, increments the decoded third header byte 0x5a, and multiplies 91 by the 0x2b record size. Its save writer at 0x2751 therefore writes block lengths 0x05e1, 0x0f49, and 0x0005 before its dynamic replay/resume blocks. The bundled older save instead has 0x05df, 0x0387 (21 records), and 0x0005. Consequently that save cannot have been produced by the selected interpreter/metadata combination and is retained only as historical evidence, not as its binary interchange contract.

MH1 and MH2 replacement copies

The replacement MH1 uses interpreter 3.002.102. Its AGIDATA.OVL is byte-identical to KQ4D’s, and its equal-length AGI differs from KQ4D’s at only two embedded game-signature bytes. This is stronger equivalence than the previously inspected MH1 3.002.107 copy, whose mapped full-EGA core matched KQ4D after relocation but whose image contained additional unmapped changes. The 3.002.107 observations remain historical cross-version evidence; they no longer describe the selected games/MH1 input.

MH2 and Gold Rush are both labeled 3.002.149 and their loaded images have the same length. Only 29 bytes differ. Those bytes classify completely as:

  • a Gold Rush-only helper that maps immediate room operands 0x7e..0x80 to 0x49, plus the 0x12 call site that uses it;
  • three startup allocation-size words (0x0a00 in Gold Rush and 0x0b80 in MH2); and
  • the embedded expected game signature (GR versus MH2).

MH2 action 0x12 reads its operand and calls the ordinary room-switch path directly. This same-version control corrects the earlier generalization: the room aliases are a Gold Rush build feature, not universal 3.002.149 behavior. The startup allocation-size difference is retained as diagnostic/capacity evidence until a portable observable consequence is established.

The replacement resource census and corrected reference audit report:

Selected gameReadable logic/picture/viewValid soundsInert sound anomalies
MH166 / 237 / 13899Entries 136 and 138 point into non-header bytes in MHVOL.0; unreferenced.
MH296 / 248 / 181193Entries 215 and 216 select absent MH2VOL.6; unreferenced.

No source logic is skipped, and every statically addressable resource resolves. For both games, the readable sound set exactly equals the script-referenced sound set. This closes the previous resource-completeness blocker while keeping the four present-looking tail entries outside valid-game semantics. The generated reports are build/cross-version/mh_replacement_census.md and build/cross-version/mh_replacement_reference_audit.json.

Fixture Compatibility Notes

Generated fixtures must never patch files under games/. The source game is copied into a destination under build/, copied files are made writable, and only that generated copy is modified. This matters because private game inputs may intentionally be read-only and because each interpreter version can require different container-writing rules.

Current v2 fixture writers still assume the SQ2-style split-directory format. The v3 path supports direct/uncompressed logic and view replacement plus picture-nibble picture replacement in a copied combined-directory game. Future v3 probes that need generated compressed logic/view/sound records would still need a dictionary encoder, but controlled fixtures can use direct records. The current direct view and picture-nibble picture path has been checked against the original GR interpreter with the synthetic picture/view QEMU probe.

Resource file notes

This page records clean-room observations about the SQ2 resource directory and volume files. It is based on local files from an explicitly selected SQ2 game directory (*DIR, VOL.*, AGIDATA.OVL) and the decrypted executable.

Directory file loading

The decrypted executable references these AGIDATA strings:

AGIDATA offsetString
0x116alogdir
0x1172viewdir
0x117apicdir
0x1182snddir

The routine at image offset 0x4305 loads all four directory files with the whole-file loader at image offset 0x3113.

Observed load order and destination pointers:

File stringPointer destination
logdir[0x11b2]
picdir[0x11b6]
viewdir[0x11b4]
snddir[0x11b8]

The whole-file loader at image offset 0x3113:

load_whole_file(path, optional_destination):
    handle = dos_open(path, mode=0)
    if open fails:
        display/retry error using AGIDATA string 0x0eef

    size = dos_seek(handle, offset=0, origin=end)
    dos_seek(handle, offset=0, origin=start)
    [0x0f1a] = size

    if optional_destination == 0:
        optional_destination = allocate(size)

    bytes_read = dos_read(handle, optional_destination, size)
    if bytes_read != size:
        display/retry disk error path

    dos_close(handle)
    return optional_destination

The exact retry behavior still needs refinement, but the open, seek-to-end, seek-to-start, optional allocation, full read, and close steps are directly visible in the disassembly.

Directory entry accessors

The four accessors multiply a resource number by three, add the result to the loaded directory base pointer, and reject entries whose first byte has a high nibble of 0xf.

Image offsetDirectory baseMissing-resource string
0x4371[0x11b2] (logdir)logic at AGIDATA offset 0x1140
0x43a5[0x11b4] (viewdir)view at AGIDATA offset 0x1146
0x43d9[0x11b6] (picdir)picture at AGIDATA offset 0x114b
0x440d[0x11b8] (snddir)sound at AGIDATA offset 0x1153

The shared check at image offset 0x434e:

entry_or_null(entry):
    if (entry[0] & 0xf0) == 0xf0:
        return 0
    return entry

So an absent resource directory entry is represented by a first byte whose high nibble is 0xf; in the SQ2 files observed so far this commonly appears as ff ff ff.

Directory entry format

Each loaded directory is an array of 3-byte entries.

The local SQ2 bytes support this interpretation:

byte0 high nibble: volume number
byte0 low nibble plus byte1 plus byte2: 20-bit offset in that VOL file

volume = byte0 >> 4
offset = ((byte0 & 0x0f) << 16) | (byte1 << 8) | byte2

Evidence from local samples:

DirectoryEntryRaw bytesDecoded targetBytes at target
LOGDIR010 6d 1bVOL.1 offset 0x06d1b12 34 01 b2
PICDIR110 96 8eVOL.1 offset 0x0968e12 34 01 a2
VIEWDIR000 9a 51VOL.0 offset 0x09a5112 34 00 1e
SNDDIR110 00 00VOL.1 offset 0x0000012 34 01 51

Many sampled entries across all four directories point to locations in VOL.* whose first two bytes are 12 34. The third byte in the sampled headers matches the decoded volume number.

Observed directory sizes:

FileSizeEntry count
LOGDIR426142
PICDIR444148
VIEWDIR720240
SNDDIR21070

Two entries at the ends of LOGDIR and PICDIR do not have an absent high nibble, but decode beyond the end of VOL.0:

DirectoryEntryRaw bytesDecoded target
LOGDIR14101 ff ffVOL.0 offset 0x1ffff
PICDIR14702 ff ffVOL.0 offset 0x2ffff

The current working interpretation is only that the executable’s generic directory-entry check would not reject these bytes. No observed local call path has attempted to load these resource numbers yet.

Volume file handles

The routine at image offset 0x3080 loops over volume numbers 0 through 4. For each number it formats AGIDATA string 0x0ee8 (vol.%d) into a local buffer, opens the resulting filename with the DOS open wrapper at 0x5cce, and stores the returned handle in a table starting at [0x0f04].

The routine at image offset 0x30d6 loops over the same five handle slots and closes every handle not equal to 0xffff, then resets the slot to 0xffff.

SQ2 contains VOL.0, VOL.1, VOL.2, and a tiny VOL.3; there is no VOL.4 in the current directory. The interpreter still has five handle slots.

Volume record header and generic reader

The generic resource reader starts at image offset 0x2e56. A retry wrapper at image offset 0x2e32 calls it until it returns a non-zero pointer or sets error state [0x0f02] to 5.

Given a non-null directory entry pointer and an optional destination pointer, the reader:

read_volume_resource(entry, optional_destination):
    if volume handles are not open:
        open VOL.0 through VOL.4

    volume = entry[0] >> 4
    handle = volume_handles[volume]
    offset = ((entry[0] & 0x0f) << 16) | (entry[1] << 8) | entry[2]

    seek(handle, offset, origin=start)
    header = read(handle, 5)

    require header[0] == 0x12
    require header[1] == 0x34
    require header[2] == volume

    payload_length = header[3] | (header[4] << 8)
    [0x0f1a] = payload_length

    if optional_destination == 0:
        optional_destination = allocate(payload_length)

    require read(handle, optional_destination, payload_length) == payload_length
    return optional_destination

The little-endian length is pinned down by the stack offsets in the disassembly: the 5-byte header is read to [bp-0x0f]..[bp-0x0b]; image offsets 0x2f73..0x2f85 compute ([bp-0x0b] << 8) + [bp-0x0c]. Since [bp-0x0c] is the fourth header byte and [bp-0x0b] is the fifth, this is header[3] | (header[4] << 8).

Local samples confirm the same layout. SNDDIR entry 1 points to VOL.1 offset 0x00000; the header bytes are 12 34 01 51 00, so the payload length is 0x0051. The next sound entry starts at 0x00056, exactly 5 header bytes plus 0x51 payload bytes later.

The resulting volume record layout is:

byte 0: 0x12
byte 1: 0x34
byte 2: volume number
byte 3: payload length low byte
byte 4: payload length high byte
byte 5..: payload bytes

Sampled validation over present entries:

DirectoryPresent entriesBad magicBad volume byteOut of range
LOGDIR119001
PICDIR75001
VIEWDIR203000
SNDDIR49000

The two out-of-range entries are the end entries shown in the directory-format section above.

Resource load call graph

Four higher-level loaders call the directory accessors and then the generic reader:

Resource typeLoaderExisting-cache lookupDirectory accessorGeneric reader call
Logic0x119a0x110f0x43710x11e0 -> 0x2e32
View0x39f70x39790x43a50x3a54 -> 0x2e32
Picture0x4a3b0x49e80x43d90x4a90 -> 0x2e32
Sound0x51260x50d80x440d0x5185 -> 0x2e32

The loaded resource payload returned by 0x2e32 does not include the 5-byte VOL.* record header; it begins at the resource-type-specific payload.

Gold Rush / AGI v3 resource container comparison

Gold Rush (games/GR) uses interpreter version string Version 3.002.149 in AGIDATA.OVL and changes the resource container without changing the decoded logic/picture/view/sound payload categories. Unlike SQ2, games/GR/AGI is already an MZ executable, so no SIERRA.COM decrypt step was needed before disassembling it.

The v3 game directory is a single combined GRDIR file instead of four split directory files. Its first eight bytes are four little-endian section offsets:

Header wordSectionOffsetEntry countPresent entries
+0logic0x0008245182
+2picture0x02e7245186
+4view0x05c6256247
+6sound0x08c65144

The section payloads are still 3-byte directory entries using the same packed volume/offset calculation as SQ2. The absent-entry test changed, though: SQ2’s shared check rejects any entry whose first byte has high nibble 0xf, while the v3 shared check at GR image 0x4599 rejects only the exact byte sequence ff ff ff.

GR image 0x44de is the v3 directory loader corresponding to SQ2 code.resource.load_all_directories. It formats a combined directory filename from the runtime prefix and the "%sdir" string in AGIDATA.OVL. When the combined file opens, the loader reads it whole and stores section-base pointers derived from the first four header words. If the combined open fails, the same routine falls back to separate logdir, picdir, viewdir, and snddir loads.

The v3 volume files are named with a prefix, for example GRVOL.0 through GRVOL.12. GR image 0x33c2 opens sixteen possible volume handles by formatting "%svol.%d", so higher volume numbers are part of the observed v3 container design rather than malformed directory entries.

The v3 generic reader starts at GR image 0x30d0, with a retry wrapper at 0x30ac. It reads a 7-byte record header:

byte 0: 0x12
byte 1: 0x34
byte 2: metadata/volume byte
byte 3: expanded length low byte
byte 4: expanded length high byte
byte 5: stored length low byte
byte 6: stored length high byte
byte 7..: stored bytes

If metadata bit 0x80 is clear, the metadata byte must equal the directory entry volume. If metadata bit 0x80 is set, the reader records a picture-specific transform flag, masks the metadata byte to its low nibble, and then compares that low nibble with the directory entry volume.

After allocation, v3 has three payload paths:

ConditionTransformGR image offsetObserved resource families
metadata bit 0x80 setpicture nibble expansion0x9a5bpictures
expanded length equals stored lengthdirect read0x607d DOS read wrappera few logic/sound resources
expanded length differs from stored lengthdictionary expansion0x07f4most logic, all views, most sounds

The dictionary path is a bitstream decoder with 9-bit initial codes, growth to 10 and 11 bits, reset code 0x100, and end code 0x101. Dictionary entries are prefix/suffix pairs; after each ordinary code the decoder adds previous_string + first_byte(current_string) at the next dictionary slot.

The picture path is not the same dictionary compression. It is a nibble realigner: when the stream emits picture command 0xf0 or 0xf2, the next color/control operand is packed into one nibble. The transform expands those nibbles back into ordinary byte-sized picture command operands and stops after emitting 0xff.

tools/agi_resources.py implements the observed split v2 and combined v3 container formats. Local tests in tests/test_agi_resources.py currently validate:

  • SQ2 detection as split-directory/direct-record layout.
  • GR detection as combined-directory/7-byte-record layout.
  • Synthetic dictionary reset/literal/end expansion.
  • Synthetic 0xf0/0xf2 picture-nibble expansion.
  • Synthetic picture-nibble encoding back to the observed packed storage form.
  • v3 copied-fixture patching for direct logic/view records and picture-nibble picture records.
  • All present GR resources expanding to their header-declared lengths.

The full GR transform census from the local parser is:

Resource familyDirectDictionaryPicture nibble
logic21800
picture00186
view02470
sound3410

tools/qemu_fixture.py can now write generated v3 fixtures without modifying games/. It appends direct records for generated logic/view payloads and picture-nibble records for generated picture payloads, then patches the selected entry in the combined directory. This is intentionally not a general v3 dictionary compressor: original compressed local resources are read through the decoder, while controlled synthetic fixtures use the direct-record path already present in the observed interpreter.

The generated v3 fixture path is also original-engine checked. QEMU report build/gr-v3-behavior/synthetic_picture_view_suite.json compares a blank control, a generated picture-nibble picture fixture, and a generated picture-plus-direct-view fixture. The picture-only capture differs from blank by 215,040 pixels, and the picture-plus-view capture differs from picture-only by 128 pixels, proving that the original GR interpreter accepts both generated v3 record forms for this controlled case.

The logic disassembler now uses tools/agi_resources.py for payload loading and detects AGIDATA dispatch-table bases from the observed operand metadata signature. Observed table bases are:

Local inputAction tableCondition table
SQ2 / v20x061d0x08fd
GR / v30x04400x0762
KQ4D / v30x06200x0942

The GR and KQ4D v3 action dispatchers support action opcodes through 0xb5. The decoded Gold Rush scripts observed so far only use actions through 0xa9. The decoded KQ4D demo scripts use the normal sound actions 0x62..0x64 and only reference sound resources 70..79, which are the clean v3 sound records in that local directory. Later present-looking KQ4D sound-section triples that do not point at 12 34 headers are therefore planning evidence for source inspection, not currently observed valid-resource behavior.

The GR condition dispatcher compares predicate bytes with 0x26, but only entries 0x00..0x12 are structured condition-table records in the observed AGIDATA.OVL; bytes above that overlap string/data and are not treated as confirmed predicates. Local GR scripts observed so far use condition opcodes only through 0x0e.

Resource cache records

The four resource families keep separate singly linked cache lists. In every observed record, word +0x00 is the next-record pointer. The lookup helpers walk the list for a matching resource number and also leave an insertion/link slot in a family-specific global so the loader can append a miss without rescanning.

Resource typeRoot and insertion stateLookupRecord sizeRecord fields
Logicroot [0x0977], previous-link slot [0x0983]0x110f10 bytes+0x00 next, +0x02 logic number byte, +0x03 message count byte, +0x04 bytecode pointer (payload + 2), +0x06 current instruction pointer, +0x08 message offset table pointer
Viewroot [0x0ffa], previous-link slot [0x1000]0x39795 bytes+0x00 next, +0x02 view number byte, +0x03 payload pointer
Picturestatic first record/root [0x120e], previous-link slot [0x1214]0x49e85 bytes+0x00 next, +0x02 picture number byte, +0x03 payload pointer
Soundstatic first record/root [0x125a], previous-link slot [0x1268]0x50d814 bytes+0x00 next, +0x02 sound number word, +0x04 payload pointer, +0x06/+0x08/+0x0a/+0x0c channel stream pointers

View loader 0x39f7 allocates a 5-byte cache record on a miss and writes it through the link slot left in [0x1000]. Picture loader 0x4a3b and sound loader 0x5126 use their static first records when the previous-link slot is zero, then allocate later records (5 bytes for pictures and 14 bytes for sounds). Sound loader 0x5126 derives the four channel stream pointers by reading the first four little-endian words in the payload and adding each offset to the payload base.

The same saved link slot gives picture/view discard its stack-shaped effect. View discard helper 0x3f0d finds the selected record through 0x3979, then stores zero through [0x1000]. Picture discard 0x4bce does the equivalent through [0x1214]. The cleared word is the family root when the match is first, or the preceding record’s next link otherwise. Consequently the selected record and every later record in that family become unreachable together; this is not arbitrary single-entry deletion.

Both helpers flush the object update lists, pass the selected record address to code.heap.rewind_to, rebuild the lists, and refresh the free-memory state. Because the allocation frontier is shared by resource families, valid scripts must not keep using a selected payload after discarding its record. The clean spec expresses the stable family-visible truncation rule and excludes stale payload use rather than exposing allocator addresses.

Selected local scripts follow the expected stack discipline. KQ2 logic 67 loads views 53,59,51,52,57,60 and discards them as 60,57,52,51,59,53. Common picture setup paths load and prepare a picture, then discard it before loading the room’s views and sounds. This is game-data support for the source-derived list/rewind rule, not the basis for it.

Early payload observations from these loaders:

  • Logic payloads begin with a little-endian offset. Loader 0x119a treats payload + 2 as an instruction/data pointer, reads the first two payload bytes as a little-endian offset, then reads one byte at payload + 2 + offset into the logic cache record.
  • Sound loader 0x5126 reads the first four little-endian words in the sound payload as offsets relative to the payload base and stores four derived pointers in its cache record.
  • View and picture loaders store the returned payload pointer directly in their cache records. Later graphics/object routines parse view group/frame tables and picture command streams as described in Graphics and Object Pipeline.

Runtime model

This page lifts the byte-level notes into implementation-facing data types and operation families. It is still a clean-room model: every name here is derived from local disassembly, local SQ2 resources, and the evidence recorded in the other pages.

The names are intentionally descriptive rather than final API names. A new implementation can use different public names, but should preserve the observed storage widths, lifetimes, and side effects unless later evidence proves a better model.

Global scalar state

The interpreter uses a byte-variable array rooted at DS:0x0009. Variable opcodes operate on unsigned bytes:

Operation familyObserved handlersImplementation contract
Increment/decrement0x01 (inc_var), 0x02 (dec_var)Saturating byte update. Increment stops at 0xff; decrement stops at 0x00.
Assignment0x03 (assignn), 0x04 (assignv)Store an immediate byte or another variable byte.
Arithmetic0x05 (addn) through 0x08 (subv), 0xa5 (muln) through 0xa8 (divv)Byte arithmetic. Multiply stores the low byte of the product; divide stores the 8-bit quotient.
Indirection0x09 (indirect_assignv), 0x0a (assign_indirectv), 0x0b (indirect_assignn)Treat a variable’s byte value as another variable index.

The flag store is a packed bitfield rooted at DS:0x0109. The flag helper maps a flag number to:

byte_address = 0x0109 + flag_number / 8
mask = 0x80 >> (flag_number & 7)

This means flag 0 is the high bit of byte 0x0109, flag 1 is the next bit, and so on. Initialization helper 0x752a clears 0x20 bytes, so the observed flag storage spans 256 bits.

Strings and parsed input

Fixed string slots begin at DS:0x020d. Each slot is 0x28 bytes, and the parser-facing action 0x75 (parse_string_slot) accepts only slot indexes 0..11, giving twelve script-visible slots in the observed interpreter.

Observed string operations:

OperationOpcodeContract
Copy current-logic message to slot0x72 (set_string_slot_from_message)Resolve a message through the current logic record and copy at most 0x28 bytes into 0x020d + slot * 0x28.
Prompt/edit a slot0x73 (prompt_string_to_slot)Display a prompt message, accept edited input, and store a null-terminated result in the chosen slot. The requested max length is clamped to 0x28.
Copy from pointer table to slot0x74 (set_string_slot_from_table)Read a word pointer from DS:0x0c8f + index * 2 and copy at most 0x28 bytes into the chosen slot. Static SQ2 data has zero-filled entries in the sampled table.
Parse a slot0x75 (parse_string_slot)Normalize the slot text, look words up in WORDS.TOK, fill parsed-word buffers rooted around 0x0c7b, and set/clear parser flags.
Compare two slotscondition 0x0f (string_slots_equal_normalized)Compare two slots after dropping bytes from DS:0x094b and lowercasing ASCII A..Z; this does not use WORDS.TOK.

Parser normalization for action 0x75 uses two SQ2 tables. Bytes in data.words.parser_separators_0c67 ( ,.?!();:[]{}) become word separators and separator runs collapse to one space. Bytes in data.words.parser_ignored_0c75 (apostrophe, backtick, hyphen, and double quote) are dropped without creating a separator. The normalized buffer is trimmed of a trailing space and then scanned one token at a time.

Dictionary lookup is case-insensitive for ASCII letters. Recognized dictionary entries whose word ID is zero are ignored and do not consume one of the ten parsed output slots; local examples include the, a, i, to, and with. Recognized nonzero IDs are appended to the parsed-ID table. If lookup finds an unknown token, the parser stores the pointer for the next output slot, records len(parsed_ids) + 1 in byte variable [0x0012] and data.words.parsed_count_or_error_position, sets parser flag 2, and stops. So an unknown word after ignored zero-ID words still reports output slot 1. If parsing reaches the end with at least one nonzero word ID, the same count word stores the number of parsed IDs and flag 2 is set. If only zero-ID words were present, flag 2 remains clear.

Condition 0x0e (input_word_sequence) consumes the parsed-word buffer rather than the raw string slot. Its bytecode operand is a count followed by that many little-endian word IDs. Word ID 0x270f terminates a successful match, and word ID 0x0001 behaves as a wildcard for one parsed word. QEMU batch parser_edges_001 validates exact two-word matching for look get, wildcard matching for look *, and 0x270f accepting a look prefix while an additional parsed word remains. Source helper 0x095c also allows a terminator-only pattern to match an unknown-token parse state when flag 2 is set and data.words.parsed_count_or_error_position is nonzero; QEMU batch parser_unknown_terminator_001 confirms this behavior.

Condition 0x0f (string_slots_equal_normalized) is the direct string-slot comparison path. It treats space, tab, ., ,, ;, :, ', !, and - as ignored bytes in the local SQ2 AGIDATA.OVL, lowercases ASCII uppercase letters, and then requires exact byte equality through both strings’ zero terminators.

For a new implementation, the clean boundary is:

StringSlots: fixed-size editable strings
WordParser: converts one slot into word IDs and parser flags
SaidLikeCondition: matches bytecode word sequences against WordParser output
StringCompareCondition: normalized direct slot equality, independent of WordParser

This separation matches the observed data flow and now has dynamic coverage for the basic message-to-string parse path, exact parsed-word matching, wildcard matching, terminator-prefix matching including the unknown-token edge, and direct normalized string comparison.

Logic resources and execution state

Loaded logic resources are represented by 10-byte cache records linked from [0x0977]:

+0x00: next record
+0x02: logic number
+0x03: message count
+0x04: bytecode base pointer
+0x06: current instruction pointer
+0x08: message offset table base

The interpreter loop reads from record+0x06. Action 0x91 (save_logic_resume_ip) stores the current bytecode pointer there, and action 0x92 (restore_logic_entry_ip) restores it from the bytecode base at record+0x04.

Implementation-facing lifetimes:

Load pathObserved actionsLifetime
Cached load0x14 (load_logic), 0x15 (load_logic_var)The record remains linked from [0x0977].
Nested call0x16 (call_logic), 0x17 (call_logic_var)If the target was not already cached, it is loaded temporarily, executed, unlinked, and freed by rewinding the heap.
Room/state switch0x12 (switch_room_like), 0x13 (switch_room_like_var)Resets object/resource state, changes byte variable 0, loads the destination logic, and reinitializes display/update state.

The important higher-level type is not just “logic bytecode”; it is a logic activation record containing bytecode, messages, and resumable instruction state.

Resource handles

All four resource types are addressed through directory entries and VOL.* records, then cached as small linked records. After the generic volume reader, payload pointers refer to the type-specific payload, not the five-byte VOL.* header.

Resource typeMain loaderCache identityMain consumer
Logic0x119alogic numberBytecode interpreter and message resolver.
View-like0x39f7view numberObject records and transient preview objects.
Picture-like0x4a3bpicture numberPicture command decoder, via global [0x1377].
Sound-like0x5126sound numberFour derived payload pointers used by sound start/stop code.

A replacement implementation should separate “resource id”, “loaded payload”, and “typed decoded view” concepts. The original interpreter often decodes lazily by storing raw payload pointers in object records, but the object contracts depend on decoded fields such as group counts, frame pointers, width, and height.

Sound resources have a small typed structure on top of the raw payload. The loader reads the first four little-endian words and stores four payload-relative channel stream pointers in the sound cache record. The playback start helper copies those pointers into the runtime channel pointer table and initializes per-channel countdown, active, and attenuation words. The tick routine then consumes each channel as a sequence of records:

FieldSizeMeaning
Duration16-bit little-endianCountdown value for the next sound event; 0xffff terminates that channel.
Tone/control word16-bit little-endianDriver-facing tone/control value. The PC-speaker path converts this word to the PIT divisor documented below; other hardware selectors pass encoded bytes to port 0xc0.
Control byte8-bitLow nibble is copied as the observed attenuation/control value; 0x0f is used for silent/terminated channels.

The local SQ2 corpus scan found 49 present sound resources. All present resources have four sorted, in-bounds channel offsets, and the first offset is 8, immediately after the four-word header. The current clean-room model covers the resource container and event stream shape, not full audio synthesis.

Source inspection also gives an implementation-facing playback schedule. The timer interrupt hook calls the sound tick only while data.sound.active_state is nonzero. At the start of each tick, flag 9 is tested; if flag 9 is clear, the driver stops immediately and sets the configured completion flag. Driver start initializes every channel countdown to 1, so the first event or channel terminator is read on the first active tick. After a non-terminating event is read, its duration word becomes the next 16-bit countdown. A duration of zero would wrap and delay the next channel record read for 65,536 ticks; the local SQ2 corpus has no zero-duration sound events.

The active channel set depends on the hardware selector. When selector [0x112e] is 0 or 8, the tick loop advances only channel 0. Other observed selector values advance all four channels. Natural completion occurs when the last active channel reaches its 0xffff terminator; the stop path clears active state and sets the flag stored by start_sound_with_flag.

Hardware output is source-backed only to the driver interface level. For selector 0/8, the driver uses the PC-speaker-style PIT path: an event with attenuation nibble 0x0f clears port 0x61 bits 0 and 1; otherwise the tone word is converted to a PIT divisor as 12 * (((tone_word & 0x3f) << 4) + ((tone_word >> 8) & 0x0f)), written low byte then high byte to port 0x42 after mode byte 0xb6 is written to port 0x43, and port 0x61 bits 0 and 1 are set. Stop on this path emits the same silence behavior.

Other observed selectors write driver bytes to port 0xc0. Tone output sends the high tone byte and, unless that high byte has top bits 0xe0, also sends the low tone byte. Stop on this path writes the four silence bytes 0x9f, 0xbf, 0xdf, and 0xff. Selector 2 calls a small source helper that adjusts an internal control byte whose high bits match 0x90 and whose low nibble is below 8; the observed port-facing tone bytes remain the high/low tone bytes described above.

Attenuation output is also source-modeled. Each channel has a base attenuation byte, an envelope index, and a previous envelope value. If the base attenuation is 0x0f, the helper writes the channel mask plus 0x0f and skips envelope processing. Otherwise, when the envelope index is not 0xffff, the helper reads a byte from the default table at 0x17b8. Byte 0x80 disables the envelope and copies the previous envelope value into the base attenuation. Other table bytes are deltas from the base attenuation, not cumulative deltas: negative underflow clamps to 0, positive overflow clamps to 0x0f, and the result becomes the previous envelope value. A runtime adjustment byte at [0x0020] is then added and clamped to 0x0f; selector 2 also raises non-silent values below 8 by 2. The final port byte is the adjusted low nibble ORed with the channel mask 0x90, 0xb0, 0xd0, or 0xf0.

These port writes are an implementation detail for hardware-facing compatibility. The portable game-state contract remains the event schedule, active-channel set, attenuation state, tone/attenuation port-output boundary, and completion-flag behavior above. This is not a claim about faithfully synthesizing the analog waveform from the original hardware.

Object records

Persistent objects live in a table of 43-byte records from [0x096b] to [0x096d]. The record is the central runtime type for visible, movable entities:

ObjectRecord {
    current_position: x at +0x03, y/baseline at +0x05
    selected_view: resource byte +0x07, payload pointer +0x08
    selected_group: byte +0x0a, group pointer +0x0c
    selected_frame: byte +0x0e, frame pointer +0x10
    saved_frame_and_position: +0x12, +0x16, +0x18
    dimensions: width +0x1a, height +0x1c
    motion: step +0x1e, direction +0x21, mode +0x22, mode2 +0x23
    priority_control: byte +0x24
    flags: word +0x25
    motion_parameters: mode-specific bytes +0x27..+0x2a
}

The implementation-facing object operations are:

Operation familyRepresentative actions/helpersContract
Bind view resource0x29 (set_object_resource), 0x2a (set_object_resource_var), helper 0x3ae7Store a loaded view payload on the object and select a default or requested group/frame.
Select group/frame0x2b (set_object_subresource), 0x2c (set_object_subresource_var), 0x2f (set_object_derived_resource_2), 0x30 (set_object_derived_resource_2_var)Validate indexes, update selected pointers and dimensions, clamp coordinates when necessary.
Position0x25 (set_object_pos), 0x26 (set_object_pos_var), 0x28 (add_object_pos_from_vars), 0x93 (set_object_pos_dirty), 0x94 (set_object_pos_dirty_var)Write current and/or saved coordinates; dirty-position forms set object flag 0x0400 and call placement.
Activate/deactivate0x23 (activate_object), 0x24 (deactivate_object)Add/remove the object from graphics/update participation and refresh the update lists.
Motion control0x48..0x55, 0x51 (move_object_to), 0x52 (move_object_to_var), 0x53 (approach_first_object_until_near), 0x54 (start_random_motion)Configure object direction/motion modes, completion flags, and target/step parameters. 0x51/0x52 target an object at X/Y and set a completion flag when it arrives; 0x53 approaches the first object entry until within a threshold and sets a completion flag; 0x54 starts random autonomous direction changes.
Object0/global direction mirror0x83 (clear_global_0139), 0x84 (set_global_0139_and_clear_object0_field_22), code.engine.main_cycleAt the pre-logic mirror point, [0x0139] == 0 copies object 0 direction byte +0x21 to global byte [0x000f]; nonzero copies [0x000f] back to object 0. After logic returns, object 0 +0x21 is restored from [0x000f], so script writes to object 0 direction occur after the branch point and do not seed the next cycle by themselves.
Collision/control flags0x3d (set_object_bit_0008), 0x43 (set_object_bit_0200), 0x58 (set_object_bit_0002), and companionsToggle bits that affect horizon clamp, collision tests, and control-buffer acceptance.
Diagnostics0x85 (display_object_diagnostics_var)Display object number, X/Y, width/height, priority/control, and step size using the interpreter’s built-in diagnostic template.

The separate 3-byte table rooted at [0x0971] stores object/item metadata used by inventory-like logic. The third byte is a location/state marker. Actions 0x5c..0x61 set, clear, and read that marker, and action 0x7c (show_inventory_selection) treats marker value 0xff as “carried”: it builds a two-column list from those entries, displays their table-relative name pointers, and stores the selected item index in byte variable [0x22] or 0xff on cancel.

Two temporary object forms reuse the same structure:

  • Actions 0x7a (setup_transient_object) and 0x7b (setup_transient_object_var) use a fixed object-like record at 0x0eb4.
  • Actions 0x81 (display_view_resource_text_like) and 0xa2 (display_view_resource_text_like_var) use a stack-local 43-byte record.

That reuse is a strong signal that object rendering should be implemented against a common object/frame interface rather than only against persistent table entries.

Graphics and update operations

The picture decoder, object renderer, and display overlay operate on a logical graphics/control buffer segment stored at [0x136f]. The observed coordinate space is 0xa0 columns by 0xa8 rows.

Implementation-facing phases:

  1. Picture preparation decodes picture command bytes into the logical buffer.
  2. View binding selects frame data and dimensions for object records.
  3. Placement validates object coordinates against screen bounds, the horizon-like line [0x012d], object-object crossing, and control-buffer classes.
  4. Update-list builders partition active objects between roots 0x1703 and 0x16ff. Each root draws objects in ascending key order, where the key is baseline Y unless fixed-priority bit 0x0004 routes object byte +0x24 through the reverse-priority helper. Equal keys remain in object-table order, so later object-table entries draw later. Root 0x1703 is drawn completely before root 0x16ff.
  5. Render nodes save backing rectangles, draw frame data, and later restore old rectangles during list flushes.
  6. Display-overlay entries copy full-screen or rectangular regions from the logical buffer to display memory.

This implies a useful replacement architecture:

ResourceStore -> LogicVM -> ObjectTable -> PictureBuffer
                          -> RenderLists -> DisplayBackend

The original interpreter interleaves these concerns through global helpers, but the observed side effects line up with those separable components.

Implementation state machines

The source names and addresses are useful evidence, but a clean-room implementation should model the main runtime objects as small state machines. These states are implementation-facing summaries of the observed transitions; they are not claims about the exact original memory layout.

Top-level cycle:

PhaseSource-backed workPortable lifetime/order consequence
Pacingcode.engine.wait_for_cycle_counter waits for [0x1784] >= v10, then clears [0x1784].Timer/sound IRQ work is asynchronous; the synchronous cycle starts only after its pacing threshold.
Input resetcode.input.clear_status_bytes clears 50 mapped-status bytes; flag helper calls clear f2 and f4.Old status events and parsed-input readiness cannot leak into the next ordinary cycle.
Input production/dispatchcode.input.update_timed_events may enqueue due events, then code.input.process_cycle_events clears v19/v9, services a requested menu, and consumes pending events.New mapped statuses and direction input are established before object direction mirroring and logic 0.
Pre-logic object workcode.engine.main_cycle applies the object-0/global direction mirror, then calls code.motion.pre_mode_and_boundary_update.Autonomous direction and configured-boundary effects can be observed by logic in the same cycle.
LogicThe cycle snapshots v3 and f9, establishes code.control.save_abort_context, and calls logic 0. A zero result clears v9, v4, v5, and f2, then calls logic 0 again from the saved control context.Room/restart-style immediate re-entry does not repeat pacing, input, direction mirroring, or pre-motion.
Post-logic cleanupObject 0 direction is restored from the global byte. A v3/f9 change redraws enabled status text. The cycle clears v4, v5, f5, f6, and f12.Boundary/new-room/restart markers survive throughout logic execution and expire before the next post-update/cycle.
Object updatecode.object.frame_timer_update runs only when text-mode byte [0x1757] is zero.Alternate text mode suppresses direction selection, animation, movement, object draw, and dirty refresh as one stage.

The timer interrupt path is not inserted into this table as a synchronous phase. It advances the elapsed-time and pacing counters independently; the sound hook advances active sound before periodically chaining to the ordinary timer path. A host scheduler may represent these as queued tick events while preserving their state transitions relative to cycle boundaries.

Resource lifecycle:

StateEntered byExited byObservable contract
UncachedRoom reset, explicit discard, or startupLoad action/cache missNo payload pointer should be usable by object, picture, logic, or sound consumers.
Cached raw payloadload_logic, load_view, load_picture_var, load_sound, and variable formsDiscard action or room resetDirectory and volume lookup has succeeded; cache identity is the resource number. Load-on-miss records a resource-event pair when recording is enabled.
Selected for useLogic call, view binding, picture prepare/overlay, sound startReturn from operation or later selectionA cached payload is attached to a consumer: logic activation, object record, picture decoder global, or sound state.
Mutated/displayedPicture decode/show, transient object draw, active object refresh, sound start/stopNext refresh, discard, room switch, replayUser-visible or saved-state side effects occur. Picture/view/transient operations append replay pairs unless flag 7 or the recording gate suppresses recording.
Replay/restoredRestore or display-mode replayReplay finish at code.restore.finish_replay_and_reenable_recordingEvent recording is disabled while saved pairs are consumed, then re-enabled before normal execution continues.

Heap and allocation lifecycle:

StateEntered byExited byObservable contract
Startup memory allocationcode.startup.allocate_runtime_memorySuccessful DOS memory allocation, or fatal startup errorStartup shrinks/probes the resident DOS block, requests a runtime memory block with DOS AH=48h, converts the returned segment into a DS-relative byte offset, writes that offset to both [0x0a55] and [0x0a57], and writes [0x0a5b] = [0x0a57] + requested_runtime_paragraphs * 16. Failure displays the startup memory error and terminates through DOS.
Bump heap activeStartup after heap base/limit initializationInterpreter shutdown or fatal allocation failureCurrent heap pointer [0x0a55] advances monotonically on ordinary allocations. Heap base [0x0a57], room/reset mark [0x0a59], limit [0x0a5b], temporary mark [0x0a5d], and high-water pointer [0x0a5f] define the allocator state.
AllocationHelper 0x13d6(size)Success or fatal error pathIf size <= [0x0a5b] - [0x0a55], the helper returns the old heap top, advances [0x0a55], refreshes byte variable 8 at [0x0011], and updates high-water [0x0a5f]. If not, it displays the out-of-memory message and calls restart/exit helper 0x02ae; no recoverable allocation failure return was observed.
Temporary mark0x144b0x145aThe current heap top is saved in [0x0a5d]; restore rewinds [0x0a55] only if that mark is nonzero, then clears it.
Room/reset markStartup 0x1476, after initial setup and logic 0 load0x1485The mark at [0x0a59] is the broad cleanup target for room switch, restart, and restore. Reset helper 0x1485 frees update-list nodes, clears the temporary mark, rewinds [0x0a55] to [0x0a59], and refreshes [0x0011].
Direct rewindTemporary call_logic cleanup or source helper 0x143c(ptr)Caller-controlled0x143c stores ptr directly in [0x0a55]. It does not refresh [0x0011] by itself, so callers that need a current free-memory byte use 0x14a0.
Diagnostics0x87Modal message closeHeap-status text reports heap size [0x0a5b] - [0x0a57], current use [0x0a55] - [0x0a57], maximum use [0x0a5f] - [0x0a57], room/reset mark [0x0a59] - [0x0a57], and resource-event high-water [0x170f].

Action 0x8e configures the replay log capacity. It writes data.event.pair_capacity, flushes update-list state, resets/allocates the pair buffer through code.event.reset_pair_buffer, clears the active pair count, and rebuilds update-list state.

Save/restore data model:

ElementProducersConsumersContract
Save description bufferSave/restore selector helper0x7d, 0x7e, 0xaaRuntime buffer [0x0e72] holds the selected or entered save description/path text. The selector also edits path buffer 0x1962, fills header/description buffer 0x1c6c for newly described saves, and formats the selected filename into 0x1c8c. 0xaa copies up to 0x1f bytes from [0x0e72] into a fixed 40-byte logic string slot.
Save selector lifecycle0x7d, 0x7eDOS file I/O handlerscode.save.select_slot_or_path saves text/input state, erases the prompt marker, stops active sound, prompts for a path if needed, scans up to 12 numbered save files, displays descriptions, handles Enter/Escape/up/down selection, then restores text state and returns zero for cancel or a nonzero selection for file I/O.
Save filename/signature prefix0x8fSave/restore selector and slot readerThe string at DS:0x0002 is set by verify_game_signature, used as the third string in the %s%s%ssg.%d save filename format, and compared against the first bytes of the first saved state block when scanning restore candidates. For SQ2, the normal initialized prefix is SQ2, producing SQ2SG.N; for the observed Gold Rush v3 input, 0x8f("GR") produces GRSG.N.
Save file state blocks0x7d0x7eFiles store a 31-byte description/header followed by five little-endian length-prefixed blocks. The fixed local SQ2 block lengths are 1505, 903, 328, and 200 for global state, the object table, inventory metadata/names, and resource-event pairs. The fifth block contains variable four-byte logic-number/resume-offset records with a leading cache-head record and 0xffff terminator.
Replay pair block0x8e, resource load/display/discard actions, 0xab/0xacRestore/display-mode replayThe saved pair block length is data.event.pair_capacity * 2; active count controls how much of the buffer replay consumes.

Save/restore selector state machine:

StateEntry workUser-visible transitionOutput contract
Selector entrycode.save.select_slot_or_path records whether the normal prompt marker is visible, erases that marker, saves text/window attribute state, stops active sound, and switches to the selector text attributes.No direct choice yet; control moves to path preparation.On every exit path, the helper restores the saved text state and redraws the prompt marker only if it was visible on entry.
Path preparation/editIf data.save.path_buffer_1962 is empty, the current DOS directory helper fills it. If the runtime save-description/path buffer [0x0e72] is already nonempty, the helper skips the path prompt. Otherwise it displays the save or restore path question, edits data.save.path_buffer_1962 with a 31-character modal editor, and validates it through code.dos.validate_path. The validator skips leading spaces, fills an empty accepted string with the current directory, strips a trailing slash/backslash from multi-character paths, accepts a single slash/backslash, probes two-character drive paths such as A:, and otherwise uses DOS find-first with directory attributes.Enter accepts the edited path if validation succeeds. Escape/cancel returns zero. An invalid or unavailable path displays the insert-disk/path failure message and loops while the acknowledgement helper returns nonzero.Success leaves a validated path in data.save.path_buffer_1962; failure returns zero to the action, so save/restore continues down its cancel/failure continuation rather than performing file I/O.
Slot scanThe selector formats 1..12 candidate filenames with code.save.format_slot_filename. Restore candidates must pass code.save.read_slot_summary: open succeeds, a 31-byte description is read, the first length prefix is skipped, and seven first-block payload bytes compare against data.save.signature_prefix_0002.The selector displays available descriptions in a modal list. Empty save slots can be accepted by the save path; restore only presents candidates that pass the signature check.Each valid row has a description, slot number, and timestamp-like ordering metadata. The selected slot later formats the final filename into data.save.filename_buffer_1c8c.
Slot interactionThe current row is marked with the right-arrow glyph and the old row is cleared with a space.Enter accepts the current row; Escape returns zero; normalized movement events 1 and 5 move up and down with wrap.Enter yields a nonzero slot result. Escape/cancel yields zero and no caller file I/O should occur.
Save-description editIn save mode, accepting an empty-description slot opens a second 31-character modal editor for the save description.Enter stores the accepted description; Escape/cancel abandons the selection.Accepted text fills the 31-byte save header buffer data.save.header_description_buffer_1c6c before action 0x7d creates the file. Restore mode never writes this buffer from the selector.
Caller confirmation and I/OThe selector has restored modal state and returned to action 0x7d or 0x7e.Save and restore each display their caller-side confirmation text before doing DOS file I/O.Save create/write failures are recoverable and continue after 0x7d; restore open failure is recoverable and continues after 0x7e; restore read failure displays the fatal restore-error dialog and exits through cleanup.

Gold Rush / AGI v3 keeps the five-block save-envelope shape but changes the object/inventory block written from [0x07d6] with length [0x07da]: action 0x7d applies a repeating Avis Durgan XOR key from data address DS:0x072c before the save path and applies the same transform again before return, so the on-disk block is transformed while runtime memory is restored. QEMU extractions validate this for both blank-prefix and signed saves: build/gr-v3-behavior/save_xor_extract_qemu_001.json writes SG.1, while build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json executes 0x8f("GR"), writes GRSG.1, and confirms that the first saved-state block starts with GR\0. Both saves have block lengths 1028, 989, 1811, 100, and 12, and both third blocks change and round-trip under gr_v3_object_inventory_save_xor(). Source inspection shows the first block is the contiguous range DS:0x0002..0x0405, giving a byte-complete 1028-byte global-state partition with the same signature/variables/flags/timer/string roles as profile 2.936, an expanded 49-slot key map, no second 480-byte opaque string bank, and GR tail fields for the menu interaction and key-release gates. The observed GR OBJECT metadata decodes to item-table size 0x0189, maximum object index 22, and runtime payload length 0x0713; therefore the GR save uses 23 object records in block 2, 131 three-byte inventory entries plus a 1418-byte name pool in decoded block 3, 50 replay-pair slots in block 4, and the same four-byte logic-resume record grammar in block 5. The signed restore probe in build/gr-v3-behavior/signed_restore_roundtrip_suite.json uses a generated GRSG.1 from the original engine, restores it in a second fixture, and matches the restored capture to a direct saved-state control while differing from an unrestored control. Source inspection shows GR action 0x7e reads the same five length-prefixed blocks as SQ2, then applies the same repeating-key helper over [0x07d6]..[0x07d6]+[0x07da] after the third block has been loaded.

Restart, save/restore, and termination lifecycle:

TransitionTriggerState effectsContinuation
In-engine restart accepted0x80 confirmation succeeds, or flag 16 skips confirmationStops sound, erases input, preserves flag 9, rewinds heap/update-list state through code.heap.reset_dynamic_state, reruns initial object/inventory setup through code.restart.initialize_game_tables, refreshes display/menu state, sets flag 6, restores flag 9 if it had been set, clears timer words [0x0129] and [0x012b], and reloads trace logic [0x1d12] if configured.Returns zero to the action dispatcher, ending the current logic stream so the main loop continues from the reset state.
Restart canceled0x80 confirmation returns zero/EscapeSound has already been stopped and the prompt/input line redrawn; the reset block is skipped.Returns the following bytecode pointer, so the current logic continues.
Save create failure0x7d receives 0xffff from code.dos.create_fileDisplays the directory-full or write-protected message rooted at 0x0df0, restores modal text/window state, restores byte [0x0d15], and clears modal-state word [0x0615].Returns the following bytecode pointer; the game continues.
Save write failure0x7d short-writes the 31-byte header or any length-prefixed blockCloses the partial file, deletes filename buffer 0x1c8c, displays the disk-full message rooted at 0x0e46, restores modal text/window state, restores byte [0x0d15], and clears modal-state word [0x0615].Returns the following bytecode pointer; the game continues.
Restore open failure0x7e receives 0xffff from code.dos.open_fileDisplays the can’t-open-file message rooted at 0x0d73, restores modal text/window state, restores byte [0x0d15], and clears modal-state word [0x0615].Returns the following bytecode pointer; the game continues.
Restore success0x7e reads all five state blocksRestores scalar/object/inventory/event/cache state from the save file, restores display adapter/mode bytes, adjusts hardware-mode flag 11, replays saved resource-event pairs with recording disabled only during replay, refreshes display/resource state, clears the caller return pointer, and refreshes menu/list state.The action returns zero, so execution restarts through the restored state rather than continuing after the restore opcode.
Restore read failure0x7e fails any length-prefixed block readCloses the save file, displays the restore-error message, then enters code.system.exit_with_cleanup.Terminates the DOS process through int 21h AH=0x4c; this is not a recoverable restore failure in the observed source.
Confirmed exit or fatal helper0x86(1), confirmed 0x86(0), verification failure, allocation failure, or restore read failurecode.system.exit_with_cleanup closes the log file if open, restores interrupt hooks and timer state, sets the BIOS video mode from the saved mode byte, and calls the DOS terminate wrapper with exit code zero.DOS process exits.

Gold Rush / AGI v3 changes the prompt-marker redraw branch in action 0x80. It records data.input.prompt_marker_visible_state before erasing the marker; after confirmation it redraws the marker when restart was accepted or, for a canceled restart, only when the marker had been visible before entry. The local truth-table helper gr_v3_restart_redraws_prompt_marker() models this branch. QEMU report build/gr-v3-behavior/restart_prompt_marker_qemu_001.json confirms the canceled branch by comparing hidden and visible prompt-marker controls: hidden cancel left 0 prompt-row foreground pixels, while visible cancel restored the 8-pixel prompt glyph.

Picture decoder lifecycle:

StateEntered byExited byObservable contract
Picture cached0x18/0x19/0x1c resource helpersDiscard, room/reset cache cleanup, or replacement selectionThe resource cache stores the selected picture payload pointer. Loading records resource-event pairs unless event recording is blocked.
Fresh decode0x19 prepare path through 0x6445Scanner completion at 0xffThe logical 160 by 168 byte buffer is initialized to default cell 0x4f, draw globals are reset, and then the scanner runs over the selected payload.
Overlay decode0x1c path through 0x6440Scanner completion at 0xffThe scanner runs without the fresh-buffer fill, so valid commands compose over the existing logical buffer.
Command scannerPicture bytes read from the selected payload0xff sentinel or payload exhaustion in local toolingBytes below 0xf0 are ignored by the top-level scanner. Commands 0xf0..0xfa dispatch to drawing handlers; 0xfb..0xfe have no handler in the observed table. Raw one-byte operands for 0xf0, 0xf2, and 0xf9 consume the next byte even when it is >= 0xf0. Coordinate/list readers accept only bytes <= 0xef; a command/sentinel byte terminates the active drawing command and remains pending for the scanner.
Draw state0xf0..0xf3, 0xf9Later draw-state command or scanner endLow visual and high control nibbles can be independently enabled/disabled. Pixel writes always flow through the common writer using active draw bits and odd/even masks. In the full EGA target, the observed visual mask values are the same on odd and even rows.
Line/corner/pattern drawing0xf4..0xf7, 0xfaCommand reader sees another command/sentinel byteCorner and line commands plot into the logical byte buffer with coordinate clamping. Relative-line endpoints use 8-bit coordinate arithmetic before the high-side clamp, so a negative underflow becomes the right or bottom edge rather than zero. Diagonal lines use the interpreter’s byte-width accumulator behavior. Pattern plotting uses profile-local AGIDATA tables. Full-brush v2 profiles can linearly write a computed X 160 as X 0 on the next row; v3 uses a two-pixel radius-one center row and a tighter horizontal clamp that prevents this valid-stream wrap.
Seed fill0xf8Seed list terminatesThe selected expansion-test channel is visual when visual drawing is enabled, otherwise control when control drawing is enabled. The write itself still uses the common active-channel writer, so accepted cells can update both nibbles when both channels are active. A clean implementation may use any finite connected-region traversal that produces the same final logical buffer for valid data.
Display finalization0x1a, replay/display helpers, object pipeline refreshNext draw/update/display transitionThe decoded logical buffer is converted to visible output by the active EGA display path, with text/modal overlays handled separately from picture decoding.

View and cel drawing contract:

ElementImplementation contract
View payloadBytes +0x00/+0x01 are reserved in the current SQ2 model; all local resources contain 0x01,0x01 and observed runtime paths begin meaningful parsing at +0x02. Header byte +0x02 is the group count. Group offsets are relative to the payload base from +0x05; frame offsets are relative to their group base. Frame bytes are width, height, control byte, then row-terminated run data.
Cel rowsEach nonzero row byte encodes color in the high nibble and run length in the low nibble. A zero byte ends a row. The control low nibble is the transparent color; transparent runs advance X without writing.
Orientation rewriteIf frame control bit 0x80 is set and bits 0x70 do not match the selected group, the interpreter rewrites the frame stream in place so visible row runs are mirrored horizontally and bits 0x70 cache the selected group.
Baseline placementThe draw top is baseline_y - frame.height + 1. Placement helpers search/clamp requested object coordinates before activation/transient drawing, including horizon, screen bounds, collision, and control-buffer acceptance.
Priority/control gateVisible object pixels compare the object’s low priority nibble against the destination high nibble. High nibbles greater than 0x20 are compared directly; lower classes trigger a downward scan in the same column until a comparable high nibble is found or the lower buffer limit is reached. A pixel draws when the discovered value is less than or equal to the object’s priority.
Pixel writeNontransparent cel pixels write `(object_priority << 4)
Transient versus persistentAction 0x7a draws a transient view/cel against the current picture state. Persistent objects bind view payloads into 43-byte object records, enter update lists when activated, and are redrawn/restored by the object/update pipeline.

Persistent placement and movement accept a proposed object position only when the object-object crossing test accepts it and the control-buffer scan accepts the selected frame-width scanline. In the control scan, class 0x00 rejects immediately, class 0x10 requires object bit 0x0002, class 0x20 leaves final state (flag3=true, flag0=false), class 0x30 leaves final state (flag3=false, flag0=true), and other nonzero classes leave (flag3=false, flag0=false). The final scanned class, not an accumulated history of earlier cells, drives the post-scan 0x0100 and 0x0800 rejection bits and the global flag updates for objects whose byte +0x02 is zero. Priority/control byte 0x0f bypasses this scan.

Object drawing lifecycle:

StateEntered byExited byObservable contract
Empty/reset recordreset_object_state, room reset, startupView bind and field setupActive flag is clear; drawing and movement passes ignore the record.
Bound frameset_object_resource*, group/frame selectorsPosition/activation changes or discard/resetRecord has a view payload, selected group/frame pointer, width, and height.
PlacedPosition setters and placement helper 0x593aActivation, movement, or another position setterCoordinates have been clamped/searched against bounds, horizon, collision, and control-buffer acceptance. Dirty bit 0x0400 suppresses one movement delta and is then cleared.
Active/listedactivate_objectdeactivate_object, reset, room switchActive/update bits include 0x0001; update-list roots are flushed/rebuilt and render nodes may save backing rectangles.
RenderedUpdate-list processing or transient draw helperList flush/rectangle restore or redrawThe selected frame’s run data is composited into the logical buffer using transparent-color skip and priority/control gating. Dirty refreshes copy the union of the previous saved footprint and the current footprint, where top is baseline_y - height + 1.

Motion and animation lifecycle:

StateEntered byExited byObservable contract
StationaryMotion mode +0x22 is zero or direction +0x21 is zeroScript motion action or autonomous mode dispatchPer-cycle movement writes no new position. Frame animation may still run when bit 0x0020 is set.
Countdown gatedObject byte +0x01 is nonzeroCountdown reaches zeroMovement is skipped until due; the countdown reloads from +0x00 after a due cycle.
Autonomous mode stepMode 1, 2, or 3 with due countdownDirection computed or completion helperMode 1 picks/randomizes direction, mode 2 approaches object 0 with stuck recovery, and mode 3 recomputes direction toward target X/Y. Completion restores step size, sets the configured flag, and clears mode.
Proposed moveDirection and step produce candidate X/YAccept/reject testsCandidate is clamped to screen/horizon bounds; boundary globals are recorded when a clamp survives.
Accepted or restoredControl/collision tests completeNext cycleAccepted moves keep the candidate coordinates. Rejected moves restore saved coordinates, clear boundary code, and run placement search.
Frame callbackBit 0x0020 set and frame timer reaches zeroFrame mode handlerMode byte +0x23 advances or wraps frames, with one-callback startup delay bit 0x1000 where applicable.

Gold Rush / AGI 3.002.149 source comparison adds two version-specific motion notes. First, object motion mode 4 dispatches to the same target-direction helper as mode 3; SQ2 treats modes above 3 as no autonomous-mode dispatch. The mode-4 dispatcher branch is now validated with an instrumented GR fixture: a copied interpreter under build/ changes action 0x51’s mode seed from 3 to 4, and QEMU shows the resulting capture matches the unmodified mode-3 target-movement capture while a stationary control does not. That confirms the internal dispatcher behavior, but the natural engine path that seeds mode 4 remains source-only rather than script-visible.

Second, GR’s automatic direction-to-loop selection changes the high group-count branch. Exactly-four-loop views use the four-plus direction table without an extra flag check, while views with more than four loops use that table only when flag 0x14 is set. QEMU report build/gr-v3-behavior/frame_selection_gate_qemu_001.json validates exact-four view 177 selecting group 1 for direction 6 whether flag 0x14 is clear or set, and more-than-four view 39 remaining on group 0 until flag 0x14 is set.

Text/input UI lifecycle:

StateEntered byExited byObservable contract
Input line hidden/disabled0x77 or display/text cleanup paths0x78Word [0x05d3] is zero; refresh helpers should not redraw the normal input line.
Input line enabled0x780x77, modal prompts, or text-window cleanupWord [0x05d3] is one; 0x78 redraws/clears the configured input row in the normal EGA path, 0x89 redraws from the entered source input buffer, and 0x8a erases visible input characters.
Prompt/status configured0x6c, 0x6f, 0x70, 0x71Later configuration or cleanupPrompt character, row/column-like globals, status-line enable word [0x05d9], and display offset [0x1379] determine where text helpers draw and erase. QEMU validates that setting the prompt marker from an empty message suppresses marker drawing on the next input-line redraw, and that 0x70 visibly redraws the configured status row.
Modal text window activeMessage display, prompt/edit, menu, or diagnostic helpers0xa9 or the helper’s own close pathThe message-window opener at 0x1d96 first closes any already active saved window, formats the text, applies temporary 0x97/0x98 row, column, and width overrides when present, computes packed rectangle words [0x0d23] and [0x0d25], draws/saves the boxed region through helper 0x5590, then sets [0x0d1d] = 1. Closing through 0xa9/0x1f2b restores that saved rectangle through helper 0x560c([0x0d23], [0x0d25]) when active, then clears [0x0d0f] and [0x0d1d].
Alternate text/input-width mode0x6a, 0xa3, and related display-mode helpers0x6b, 0xa4, or 0xa9/cleanup pathsByte [0x1757] alters text drawing, while word [0x0d0f] changes input-character width limits in helper 0x3652. QEMU validates that 0x6a clears the visible logical surface using the current text attribute pair in the observed EGA path, that 0x6b restores ordinary picture/object drawing, that 0xa3 permits wrapped live input with a long blank string slot 0, and that both 0xa4 and inactive 0xa9 clear the width flag.
Event/edit loopcode.input.edit_string, menus, inventory selection, confirmation dialogs, keyboard IRQ hookEnter, Escape, selected mapped/status event, or tracked key releaseThe shared event queue feeds raw key predicates, line editors, menu status-byte events, and confirmation exits. SQ2 action 0xad increments byte [0x1530]; GR v3 action 0xad sets byte [0x0405], and GR-only 0xb5 clears it. In both observed hooks, selected scan codes 0x47..0x51 use an enable table and one pressed latch; release clears the latch and enqueues type-2 event value 0 only when the gate byte is nonzero. tools/agi_input.py models this source-backed latch contract.

Menu/list data model:

ElementSource-backed layoutContract
Heading node18-byte heap node. Offsets +0x00 and +0x02 are next/previous heading links; +0x04 is the heading text pointer; +0x08 is the heading column; +0x0a is an enabled/usable word; +0x0c is the circular item-list root; +0x0e remembers the current item for this heading; +0x10 is the item count.Headings form a circular list rooted at data.menu.heading_root. Empty headings are marked disabled during later setup/finalization. Left/right navigation skips disabled headings.
Item node14-byte heap node. Offsets +0x00 and +0x02 are next/previous item links; +0x04 is the item text pointer; +0x06 is the menu row/order; +0x08 is the menu column; +0x0a is the enable word; +0x0c is the script-visible item id.Items form a per-heading circular list. Item navigation does not skip disabled entries; the enable word is checked only when Enter is pressed.
Menu globalsdata.menu.finalized, data.menu.request_interaction, data.menu.heading_root, data.menu.current_heading, and data.menu.current_item.Setup opcodes populate the list until finalized. Interactive movement persists the current heading/item before each event-loop iteration so later menu openings resume from the remembered position.

Gold Rush / AGI v3 adds one extra gate in front of the shared menu interaction path. Action 0xb1 stores its immediate byte in word [0x0403]; the GR code.menu.interact routine returns immediately while that word is zero, even if action 0xa1 has requested interaction. QEMU report build/gr-v3-behavior/menu_gate_suite.json validates this observable split: 0xb1(0) plus a menu request matches a blocked control, while 0xb1(1) plus the same request produces a distinct modal-menu capture.

Menu interaction lifecycle:

StateEntry workEvent/transitionOutput contract
Setup mutable0x9c allocates and links heading nodes; 0x9d allocates and links item nodes under the current heading.Setup continues until 0x9e. If a heading has no item list, setup marks its enable word zero.List nodes live on the interpreter heap. After finalization, later 0x9c/0x9d calls are ignored by the observed handlers.
Setup finalized0x9e records the heap room/reset mark, sets current_heading to the root heading, sets current_item from the heading’s item root, and sets the finalized word.0x9f and 0xa0 can later enable or disable item ids by walking all heading/item lists.The circular lists remain active until the broader heap/menu state is rebuilt by restart/restore/room lifecycle helpers.
Interaction requested0xa1 tests flag 14; when set, it writes data.menu.request_interaction = 1. The input/main-loop path enters code.menu.interact and clears the request word on exit.No direct selection occurs in 0xa1; it only requests the modal menu path.QEMU validates the request plus one-item Enter, Escape, disabled-Enter, and re-enable paths.
Menu openedcode.menu.interact clears/redraws the text row, redraws each heading, loads current_heading/current_item, and draws the active heading and its item rectangle.The loop waits through the shared input/event helpers, then branches on normalized event type.The modal menu owns the visible text surface while active; tests refresh the picture after closing before comparing graphics output.
Raw-key eventEvent type 1 supplies a raw key word.Enter (0x000d) selects only if the current item’s enable word is nonzero; Escape (0x001b) exits without selection; other keys loop.Enter on an enabled item enqueues event type 3 with the item’s id. Enter on a disabled item leaves no status event and continues waiting.
Movement eventEvent type 2 supplies movement value 1..8, mapped from the local raw-key table.Values dispatch through table.menu.navigation_dispatch: previous item, first item, next enabled heading, last item, next item, last heading, previous enabled heading, and root heading.Movement redraws/unhighlights affected rows, remembers a heading’s current item before leaving it, stores updated current_heading and current_item, and waits for another event.
Exit cleanupSelection or Escape calls the helper that remembers the active item for the heading and restores the saved menu rectangle.Status line is redrawn when enabled; otherwise the associated row is cleared.data.menu.request_interaction is cleared. Selection is observable through condition 0x0c on the enqueued item id.

Text rectangle clears (0x69 and 0x9a) are display-surface operations. They do not decode or mutate a picture resource; they overwrite the visible text-cell rectangle with the requested attribute/background. In the EGA target validated so far, text columns are four logical pixels wide and text rows are eight logical pixels tall.

Status-line hide (0x71) and input-line disable (0x77) use the same visible text surface rather than the decoded picture buffer. In the normal EGA path, 0x71 clears the single row configured as the status row by 0x6f operand 2, and 0x77 clears the single row configured as the input row by 0x6f operand

  1. The validated row geometry is eight logical pixels tall.

Input-line enable (0x78) also redraws the configured input row through the text surface before it writes any configured prompt/input strings. With an empty prompt marker and empty input buffers, the validated visible result is the same single eight-pixel-tall black row. Input-line erase (0x8a) uses repeated backspace handling to reduce visible input length [0x0ff8] to zero; QEMU validates that typed live-edit glyphs disappear from the configured input row. Input-line refresh (0x89) does not replay unaccepted live-edit characters in the observed path. After Enter copies accepted text into the source input buffer at 0x0fce, 0x89 repaints visible glyphs by copying that source into visible buffer 0x0fa4. Entering alternate text-attribute mode through 0x6a is broader: the observed EGA result clears the full logical surface using the current attribute pair and ordinary transient-object composition is not visible while that mode remains active in the probe. With the default pair this is a black clear. After 0x6d(0, 1), the disassembly shows the stored pair is reinterpreted by 0x6a with [0x1757] = 1 as packed text attribute 0xf0; QEMU validates a full-screen visual color 15 clear for that case.

The prompt marker configured by 0x6c is a single byte copied from the start of the resolved message. When that byte is zero, input-line redraw skips marker drawing. The current QEMU evidence covers the empty-message suppression path; nonempty prompt glyph shape is still a font/text-rendering detail rather than a fully modeled compatibility contract.

Menu interaction is another consumer of the shared event queue. Menu setup builds circular heading and item lists; each node’s word at offset +0x00 is the next pointer and +0x02 is the previous pointer. A heading’s item root is at +0x0c, its remembered/current item is at +0x0e, and its active/enabled word is at +0x0a. Item nodes store their enabled word at +0x0a and their script-visible selection id at +0x0c. Enter enqueues a type-3 event carrying that id only when the current item is enabled. Movement events use values 1..8: up/previous item, page-up/first item, right/next enabled heading, page-down/last item, down/next item, end/last heading, left/previous enabled heading, and home/root heading. After each movement, the engine persists the current heading and item in [0x1d2e] and [0x1d30].

Diagnostic and Trace Services

Several action opcodes are developer-facing or VM-facing services rather than ordinary game-world operations:

  • 0x87 (show_heap_status) formats heap/script memory counters into a message box.
  • 0x1d (show_priority_screen) temporarily swaps the high and low nibbles of every logical graphics-buffer byte during a full-screen refresh. The observed command phrase is show pri, and the displayed buffer is best modeled as a priority/control inspection view.
  • 0x95 (enable_action_trace_window) and 0x96 (configure_action_trace_window) manage an action-trace window. When enabled, the dispatcher calls the trace formatter before each action handler. QEMU validates the enabled path as a red-bordered, white-filled trace window with black opcode/operand text. A new implementation can expose this as optional VM tracing rather than coupling it to normal game semantics.
  • 0x8f (verify_game_signature) resolves a short message string and compares it against the interpreter’s embedded SQ2 signature. A mismatch calls the same restart/exit helper used by confirmation-gated exit paths.

UI, diagnostics, and device state

Several actions are better modeled as services around the VM rather than as game-world operations:

  • The input line has an enabled flag at [0x05d3], a visible/input buffer at 0x0fa4, an entered source buffer at 0x0fce, and a length word at [0x0ff8]. Actions 0x77, 0x78, 0x89, and 0x8a disable, enable, refresh, and erase that line.
  • Action 0x88 is a pause dialog. It stops sound, displays a fixed pause message, and returns to the script after the display helper completes.
  • Action 0x87 is a heap/status diagnostic. It formats heap and script-budget globals into a message, so a replacement implementation can expose it as a diagnostic overlay without tying it to game state.
  • Action 0x6e is a transient display shake. In the normal path it does not mutate the logical picture/object buffers; it writes CRT/display-register offsets from a small table and waits on timer ticks. A portable implementation can model the observable contract as a short camera/display offset animation with no persistent surface change.
  • Action 0x8b is joystick calibration. It presents a centered-joystick prompt, samples joystick state through helpers around 0x63be and 0x6425, and stores calibrated bounds in globals near 0x15c9..0x15cf.
  • Action 0x8c toggles bit 0 of display mode word [0x1130] when the current hardware/display configuration allows it, then rebuilds display state.

Sound and audio

This chapter describes the interpreter-visible sound contract observed so far. It is intentionally scoped to resource parsing, playback scheduling, completion flags, and driver output at the port-write boundary. It does not attempt to specify analog waveform synthesis beyond the bytes and PC-speaker divisor the interpreter emits.

Evidence for this chapter comes from the SQ2 executable disassembly, the symbolic labels in Symbolic Labels, local resource parsing helpers in tools/agi_sound.py, and regression tests in tests/test_sound_resources.py.

Sound resource payloads

A loaded sound payload begins with four little-endian words. Each word is a payload-relative offset to one channel stream.

BytesMeaning
0x00..0x01Channel 0 stream offset.
0x02..0x03Channel 1 stream offset.
0x04..0x05Channel 2 stream offset.
0x06..0x07Channel 3 stream offset.

Each channel stream is a sequence of duration/tone/control records terminated by the duration word 0xffff.

FieldSizeMeaning
Duration16-bit little-endianThe countdown value loaded after this event is consumed. The value 0xffff is a terminator and has no following tone/control bytes.
Tone word16-bit little-endianEncoded tone/control value consumed by the hardware driver path.
Control byte8-bitThe low nibble is the observed attenuation/control value. 0x0f is the silent value on the PC-speaker path.

The local SQ2 corpus has 49 present sound resources. All present SQ2 sound payloads observed so far have four sorted, in-bounds channel offsets and a first channel offset of 8, immediately after the four-word header. All four channels in every present payload parse to a 0xffff terminator.

One concrete parser check uses sound 1:

PropertyObserved value
Channel offsets(8, 15, 22, 29)
Channel 0 first duration0x0027
Channel 0 first tone word0x8037
Channel 0 first control byte0x9f
Channel 0 terminator offset13

Load and playback actions

The logic bytecode exposes sound through three action opcodes.

OpcodeSymbolic nameSource-backed behavior
0x62load_soundLoads and caches a sound-like resource. The cache record stores the resource number and four derived channel stream pointers.
0x63start_sound_with_flagStops any currently active sound state, stores the supplied completion flag number, clears that flag, finds the loaded sound resource, and starts the driver.
0x64stop_sound_or_clear_sound_stateStops active sound state if present. When a sound was active, the stop path silences the driver and sets the stored completion flag.

The current symbolic labels for the source paths are:

LabelSQ2 image addressRole
code.sound.load_resource0x5126Sound resource loader/cache path.
code.sound.find_loaded_resource0x50d8Lookup used before playback start.
code.sound.start_with_flag0x51d3Action handler for opcode 0x63.
code.sound.stop_or_clear_state0x5234Shared stop helper used by opcode 0x64 and before starting another sound.
code.sound.driver_start0x7f96Low-level playback initialization.
code.sound.driver_tick0x801cTimer-driven playback tick.
code.sound.driver_stop_core0x80c1Shared low-level stop/completion helper.

start_sound_with_flag requires the sound to have been loaded first. In the current QEMU dispatch probes, a generated logic script calls load_sound before start_sound_with_flag, then observes that stop_sound_or_clear_sound_state sets the expected completion flag.

Runtime playback state

Driver start copies the four cached channel pointers into runtime state and initializes each channel countdown to 1. That means the first event, or the first channel terminator, is read on the first active sound tick.

Important runtime globals are:

LabelSQ2 data addressMeaning
data.sound.active_state0x1258Nonzero while sound playback is active.
data.sound.completion_flag0x126aThe flag number set by the stop/completion path.
data.sound.channel_stream_pointers0x1788..0x178fCurrent stream pointers for the four channels.
data.sound.channel_countdowns0x1790..0x1797Per-channel countdown words.
data.sound.channel_active_words0x1798..0x179fPer-channel active markers.
data.sound.channel_attenuation0x17a8..0x17afPer-channel low-nibble attenuation/control values.
data.sound.active_channel_byte_limit0x1804Tick-loop limit: 2 means channel 0 only, 8 means channels 0 through 3.
data.sound.remaining_active_channels0x1806Decremented when active channels terminate. Zero triggers completion.

At the start of each tick, the driver tests flag 9. If flag 9 is clear, the driver stops immediately and sets the stored completion flag. This is modeled by sound_completion_tick(..., sound_flag_9_set=False) == 1 in the local tests.

The active channel set depends on the hardware selector at data 0x112e:

Hardware selectorActive channels
0Channel 0 only.
8Channel 0 only.
Other observed valuesChannels 0, 1, 2, and 3.

After an event is read, its duration word becomes the next countdown. A duration of zero wraps through the 16-bit countdown behavior and delays the next record read for 65,536 ticks. The local SQ2 corpus has no zero-duration sound events, but the behavior is source-modeled and covered with a synthetic unit test.

Natural completion occurs when every active channel reaches a 0xffff terminator. The stop/completion helper clears active state, silences the driver, and sets the stored completion flag.

Tone output boundary

The source-backed model stops at the driver output boundary. A replacement engine does not need to reproduce port I/O internally, but it does need to produce the same interpreter-visible scheduling, silence, and completion behavior for valid resources.

For hardware selectors 0 and 8, the driver uses a PC-speaker-style path. An event whose attenuation nibble is 0x0f disables the speaker gate. Otherwise the tone word is converted to a PIT divisor:

divisor = 12 * (((tone_word & 0x3f) << 4) + ((tone_word >> 8) & 0x0f))

The source writes mode byte 0xb6 to port 0x43, writes the divisor low byte then high byte to port 0x42, and sets bits 0 and 1 at port 0x61. Silence clears those port 0x61 bits.

For other observed selectors, tone output writes encoded bytes to port 0xc0. The driver writes the high tone byte first. It writes the low tone byte too unless the high byte has top bits 0xe0, in which case the high byte alone is written. The stop path writes the four silence bytes:

0x9f 0xbf 0xdf 0xff

Attenuation and envelope boundary

The non-PC-speaker attenuation path combines a channel mask with a low-nibble attenuation value.

ChannelOutput mask
00x90
10xb0
20xd0
30xf0

The default attenuation envelope table starts with:

0xfe 0xfd 0xfe 0xff 0x00 0x00 0x01 0x01 ...

and ends with sentinel byte 0x80.

While an envelope is active, the table byte is interpreted as a signed delta from the event’s base attenuation, not as a cumulative delta from the previous envelope value. The result clamps into the attenuation range 0..0x0f. When the envelope sentinel 0x80 is reached, the helper disables the envelope and copies the current envelope value back into the base attenuation.

Selector 2 has one additional observed adjustment: after the normal envelope and global adjustment steps, a non-silent output attenuation below 8 is increased by 2.

Compatibility evidence

Local parser/model evidence is in tests/test_sound_resources.py. That suite checks the present SQ2 sound corpus, event parsing, active-channel selection, duration scheduling, flag-9 early stop behavior, PC-speaker divisor generation, non-PC tone byte writes, stop silence bytes, channel masks, and attenuation envelope semantics.

QEMU evidence currently covers dispatch and completion-flag behavior for the logic opcodes that load, start, and stop sound. The current compatibility chapter records the commands for:

  • sound_load_stop_dispatch_smoke
  • sound_start_stop_dispatch_smoke
  • sound_stop_sets_completion_flag

Optional future dynamic work could validate natural completion timing against the original engine, but the current implementation-facing model is already source-backed at the interpreter and driver-output boundary. Analog waveform synthesis remains outside the present clean-room compatibility target.

Graphics and object pipeline

This page is a higher-level synthesis of the interpreter code paths that load picture and view-like resources, bind view data to 43-byte object records, and drive the drawing/update lists. Names remain provisional where the disassembly shows a stable mechanism but not yet a final user-level concept.

Resource caches feeding graphics

The resource loaders described in Resource Files feed two graphics-facing caches:

ResourceLoaderCache lookupDirectory accessorPayload use
View-like0x39f70x39790x43a5Stored on object records and parsed into subresource pointers.
Picture-like0x4a3b0x49e80x43d9Stored globally at [0x1377] before picture decoding.

Both caches use small linked records whose byte at +0x02 is the resource number and whose word at +0x03 is the loaded payload pointer. The view cache is rooted at [0x0ffa]; the picture cache has a first static record at 0x120e and a linked tail/root variable at [0x1214].

Several loaders and mutating object actions wrap their work with:

0x6a54: clear/flush update lists rooted at 0x16ff and 0x1703
0x6a8e: rebuild and process those lists

That wrapper pattern is the strongest current evidence that resource changes, picture changes, and object field changes all participate in one redraw/update pipeline.

Picture flow

Logic action 0x18 (load_picture_var) enters at 0x4a16. It reads the picture-like resource number from var[arg0] and calls 0x4a3b.

0x4a3b first checks the picture cache through 0x49e8. On a miss it:

  1. Calls 0x6a54.
  2. Records pair (2, picture_number) through 0x70b1.
  3. Allocates or uses a 5-byte cache record.
  4. Calls picture directory accessor 0x43d9.
  5. Calls the generic volume reader through 0x4a90 -> 0x2e32.
  6. Stores the returned payload pointer at cache record +0x03.
  7. Calls 0x6a8e on successful load.

Logic action 0x19 (prepare_picture_var) enters at 0x4aaa, reads the picture number from a variable, and calls 0x4acf. That helper requires the picture to be cached, stores the payload pointer in global word [0x1377], and then calls:

0x6a54
0x6445
0x6a8e

Afterward it clears word [0x1216].

Logic action 0x1a (show_picture_like) enters at 0x4b82. It clears flag 15 through 0x74d0, calls 0x1f2b(0), calls display helper 0x5546, and sets word [0x1216] = 1. Source and QEMU resource-lifecycle probes agree on the screen-visible distinction: 0x19 decodes the selected picture payload into the logical graphics buffer, while 0x1a copies/finalizes that logical state to the display. The overlay_picture_var_composes_extra_picture probe showed that an overlay performed through 0x1c was not visible until a following 0x1a refresh made the composed logical picture visible.

Logic action 0x1c (overlay_picture_var) also selects a cached picture-like payload into [0x1377], but its helper path calls decoder entry 0x6440 instead of 0x6445. Since 0x6445 first clears/fills the graphics/control buffer and then falls into 0x6440, 0x1c appears to be the picture path that draws without that extra clear step.

Restore and display-mode replay reconnect to this same picture/object pipeline through the resource-event log. Replay event kind 4 calls the normal picture prepare/decode helper (0x4acf) for a previously recorded picture, while kind 8 calls the overlay prepare helper (0x4b3b). Kind 5 is not a resource load; it restores the three staged byte pairs at 0x0eae..0x0eb3 and calls code.object.setup_transient_display_object (0x2d52), which feeds the temporary object into the object drawing path. Replay disables event recording while it consumes the pair stream, then reaches image 0x6927 and calls code.event.enable_recording before rebinding object views and refreshing the display. A display-mode replay QEMU probe showed that the event log can exclude an unrecorded or rolled-back picture while the visible CGA-style background after 0x8c becomes row-interleaved. Source inspection now points to CGA color/display remapping of the recorded picture after [0x1130] toggles, not to the unrecorded picture surviving in the logical buffer. That keeps this display artifact outside the full 16-color EGA target path.

Picture decoder

Picture decoding starts at 0x6445 or 0x6440. The 0x6445 entry performs an extra setup call:

ax = 0x4f4f
call 0x5257

Both entries then reach 0x644e, which initializes drawing globals:

[0x1369] = 0
[0x15ee] = 0
byte [0x136d] = 0xff
byte [0x136e] = 0xff
call 0x6475
if [0x1130] == 2: call 0x9899

The command scanner at 0x6475 walks the payload pointer in [0x1377]. It reads bytes until 0xff, ignores bytes below 0xf0, and dispatches command bytes 0xf0..0xfa through the table at 0x15d6.

The current SQ2 table at DS:0x15d6 is:

0xf0 -> 0x6494
0xf1 -> 0x64b5
0xf2 -> 0x64c7
0xf3 -> 0x64ed
0xf4 -> 0x6612
0xf5 -> 0x6603
0xf6 -> 0x6646
0xf7 -> 0x665e
0xf8 -> 0x66ab
0xf9 -> 0x6524
0xfa -> 0x64ff

A local scan of all 74 valid SQ2 picture payloads found these command-byte counts. The scan treats bytes >= 0xf0 as command/sentinel bytes; picture data bytes are accepted by the coordinate readers only when they are <= 0xef.

ByteCount
0xf04746
0xf1309
0xf21018
0xf3425
0xf67736
0xf79282
0xf81447
0xf922
0xfa701
0xff74

No local SQ2 picture payload currently uses command 0xf4 or 0xf5, even though both handlers are present in the interpreter dispatch table.

0xf0 (set_visual_draw_nibble): Handler 0x6494 reads one byte, passes it through display-dependent mapper 0x5685, stores AL in [0x136b], enables the low nibble of draw word [0x1369], and updates the even/odd masks [0x136d] and [0x136e]. This is the low-nibble drawing channel used for visible picture color in the current model. This operand is a raw byte read, not the coordinate/data-byte reader, so a byte >= 0xf0 immediately after 0xf0 is consumed as the visual operand rather than treated as a command.

0xf1 (disable_visual_draw_nibble): Handler 0x64b5 clears the low nibble of [0x1369] and opens the low nibble in both write masks.

0xf2 (set_control_draw_nibble): Handler 0x64c7 reads one byte, shifts it into the high nibble, stores it in [0x136c], enables the high nibble of [0x1369], and updates both masks. This is the high-nibble drawing channel later consumed by object movement/control tests. Like 0xf0, this consumes a raw operand byte even when that byte is >= 0xf0.

0xf3 (disable_control_draw_nibble): Handler 0x64ed clears the high nibble of [0x1369] and opens the high nibble in both write masks.

0xf4 (draw_corner_path_y_first): Handler 0x6612 reads an initial coordinate pair through 0x66b8, plots it, then reads a Y coordinate through 0x66d4 and draws a vertical segment through 0x52ab. It then alternates with X-coordinate reads and horizontal segments through 0x526f. A byte above 0xef terminates the command and is left for the main scanner as the next picture command byte.

0xf5 (draw_corner_path_x_first): Handler 0x6603 is the same corner path family, but after the initial plotted coordinate it reads X first and draws a horizontal segment before alternating to vertical segments.

0xf6 (draw_absolute_lines): Handler 0x6646 reads and plots an initial coordinate pair, then repeatedly reads absolute coordinate pairs and draws a line from the previous point to the new point through 0x66e1.

0xf7 (draw_relative_lines): Handler 0x665e reads and plots an initial coordinate pair, then consumes relative-step bytes while they are <= 0xef. Bits 0x70 encode an X delta magnitude shifted down four bits, bit 0x80 chooses subtraction instead of addition for X, bits 0x07 encode a Y delta magnitude, and bit 0x08 chooses subtraction instead of addition for Y. Each decoded endpoint is computed in the byte register holding the current coordinate, so subtraction can underflow to a high unsigned byte. The handler then only clamps values above the maximum (x > 0x9f to 0x9f, y > 0xa7 to 0xa7); it does not separately clamp negative deltas to zero. The resulting endpoint is connected with 0x66e1.

0xf8 (seed_fill): Handler 0x66ab repeatedly reads coordinate pairs and calls helper 0x533b. The helper chooses exactly one expansion test channel for each seed. If the low visual draw nibble is enabled, it expands through cells whose low nibble is 0xf. Otherwise, if the high control draw nibble is enabled, it expands through cells whose high nibble is 4 (0x40 in the buffer byte). If neither channel is enabled, the command has no effect.

The selected channel also supplies the no-op test. A selected visual fill value of 0xf, or a selected control high-nibble value of 4 (0x40 in the buffer byte), exits before scanning. The seed cell must already match the selected default target, or the helper exits. Once a cell is accepted, the write itself still goes through the normal active draw byte and odd/even masks, so both nibbles may be updated when both channels are active even though only one channel controls expansion.

The SQ2 executable implements this as a stack-backed horizontal span fill. It writes the current horizontal run left and right, records the accepted span limits in the scratch block around 0x126c..0x1279, scans the adjacent row in one vertical direction for matching target cells, pushes deferred span state on the CPU stack when a branch must be revisited, then reverses direction or pops the next deferred span until a sentinel row value ends the fill. For a portable clean-room implementation, the observable contract is the filled connected region under the selected-channel target test plus the normal pixel-write rule; a queue- or stack-based flood fill is equivalent for valid finite picture data if it produces the same final logical buffer.

0xf9 (set_pattern_mode): Handler 0x6524 stores the next byte in [0x15ee]. The low three bits select one of eight pattern masks through the pointer table at DS:0x1619; bit 0x10 bypasses one mask test in the patterned plotter; bit 0x20 makes command 0xfa consume an additional byte into [0x15f8] before each patterned draw. The mode byte is also a raw operand byte, so command-looking values are legal operands for the source scanner.

0xfa (pattern_plot): Handler 0x64ff repeatedly reads coordinate pairs, then calls helper 0x652a. That helper clamps a small rectangle around the coordinate, selects a pattern pointer from DS:0x1619, and conditionally plots pixels through 0x52f9 using pattern words and the bit masks rooted at DS:0x15f9.

The helper’s observed algorithm is:

  1. radius = [0x15ee] & 0x07.
  2. Pattern row words are read from the pointer table at DS:0x1619; the local table has 2 * radius + 1 row words for each radius.
  3. The X start is clipped from (x * 2 - radius) / 2, with a right-side clamp derived from 0x140 - radius * 2. The inner loop draws radius + 1 columns.
  4. The Y start is clipped from y - radius, with a lower clamp derived from 0xa7 - radius * 2. The outer loop draws 2 * radius + 1 rows.
  5. Unless mode bit 0x10 is set, the current row word must overlap the current column mask. The column masks read from DS:0x15f9 + column * 4 are 0x8000, 0x2000, 0x0800, 0x0200, 0x0080, 0x0020, 0x0008, and 0x0002.
  6. When mode bit 0x20 is set, the byte in [0x15f8] is ORed with 1 and then advanced for every candidate pixel: shift right once, XOR with 0xb8 if the shifted-out carry was set, then draw only when bit 0 is clear and bit 1 is set.

Cross-interpreter brush audit

A structural audit of every currently selected local interpreter maps the picture scanner, 0xf9/0xfa handlers, column-mask signature, radius-pointer table, and horizontal-clamp immediate without assuming SQ2 addresses. It finds four behavioral groups:

ProfilesPicture-command behavior
2.089, 2.272The scanner ends at 0xf8; pattern commands are unavailable.
2.4110xf9 consumes and ignores one byte; 0xfa plots ordinary single pixels from coordinate pairs. No brush tables are present.
2.439 through 2.936Full shaped/stippled brushes; radius 1 uses rows e000 e000 e000; horizontal clamp constant 0x0140.
3.002.086 through 3.002.149Full shaped/stippled brushes; radius 1 uses rows 4000 e000 4000, yielding only the two center-row logical pixels under the normal mask; horizontal clamp constant 0x013e.

The column masks are identical in every full-brush build. Radius 0 and radii 2 through 7 are also byte-identical; only radius 1 changes. A normalized comparison of the 0xfa routines found the clamp immediate to be the only instruction difference between the full v2 and v3 brush cores. Mode-bit tests, stipple recurrence, iteration order, and common pixel writes otherwise match.

The data offsets relocate as follows:

BuildsDispatchMasksRadius pointers
BC 2.439, LSL1 2.4400x15520x15750x1595
MG 2.915, KQ1/SQ1.22/PQ1 2.9170x15cc0x15ef0x160f
SQ2/KQ3 2.9360x15d60x15f90x1619
KQ4 3.002.0860x16460x16690x1689
KQ4D/replacement MH1 3.002.102; prior MH1 3.002.1070x16580x167b0x169b
GR/MH2 3.002.1490x140d0x14300x1450

The reusable audit is tools/brush_table_audit.py; it requires every game directory explicitly and writes generated reports under build/. The local renderer now finds these tables by their structure instead of using SQ2’s offsets.

The pattern plotter does not maintain a separate visual/control channel path. When a candidate pixel passes the pattern tests, helper 0x652a writes AH = x, AL = y to [0x150b] and calls the common pixel writer 0x52f9. That writer selects [0x136d] for odd Y rows and [0x136e] for even Y rows, ORs in active draw bits from [0x1369], and ANDs with the selected mask. In the full 16-color EGA path, code.display.map_visual_color_for_adapter returns the same visual nibble in both registers, so visual odd/even masks are identical; the parity branch is still part of the implementation contract because other display modes can populate the two masks differently. Local renderer tests now cover pattern writes with both channels active, with visual disabled, and with control disabled. QEMU snapshot batch pattern_channel_masks_001 confirms the visible EGA surface for those valid streams; the control-buffer nibble remains source-backed plus local cell-test evidence because screenshots expose only the visual channel.

Coordinate readers are shared across the command handlers. Helper 0x66c1 reads X into AH, accepts bytes <= 0xef, clamps values above 0x9f down to 0x9f, and returns carry set on a command/sentinel byte. Helper 0x66d4 does the same for Y in AL, clamping above 0xa7. Helper 0x66b8 reads a full X/Y pair by calling those two helpers. Command/sentinel bytes are not consumed as drawing data by the coordinate helper that rejects them; control returns to the main scanner with that byte still pending in AL. Synthetic QEMU batch command_resume_001 validates this scanner-resume contract for an incomplete absolute-line coordinate pair, a corner path, and a seed-fill point list. Focused batch raw_operand_001 validates the complementary case: raw operands for 0xf0, 0xf2, and 0xf9 consume command-looking bytes as operands.

Pixel writes converge on helper 0x52f9. It treats word [0x150b] as AH = x, AL = y, computes DI = y * 0xa0 + x, selects masks from [0x136d] or [0x136e] depending on the low bit of Y, ORs the active draw bits from [0x1369], ANDs with the selected mask, and stores the result in the graphics buffer segment pointed to by [0x136f]. Helpers 0x526f and 0x52ab are optimized horizontal and vertical line drawers that use the same active draw byte and masks while restoring [0x150b] to the endpoint when done. QEMU fuzz case base_019_pattern_edge_rectangle confirms that this store is linear for v2 pattern plotting: when the v2 pattern mask computes X 160, the byte is written as X 0 on the next scanline instead of being clipped. The final would-be wrap past the 0xa0 * 0xa8 buffer is not visible. The v3 0x013e horizontal clamp prevents valid brush geometry from computing X 160.

General line helper 0x66e1 first checks for horizontal and vertical special cases and jumps to those optimized helpers. For diagonal lines, the caller has already plotted the start point. The helper computes absolute X/Y deltas and signed step bytes, picks the larger delta as the loop count, initializes the minor-axis accumulator to half of the major delta, and then repeatedly advances the Y accumulator followed by the X accumulator. Each accumulator that reaches the major delta subtracts the major delta and advances that coordinate by its signed step. The accumulators are byte-sized CPU registers, so each addition and subtraction wraps to 8 bits before the compare/next step. The resulting point is then written through 0x52f9.

A synthetic absolute line from (0,0) to (3,1) plots (0,0), (1,0), (2,1), and (3,1); the same points are produced by the packed relative byte 0x31. QEMU fuzz batch relative_underflow_001 confirms the source-modeled relative-line underflow rule: from (0,10), relative byte 0x90 draws to the right edge at (159,10), and from (10,0), relative byte 0x09 draws to the bottom edge at (10,167). A screen-scale edge case from (159,167) to (0,0) proves the byte-width accumulator behavior: the drawn line includes (25,0) and (25,1) and does not include (0,0). This was first exposed by QEMU fuzz cases base_004_clamped_absolute and base_005_exact_edge_absolute, both of which matched the local renderer after the accumulator wrap was modeled.

The constants in these helpers repeatedly point to a 0xa0 by 0xa8 style coordinate space. For example, vertical stepping adds 0xa0, bounds checks use 0xa0 for the right edge and 0xa7 for the lower edge, and object placement uses the same limits.

Graphics and control buffer helpers

The graphics buffer segment is stored in word [0x136f]. Several helpers treat that segment as a 0xa0 by 0xa8 byte grid, or 0x6900 bytes total.

Helper 0x5257 fills the buffer with the word in AX, writing 0x3480 words. Picture decoding enters through 0x6445 with AX = 0x4f4f, while helper 0x5528 clears through the same routine with AX = 0x4040 before calling the display overlay and rebuilding the priority/control table.

Helper 0x5666 converts packed coordinates to a buffer offset:

input:  AL = y, AH = x
output: DI = y * 0xa0 + x

Helper 0x56a2 initializes the table rooted at 0x127a. It writes 168 bytes, one per Y coordinate. The default pattern is:

y 0..47    -> 4
y 48..59   -> 5
y 60..71   -> 6
...
y 156..167 -> 14

Helper 0x4cbb(value) maps a priority/control value back toward a Y row. In the default-table mode it scans downward through the 0x127a table looking for the first row whose table value is below the requested value. When word [0x124a] is nonzero it instead uses the direct formula (value - 5) * 12 + 0x30. The table can also be rebuilt by helper 0x4d10 from data supplied through a pointer, so the formula path may be a fast path for the default table.

Helper 0x57cf(object) connects object drawing to the same buffer. It derives a table value from object Y, uses that value to fill the low nibble of object byte +0x24 if the low nibble is zero, calls object overlay draw entry 0x9db6, and then writes the high nibble of +0x24 around the object’s footprint in the buffer while preserving existing low nibbles. This looks like the path that leaves object footprint/control information for later movement tests, but the exact user-facing meaning of the high nibble remains open.

View resources and object records

Object records are 43 bytes each. The object table begins at [0x096b] and ends at [0x096d]. The interpreter computes object addresses by multiplying an object index by 0x2b and adding [0x096b].

The current observed field map is:

OffsetObserved use
+0x00Reload value for the per-cycle countdown byte at +0x01.
+0x01Per-cycle countdown/tick divider byte; set by action 0x50 (set_object_field_01_var).
+0x02Object grouping/event byte used by collision and boundary-event code.
+0x03X-like coordinate.
+0x05Y-like coordinate.
+0x07View-like resource number selected by 0x3ae7.
+0x08View-like resource payload pointer.
+0x0aSelected top-level subresource index.
+0x0bCount copied from payload byte [payload+0x02].
+0x0cPointer to a selected subresource table.
+0x0eSelected derived subresource/frame index.
+0x0fCount read from *([object+0x0c]).
+0x10Pointer required before object activation; updated by 0x3d6a.
+0x12Copy of +0x10 made during activation.
+0x14Pointer to a render/update node.
+0x16Previous or saved X-like coordinate.
+0x18Previous or saved Y-like coordinate; used by object crossing tests.
+0x1aWidth word from selected subresource; action 0x85 (display_object_diagnostics_var) prints it as xsize.
+0x1cHeight word from selected subresource; action 0x85 (display_object_diagnostics_var) prints it as ysize.
+0x1eStep-size byte used by multiple motion actions; action 0x85 (display_object_diagnostics_var) prints it as stepsize.
+0x1fFrame-timer reload byte set by action 0x4c (set_object_field_1f_var).
+0x20Current frame-timer countdown byte. Action 0x4c copies var[arg1] here and to +0x1f; code.object.frame_timer_update decrements it before calling code.object.advance_frame_by_mode.
+0x21Direction-like byte used by the per-cycle movement pass and actions 0x56 (set_object_field_21_var)/0x57 (get_object_field_21).
+0x22Motion/control mode byte used by actions 0x4d..0x55 (clear_object_fields_21_22 through stop_motion_mode); value 1 is random autonomous motion, value 2 approaches the first object entry until near, and value 3 is targeted movement started by 0x51 (move_object_to) or 0x52 (move_object_to_var). Gold Rush / AGI v3 also dispatches value 4 through the same target-direction helper as value 3.
+0x23Frame-cycling mode byte used by actions 0x48..0x4b (set_object_field_23_mode0 through set_object_field_23_mode2).
+0x24Priority/control byte; can be fixed by actions 0x36 (set_object_field_24)/0x37 (set_object_field_24_var) or derived from Y. Action 0x85 (display_object_diagnostics_var) prints this byte as pri.
+0x25Word-sized flag field.
+0x27..0x2aMotion/control parameters. For targeted movement, +0x27/+0x28 are target X/Y, +0x29 is the saved step size, and +0x2a is the completion flag. For random mode, +0x27 is a reseeded countdown. For approach-first-object mode, +0x27 is the near threshold, +0x28 is the completion flag, and +0x29 is a delay/sentinel byte used after stuck recovery.

Observed flag bits in object word +0x25 include:

BitObserved use
0x0001Object is active in the graphics/update pipeline.
0x0004Use object byte +0x24 as a fixed priority/control value instead of deriving one from Y.
0x0008Exempts the object from the horizon-like clamp against [0x012d].
0x0010Partitions active objects between the two update-list roots.
0x0020Enables code.object.frame_timer_update to decrement object byte +0x20 and run code.object.advance_frame_by_mode when it reaches zero.
0x0040Required by both update-list callbacks and by the movement pass.
0x0080Set when the next step would cross a script-configured rectangle boundary.
0x0200Excludes an object from object-object collision/crossing tests.
0x0400Marks an object as just positioned or otherwise dirty; the movement pass skips applying direction deltas for that cycle and then clears the bit.
0x1000One-callback startup delay for frame modes set by actions 0x49 and 0x4b; code.object.advance_frame_by_mode clears this bit and returns without changing frames.
0x2000Suppresses automatic direction-based group selection in code.object.frame_timer_update.
0x4000Set by 0x0488 when an object remains at its saved position on an update cadence; later autonomous-direction helpers use it as a stationary/stuck marker.

A QEMU logic-interpreter probe validates the visible effect of clearing bit 0x0004: after action 0x36 fixes an object’s priority/control byte to 5, action 0x38 makes placement derive the priority from baseline Y again. At baseline 80, the derived priority is 7, and the object draws over a synthetic control-6 background.

code.object.frame_timer_update (0x0563) is a separate per-cycle scan over active object records. It considers objects whose flag word has (flags & 0x0051) == 0x0051. Before frame-timer handling, it may automatically select a group from the object’s direction byte +0x21. The local target starts as sentinel 4; if bit 0x2000 is clear and byte +0x0b is 2 or 3, the target comes from data.object.group_for_direction_two_or_three_groups (AGIDATA.OVL:0x08dd). If byte +0x0b >= 4, it comes from data.object.group_for_direction_four_plus_groups (AGIDATA.OVL:0x08e7). When byte +0x01 == 1, the target is not sentinel 4, and it differs from current group byte +0x0a, the helper calls code.object.select_group (0x3bb7).

Gold Rush / AGI v3 changes the +0x0b >= 4 branch. At GR image 0x055c, exactly-four-loop views still use the four-plus direction table without an extra flag check. Views with more than four loops use that table only when flag 0x14 is set; when the flag is clear, the target remains sentinel 4 and no automatic group change occurs. QEMU report build/gr-v3-behavior/frame_selection_gate_qemu_001.json validates this with local GR view 177 for the exact-four case and local GR view 39 for the more-than-four case.

The observed table bytes for directions 0..8 are:

TableValues for directions 0..8
data.object.group_for_direction_two_or_three_groups4, 4, 0, 0, 0, 4, 1, 1, 1
data.object.group_for_direction_four_plus_groups4, 3, 0, 0, 0, 2, 1, 1, 1

QEMU batches object_bit_2000_002 and object_bit_2000_004 validate the visible bit gate and table behavior:

  • For a four-group view, direction 6 selects group 1 after action 0x2e clears bit 0x2000, while action 0x2d sets the bit and leaves the same object on group 0.
  • For a three-group view, direction 6 also selects group 1 through data.object.group_for_direction_two_or_three_groups.
  • Direction 5 in the two/three-group table maps to sentinel 4, so the selected group does not change.
  • A one-shot +0x01 = 2 delays selection until the countdown reaches 1; a per-cycle script write that keeps +0x01 = 2 prevents the group change.
  • In GR, view 177 with exactly four groups selects group 1 for direction 6 whether flag 0x14 is clear or set; view 39 with more than four groups remains on group 0 while flag 0x14 is clear and selects group 1 after that flag is set.

After the group-selection check, if bit 0x0020 is set and byte +0x20 is nonzero, code.object.frame_timer_update decrements +0x20; when the decrement reaches zero it calls code.object.advance_frame_by_mode (0x48b3) and reloads +0x20 from +0x1f.

code.object.advance_frame_by_mode interprets byte +0x23 as a frame-cycling mode. Before dispatch, it checks bit 0x1000; if set, the helper clears that bit and returns without changing the selected frame. Otherwise it starts from object byte +0x0e and the last valid frame index +0x0f - 1:

ModeSetup actionStatic behavior
00x48Increment frame and wrap from the last frame to frame 0.
10x49Increment toward the last frame. On the callback that reaches the last frame, set flag +0x27, clear bit 0x0020, clear direction byte +0x21, and reset mode +0x23 to 0.
20x4bDecrement toward frame 0. If the decrement reaches frame 0, or if the object was already at frame 0, set flag +0x27, clear bit 0x0020, clear direction byte +0x21, and reset mode +0x23 to 0.
30x4aDecrement frame and wrap from frame 0 to the last frame.

After choosing the frame, the helper calls code.object.select_frame to update the object record’s selected frame pointer and dimensions.

QEMU movement batch frame_timer_001 validates this model for the visible mode-1 path: action 0x4c seeds the countdown, action 0x49 starts forward completion mode, and view 11/group 0 advances from frame 0 to frame 1. The same batch confirms that action 0x46 suppresses the frame advance by clearing bit 0x0020, while action 0x47 restores it.

QEMU movement batch frame_timer_modes_002 validates the other visible frame modes against this static model. From view 11/group 0/frame 1, action 0x48 mode 0 wraps forward to frame 0. From frame 1, action 0x4b mode 2 reaches frame 0 and stops. From frame 0, action 0x4a mode 3 wraps backward to the last frame, frame 1. The looping-mode fixtures use a small bytecode guard that reads object field +0x0e with action 0x32 and clears bit 0x0020 once the expected frame appears, making the final capture deterministic.

In addition to absolute positioning through actions 0x25 (set_object_pos), 0x26 (set_object_pos_var), 0x93 (set_object_pos_dirty), and 0x94 (set_object_pos_dirty_var), action 0x28 (add_object_pos_from_vars) performs relative placement. It treats two variable values as signed X/Y deltas, adds them to object fields +0x03 and +0x05, clamps underflow at zero, sets dirty bit 0x0400, and then calls placement helper 0x593a. This is used in local SQ2 logic for short scripted nudges before subsequent subresource or motion changes.

Helper 0x3ae7(object, view_number) binds a loaded view-like resource to an object:

  1. Finds the cache record through 0x3979; error code 3 is reported if absent.
  2. Stores the payload pointer from cache record +0x03 into object field +0x08.
  3. Stores the resource number in object byte +0x07.
  4. Copies payload byte +0x02 into object byte +0x0b.
  5. Calls 0x3bb7 with the requested or clamped top-level subresource index.

Helper 0x3bb7 validates that object +0x08 is nonzero and that the requested subresource is within object byte +0x0b. It then calls 0x3c1b to select a subresource table and 0x3ccb to select a derived entry under that table.

Helper 0x3ccb validates the selected derived entry against object byte +0x0f, calls 0x3d6a to update object byte +0x0e, pointer +0x10, and size fields, then clamps coordinates against the 0xa0/0xa7 bounds. When it adjusts coordinates it sets object flag bit 0x0400.

View payload layout

The view-like payload layout is now partially pinned down by helpers 0x3ae7, 0x3c1b, 0x3ccb, and 0x3d6a, and by local inspection with tools/inspect_view.py.

Observed structure:

payload + 0x00: reserved/unused by observed SQ2 interpreter paths; always 0x01 in local resources
payload + 0x01: reserved/unused by observed SQ2 interpreter paths; always 0x01 in local resources
payload + 0x02: group count
payload + 0x03: u16 preview/display string offset, relative to payload base
payload + 0x05: u16 group_offset[group_count], relative to payload base

group + 0x00: frame count
group + 0x01: u16 frame_offset[frame_count], relative to group base

frame + 0x00: width
frame + 0x01: height
frame + 0x02: control byte
frame + 0x03: row-terminated encoded pixel data

The payload offset arithmetic is visible in the object helpers:

  • The inspected loader, object bind, group/frame select, and preview-text paths do not read payload bytes +0x00 or +0x01. A local census found both bytes are 0x01 in all 203 valid SQ2 view resources, so they are modeled as reserved header bytes for the current spec target.
  • 0x3ae7 copies payload[0x02] to object byte +0x0b.
  • 0x5edb, used by view-resource preview actions, reads u16(payload + 0x03) and displays payload + that_offset through 0x1ce8.
  • 0x3c1b reads u16(payload + 0x05 + selected_group * 2), adds the payload base, and stores the resulting group pointer in object word +0x0c.
  • 0x3c1b reads the first byte at the selected group pointer and stores it in object byte +0x0f.
  • 0x3d6a reads u16(group + 0x01 + selected_frame * 2), adds the group pointer, stores the resulting frame pointer in object word +0x10, then copies frame bytes +0x00 and +0x01 into object width/height fields +0x1a and +0x1c.

Local payload samples match the same layout. For example, view 11 has payload header 01 01 02 00 00, so the observed group count is 2. Group 0 starts at offset 0x09, has 2 frames, and its first frame starts at 0x0e with size 20x5 and control byte 0x01. A full local scan also found nonzero preview/display string offsets in views 220 through 239; the first was view 220 with offset 0x0249 inside a 707-byte payload.

QEMU overlay probes now validate multiple selected offsets within view 11. Group 0 frame 1, group 1 frame 0, and group 1 frame 1 all matched the local renderer in the 22-case object overlay batch, extending the earlier group 0 frame 0 fixture beyond the first frame table entry.

The object overlay draw routine at 0x9db6 -> 0x9e35 provides the current model for frame data. The draw entry receives an object pointer, loads the selected frame pointer from object +0x10, and computes the top-left buffer cell from object X and baseline Y:

left = object[+0x03]
top = object[+0x05] - frame.height + 1

The frame stream is row-oriented:

  • Frame byte +0x00 is width.
  • Frame byte +0x01 is row count/height.
  • Frame byte +0x02 low nibble is the transparent or skip color.
  • Encoded row data begins at frame byte +0x03.
  • A zero byte ends the current row.
  • A nonzero byte encodes one run: high nibble is a color-like value and low nibble is the run length.
  • If the run color equals the transparent nibble, drawing advances by the run length without writing.

When the draw routine does write a run, it stores the object priority/control low nibble from object +0x24 into the destination byte’s high nibble and the frame run color into the destination byte’s low nibble. Existing high-nibble buffer values also gate drawing. If the existing high nibble is greater than 0x20, the routine compares it with the object’s priority/control nibble and skips the pixel when the existing value is higher. If the existing high nibble is 0x20 or lower, it scans downward in the same column until it finds a higher control/priority value or reaches the lower buffer limit, then uses that value for the same comparison. This ties object drawing to the high-nibble control data produced by picture decoding. The comparison is inclusive: an object priority/control nibble equal to the existing or scanned high nibble is allowed to draw. If the downward scan reaches the lower buffer limit without finding a high nibble above 0x20, the comparison value is zero, so even a priority-0 draw can pass that local gate. When one pixel in a run is rejected, the draw routine advances to the next destination cell and continues the same run rather than aborting the run.

QEMU probes using controlled synthetic pictures validate both priority-gate branches. On the default cleared picture buffer, whose high nibble is 4, an object with priority 3 is hidden while priority 4 draws. On a synthetic picture filled to control priority 6, priority 5 is hidden while priority 6 draws. A third pair writes control 2 at the object’s destination row and control 6 one row below; priority 5 is hidden and priority 6 draws, confirming that low-control destination cells use the downward scan before the same less-than-or-equal comparison. Two additional probes intentionally used different low/high nibbles in the transient object’s staged priority/control byte: low 3 with high 6 remained hidden on a control-4 background, and low 6 with high 3 drew on a control-6 background. For visible overlay gating, the draw routine therefore uses the low nibble of object byte +0x24.

Additional QEMU probes with a zero staged priority confirm that helper 0x57cf derives the low visible priority from the runtime priority table when the low nibble of object byte +0x24 is zero. With the default table and baseline Y 80, the derived priority is 7 and the object draws over a control-6 background. After action 0xae rebuilds the priority table from row 100, the same baseline derives priority 4 and is hidden behind that control-6 background.

The local compatibility helper now models this object-frame composition at the buffer level. It takes a decoded frame, a left X, a baseline Y, and a priority nibble, computes top = baseline_y - frame.height + 1, skips pixels whose color equals the frame’s transparent low nibble, and writes (priority << 4) | color only when the high-nibble priority gate permits it. QEMU validation of add_to_pic top-edge and right-edge placement now lines up with this source-derived spiral search. For view 11/group 0/frame 0, requested left 20, baseline 2 first becomes bounds-acceptable at left 18, baseline 4. Requested left 154, baseline 80 fails the right-edge bound until the spiral reaches left 140, baseline 67. Focused QEMU batch clip_edges_001 revalidates the top-edge case and also confirms that the same view flush with the left edge at left 0, baseline 80 draws at the requested in-bounds placement. The simpler view-batch harness now uses the same placement search: focused QEMU batch clip_right_bottom_002 validates request (150, 80) as placement (140, 71) and request (20, 170) as placement (23, 167). The local compatibility helper models this bounds-only portion of 0x593a directly and exposes an accept predicate for the two later source tests, 0x4719(object) == 0 and 0x56b8(object) != 0. Local tests now reject the first four otherwise-valid candidates and confirm that the same spiral then accepts (21,81), proving that collision/control rejection extends the search without changing the movement order. Full object-record collision/control fixtures can plug those predicates in as the local model grows. This does not yet replace the full object-record/update-list pipeline, but it captures the central IBM_OBJS.OVL:0x9db6 pixel rule for focused tests. Those local tests now include the inclusive equal-priority case, a low-control cell whose downward scan finds equal priority, the no-scan-hit priority-0 case, and a multi-pixel run where one rejected pixel does not suppress later pixels.

The persistent object-table path has also been validated for static drawing. A generated logic fixture using load_view, object resource/frame selection, set_object_pos, set_object_field_24, and activate_object produced the same view 11/group 0/frame 0 output as the local composition model. Persistent fixed priority bytes with nonzero high nibbles behaved differently from the transient staged byte: 0x63, 0x36, and 0x66 were hidden in the controlled probes where ordinary low-byte priorities would have separated visible draw from rejection. The current safe interpretation is that persistent fixed priority arguments should be treated as normal 0..15 priority values until movement/control acceptance is probed more directly.

If frame control byte bit 0x80 is set, helper 0x587d may rewrite the frame data in place before drawing. It compares bits 0x70 of frame byte +0x02, shifted down four bits, with object byte +0x0a. When they differ, it stores the object value back into bits 0x70 and rebuilds each encoded row into a stack buffer before copying the rebuilt stream over the original.

The observed rewrite is a horizontal row mirror over the run-length stream:

  1. Keep the low nibble of the control byte as the transparent color.
  2. Skip explicit leading transparent runs while accumulating their width.
  3. From the first nontransparent run through the row terminator, count the total encoded width and the number of run bytes.
  4. Emit transparent runs for the row’s original implicit trailing transparent width, chunked into runs no longer than 15 pixels.
  5. Copy the counted run bytes in reverse order.
  6. End the rebuilt row with a zero terminator.

The original leading transparent pixels therefore become implicit trailing transparent pixels after the reversed run bytes. If a row has no nontransparent runs, the rebuilt row is just the zero terminator. If the implicit transparent width exceeds 15 pixels, the helper emits multiple transparent run bytes before the reversed tail. This matches a QEMU fixture using view 0, group 1, frame 0: the on-disk frame has control byte 0x81 with cached orientation bits 0, while selecting group 1 rewrites the control byte to 0x91 and mirrors the row pixels.

Local SQ2 resource scans found frame control bytes with many low-nibble transparent values, including 0x0, 0x1, 0x2, 0x3, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xc, 0xd, 0xe, and 0xf, and with optional bit 0x80; no sampled on-disk frame used bits 0x10, 0x20, or 0x40 except through the mutable 0x70 orientation/cache field. View 0 group 0 frame 0 is one concrete bit-0x80 sample: it has size 7x33, control byte 0x81, and row-terminated encoded data beginning with 13 62 00 12 64 00 ....

QEMU probes now include additional transparent-color samples: view 21/group 0/ frame 0 with transparent color 3, view 29/group 0/frame 0 with transparent color 8 and size 45x47, and view 10/group 0/frame 0 with bit 0x80 and transparent color 10. All matched the local renderer in the expanded object overlay batch. A later optional view stress batch broadened this again with larger cels and transparent colors 0, 1, 2, 5, 6, 7, 8, 10, 13, 14, and 15; all 17 base-plus-stress cases matched QEMU. Local tests also exercise the row-rewrite edge cases above directly, so the source-visible mirror contract is covered even when a particular row shape is rare in the current SQ2 resources.

No observed SQ2 path gives payload bytes +0x00 and +0x01 a runtime meaning. Keep them in the file-format model as reserved bytes so future cross-version comparisons can notice if another interpreter or game starts using them.

Object activation and deactivation

Action 0x23 (activate_object) calls helper 0x0a06 for an object index. The helper:

  1. Computes and validates the 43-byte object record address.
  2. Errors if object word +0x10 is zero.
  3. Returns early if object flag bit 0x0001 is already set.
  4. Sets object flag bit 0x0010.
  5. Calls placement helper 0x593a.
  6. Copies +0x10 -> +0x12, +0x03 -> +0x16, and +0x05 -> +0x18.
  7. Flushes list root 0x16ff through 0x0307.
  8. Sets object flag bit 0x0001.
  9. Rebuilds/processes one update list through 0x6a26 -> 0x045e.
  10. Calls render/update helper 0x5762(object).
  11. Clears object flag bit 0x1000.

Action 0x24 (deactivate_object) calls helper 0x0aab. If object flag bit 0x0001 is set, it flushes list root 0x16ff, sometimes also flushes list root 0x1703, clears the active bit, rebuilds/processes the affected lists, and calls 0x5762.

This supports the current interpretation that bit 0x0001 means an object is active in the graphics/update pipeline, while bit 0x0010 controls which of the two update-list paths is involved. The exact user-facing names remain pending.

Placement and bounds

Helper 0x593a(object) is called when activating an object and by actions that directly set object coordinates. It enforces screen and horizon-like bounds, then searches for an acceptable nearby position when the initial position fails collision or control tests.

Its bounds helper 0x5a14(object) returns success only when:

object[+0x03] >= 0
object[+0x03] + object[+0x1a] <= 0xa0
object[+0x05] - object[+0x1c] >= -1
object[+0x05] <= 0xa7
if object flag 0x0008 is clear: object[+0x05] > [0x012d]

If the object is above or at [0x012d] and bit 0x0008 is clear, 0x593a first bumps the Y-like field to [0x012d] + 1. When the starting position is not acceptable, the helper tries neighboring positions in a widening spiral until the bounds and additional tests pass. The candidate is tested before each movement step. The movement sequence is:

left 1, down 1, right 2, up 2,
left 3, down 3, right 4, up 4, ...

At each candidate, 0x593a requires all of:

0x5a14(object) != 0
0x4719(object) == 0
0x56b8(object) != 0

QEMU horizon probes validate this path with [0x012d] = 100. With bit 0x0008 clear, placing view 11 at baseline 80 clamps it to baseline 101. After action 0x3d sets bit 0x0008, the same placement stays at baseline 80. After action 0x3e clears the bit again, the baseline clamps to 101.

Actions 0x5a (set_rect_bounds_0131) and 0x5b (clear_rect_bounds_0131) configure a separate rectangle filter used by motion helper 0x06d9. Action 0x5a (set_rect_bounds_0131) stores four bounds in globals:

[0x0131] = left
[0x0133] = top
[0x0135] = right
[0x0137] = bottom
[0x013d] = 1

Helper 0x7be6(x, y) returns true only when the point is strictly inside that rectangle:

[0x0131] < x < [0x0135]
[0x0133] < y < [0x0137]

When [0x013d] is nonzero, an object has bit 0x0002 clear, and its direction byte +0x21 is nonzero, helper 0x06d9 compares whether the current baseline point and the next step point are on the same side of the configured rectangle. The next point is computed from direction byte +0x21 and step byte +0x1e. If the inside/outside result changes, the helper sets object bit 0x0080, clears direction byte +0x21, and, for the first object record, clears global byte [0x000f]. If the result does not change, it clears bit 0x0080.

Per-cycle object movement

Movement-related work is split across two nearby passes:

  1. Top-level helper code.engine.main_cycle (0x0150) calls code.motion.pre_mode_and_boundary_update (0x0644) before script logic 0. That pass scans active/update-eligible objects and, for objects with byte +0x01 == 1, calls code.motion.dispatch_mode_step (0x067a). Dispatcher 0x067a uses motion/control byte +0x22: mode 1 calls random motion helper 0x3f5a, mode 2 calls approach-first-object helper 0x0b36, and mode 3 calls targeted-motion helper 0x1672. In Gold Rush / AGI v3, relocated dispatcher 0x068a also routes mode 4 to the targeted-motion helper. The same pre-pass applies rectangle-boundary helper code.motion.rectangle_boundary_check (0x06d9) when enabled.
  2. After logic 0 returns, code.engine.main_cycle calls code.object.frame_timer_update (0x0563) unless byte [0x1757] is nonzero. This pass performs automatic direction-based group selection, frame timer updates, then calls code.motion.update_objects (0x150a).
  3. Helper 0x150a applies the current direction byte +0x21 and step byte +0x1e, checks collision/control acceptance, records boundary events, and clears bit 0x0400.

The movement pass at 0x150a scans all 43-byte object records from [0x096b] to [0x096d]. It processes only records whose flag word satisfies:

(object[+0x25] & 0x0051) == 0x0051

For each selected object, byte +0x01 acts as a countdown. If it is nonzero the pass decrements it and skips the object unless the decrement reaches zero. When the object is due to move, the pass reloads +0x01 from byte +0x00.

Unless object flag bit 0x0400 is set, the pass computes a proposed position from direction-like byte +0x21, step/speed byte +0x1e, and two signed-delta tables at 0x0a61 and 0x0a73. The proposed position is then clamped against the same 0xa0 by 0xa8 coordinate space used by placement:

BoundaryClampBoundary code
Left edgex = 04
Right edgex = 0xa0 - width2
Top edgey = height - 11
Bottom edgey = 0xa73
Horizon-like line, when bit 0x0008 is cleary = [0x012d] + 11

The pass writes the proposed coordinates to object fields +0x03 and +0x05, then accepts the move only if both later tests agree:

0x4719(object) == 0
0x56b8(object) != 0

If either test fails, it restores the saved X/Y coordinates, clears the boundary code, and calls placement search helper 0x593a(object).

When a boundary code survives, the pass records a boundary event in globals. If object byte +0x02 is zero it writes the code to byte [0x000b]. Otherwise it writes object byte +0x02 to [0x000d] and the boundary code to [0x000e]. If object byte +0x22 is 3, helper 0x16b9(object) is called to end that motion/control mode. The pass clears object flag bit 0x0400 before leaving an object.

Helper 0x4719(object) is an object-object collision or crossing test. It returns zero immediately when the object has bit 0x0200 set. Otherwise it scans all active/eligible objects, skipping candidates with bit 0x0200 and skipping candidates whose byte +0x02 matches the moving object’s byte +0x02. It then checks horizontal rectangle overlap from X and width, followed by a current/previous Y crossing test using fields +0x05 and +0x18.

QEMU probes now validate the default object-object case with two persistent objects. Object table initialization gives object 0 and object 1 different +0x02 values, so the collision helper considers them. With object 0 moving right from (20,80) toward (80,80) and object 1 parked at (50,80), object 0 stops at left 25: its next proposed step would make its right edge touch object 1’s left edge, and 0x4719 rejects the move. Setting bit 0x0200 on object 0 with action 0x43 lets the same fixture reach (80,80), confirming that the moving-object skip bit bypasses this collision test. A follow-up QEMU probe sets the same bit with 0x43, immediately clears it with 0x44, and observes the original collision stop at (25,80) again; this validates that 0x44 restores normal object-object collision testing.

Helper 0x56b8(object) is a control/priority-buffer acceptance test. If object bit 0x0004 is clear, it derives object byte +0x24 from table 0x127a using the object’s Y coordinate. It then computes the buffer offset for object X/Y, uses the selected frame width from object +0x10, scans high nibbles from the buffer segment at [0x136f], and reacts to these classes:

High nibbleSource behavior in 0x56b8
0x00Rejects the proposed move immediately.
0x10Rejects unless object flag bit 0x0002 is set; when that bit is set, scanning continues and the current class state is (flag3=false, flag0=false).
0x20Continues with current class state (flag3=true, flag0=false).
0x30Continues with current class state (flag3=false, flag0=true).
Other nonzero high nibbleContinues with current class state (flag3=false, flag0=false).

The helper resets the class state at each scanned cell, so this is not an “encountered anywhere” latch. If the scan reaches the end of the frame width, object flag bit 0x0100 rejects states whose flag0 component is false, while object flag bit 0x0800 rejects states whose flag0 component is true. For objects whose byte +0x02 is zero, the helper also writes global flags 3 and 0 through 0x74ee/0x74f4 from the final class state. Priority/control byte +0x24 == 0x0f bypasses the scan and returns accepted with both global values clear when +0x02 == 0. The exact meaning of the nibble classes is still open, but the caller contract is clear: nonzero permits the proposed move, zero rejects it.

QEMU movement probes refine this static reading:

  • Priority/control byte +0x24 == 0x0f bypasses the 0x56b8 scan. Synthetic full-screen control classes 0, 1, 2, and 3 should not be modeled as blanket movement rejection for fixed-priority-15 objects.
  • With fixed priority/control 14, a full control-class-1 picture leaves no visible object in the capture, even after 0x58 sets bit 0x0002. This fixture validates the hidden/control-class behavior but is not the positive 0x58 movement oracle.
  • The positive 0x58/0x59 oracle is the rectangle-boundary helper. With bounds (30,70)..(60,90) and countdown-gated movement from (20,80) to (50,80), bit 0x0002 clear stops the object at (30,80), 0x58 lets it reach (50,80), and 0x59 restores the stop at (30,80).
  • With fixed priority/control 14, 0x40 setting bit 0x0100 leaves the object visible at (20,80) on a full control-class-2 picture and prevents movement to (50,80). 0x42 clears the bit and restores movement.
  • With fixed priority/control 14, 0x41 setting bit 0x0800 leaves the object visible at (20,80) on a full control-class-3 picture and prevents movement to (50,80). 0x42 clears the bit and restores movement.

Targeted-motion helpers immediately after the movement pass add one more piece. Actions 0x51 (move_object_to) and 0x52 (move_object_to_var) set +0x22 = 3, store target X/Y in +0x27/+0x28, optionally replace step size +0x1e, save the original step in +0x29, and store a completion flag in +0x2a. Helper 0x1672(object) computes a direction-like byte from the object’s current position to the target fields using the current step byte and stores the result in +0x21. Helper 0x16b9(object) restores +0x1e from +0x29, sets flag +0x2a, and clears motion/control byte +0x22.

QEMU movement probes show an important script-level contract for this mode. Calling 0x51 once starts the object moving in the initially computed direction. If object byte +0x01 is not arranged to trigger the pre-movement dispatcher, script logic normally reissues 0x51 or 0x52 on each interpreter cycle while the completion flag is clear. When the object is then at, or within one step of, the target, helper 0x1672 returns the zero direction and immediately calls 0x16b9. With this repeated-call fixture, horizontal and vertical target arrival matched QEMU exactly. Targets beyond the reachable screen area complete at the movement clamp: view 11/group 0/frame 0 stopped at left 140 for a rightward target and baseline 167 for a downward target.

The source’s pre-movement mode-3 path through 0x067a is also now validated. It is gated by byte +0x01 == 1. A generated fixture that sets that byte and calls 0x51 once, without reissuing it from script logic, reaches (50,80) and sets the completion flag through the autonomous 0x067a -> 0x1672 path.

The same countdown-gated dispatcher is now validated for mode 2. A generated fixture initializes object 1, sets its step byte to 5, sets countdown byte +0x01 to 1, and starts action 0x53 (approach_first_object_until_near) toward object 0 with near threshold 35. QEMU stops object 1 at (50,80) when object 0 is parked at (80,80). An earlier exploratory threshold 25 case fell into the collision/stuck-recovery region near the target and ended at (60,75), so the passing threshold-35 case is the cleaner contract probe for direct mode-2 completion. The threshold-35 result also shows the near-band test does not complete at the exact boundary: object 1 moved past the predicted boundary position (45,80) and completed at (50,80).

Disassembly of 0x0b36 explains the threshold-25 exploratory result. Mode 2 stores sentinel 0xff in +0x29; on the first non-complete step the helper changes that to 0. If bit 0x4000 later says the object did not move, the helper chooses a random nonzero direction, computes a delay from half the Manhattan-like center/baseline distance plus one, and stores either the current step size or a random value at least as large as the step in +0x29. While +0x29 is nonzero, the helper subtracts the step size from it each pass and delays returning to the direct approach direction. That is the current source-backed model for approach stuck recovery.

Random mode 0x54 has a property-style QEMU probe rather than an exact final position assertion. A generated fixture sets step 5, sets countdown byte +0x01 to 1, starts random mode, and accepts any capture that exactly matches the object at a valid final position. The recorded run ended at (140,112).

A focused QEMU probe validates the visible effect of action 0x4e on this motion byte. The fixture starts random motion on object 0 with 0x54, then immediately executes 0x4e. During the subsequent update cycles, the object remains at its starting position (60,80), confirming that clearing +0x22 prevents the autonomous random-motion dispatcher from continuing.

Action 0x84 has the same visible clearing effect for object 0. A follow-up movement fixture starts random motion with 0x54, immediately executes 0x84, and again observes the object remaining at (60,80). This validates the object-0 +0x22 part of the handler; the broader global [0x0139] = 1 effect is still described from the source path.

The expanded movement probe set also confirms leftward and upward movement, diagonal movement, already-at-target completion, and within-step completion. A target X of 52 from starting X 20 with step 5 completes at X 50, not 52, because the remaining distance is inside the step band. A zero step-size operand does not invent a default speed; it preserves the current object step byte. In the generated persistent-object fixture that byte is zero, so the object remains stationary.

The direction lookup at DS:0x0a85 maps relative target position to direction bytes:

target above:  8 1 2
target level:  7 0 3
target below:  6 5 4
               left near right

The zero center value means the target has been reached; 0x1672 then calls 0x16b9 immediately.

Update lists and rendering work

Two linked-list roots are central to the update pipeline:

RootBuilder wrapperSelection callbackPredicate
0x16ff0x6a260x69e4(object[+0x25] & 0x0051) == 0x0051
0x17030x6a3d0x6a05(object[+0x25] & 0x0051) == 0x0041

The predicates show that bit 0x0010 partitions otherwise active/eligible objects between the two roots. Helpers 0x6b44 and 0x6b62 clear and set that bit respectively, wrapping the change with 0x6a54 and 0x6a8e so the update lists are rebuilt around the new membership. Logic action 0x3c (refresh_object_lists) computes the object address for its operand but then calls 0x6a54, 0x6a8e, and 0x6aab; in the observed code this is a global flush/rebuild/refresh pass rather than an object-local field mutation.

QEMU batch object_root_partition_004 validates the visible ordering implied by that source model. With two overlapping view-11 objects, 0x3a on the frame-1 object makes it draw behind the still-0x0010 frame-0 object, while clearing and then re-setting 0x0010 with 0x3b makes the frame-1 object draw over a frame-0 object that remains in the clear partition. The fixtures use 0x3c as the final refresh action.

The wrapper helpers around those roots are now distinct:

HelperObserved role
0x6a54Calls 0x0307 for roots 0x16ff and 0x1703, restoring saved backing rectangles through 0x9db3 and freeing all nodes.
0x6a71Calls 0x032d for both roots, freeing nodes without the restore pass.
0x6a8eRebuilds and draws root 0x1703, then rebuilds and draws root 0x16ff, using 0x6a3d/0x6a26 followed by 0x045e.
0x6aabCalls 0x0488 for root 0x1703, then root 0x16ff, refreshing dirty rectangles and updating saved-position state.

The shared builder 0x0358(root, callback) scans all 43-byte object records in object-table order and stores each accepted record with a draw key. The key is the object’s baseline-like Y field +0x05 unless object flag bit 0x0004 is set; fixed-priority records instead use 0x4cbb(object[+0x24]) as a Y-like key. The builder then performs a selection-sort style pass: on each pass it chooses the smallest remaining key, preserves the first object-table entry on ties, marks that entry consumed with key 0x00ff, and inserts a node through 0x042f.

Helper 0x042f(root, object) allocates a 16-byte render/update node, inserts it at the head of the list, and leaves the first inserted node as the root tail. Because 0x0358 inserts keys from low to high while 0x045e(root) draws from the list tail backward through previous pointers, objects draw in ascending key order. Equal-key objects draw in object-table order, so a later object-table entry is drawn later and can cover earlier equal-key objects. Root order still dominates this in-root order: 0x6a8e draws all root 0x1703 objects first, then all root 0x16ff objects.

Helper 0x4cbb(value) has two source-visible modes. SQ2’s observed normal mode has [0x124a] == 0, so it scans downward from index 0xa8 through the priority table rooted at 0x127a and returns the first index whose byte is below value; value == 0 returns 0xffff. The default table initializer writes indices 0..0xa7, so the byte at index 0xa8 is a one-past-table sentinel. In the local AGIDATA image that byte is zero, which makes positive fixed-priority values sort below ordinary baseline rows unless a future observation shows runtime code changing the sentinel. The alternate branch uses (value - 5) * 12 + 0x30 when [0x124a] is nonzero; this branch remains source-present but no SQ2 write enabling it has been found.

The list-processing helper 0x045e(root) starts with the root tail pointer. For each node it calls 0x9db0(node) to save the backing rectangle, then calls 0x9db6 with the object pointer stored at node +0x04, then follows the node’s previous pointer.

The render/update node allocator at 0x9097 creates a 16-byte node and stores its pointer back into object word +0x14.

Observed node layout:

OffsetObserved use
+0x00Next pointer in the root’s linked list.
+0x02Previous pointer in the root’s linked list.
+0x04Object pointer.
+0x06Left/X coordinate copied from object +0x03.
+0x08Top/Y coordinate computed as object[+0x05] - object[+0x1c] + 1.
+0x0aWidth copied from object +0x1a.
+0x0cHeight copied from object +0x1c.
+0x0ePointer to an allocated backing buffer for the rectangle.

When display mode word [0x1130] == 2, 0x9097 increases the backing-buffer width calculation before allocating the buffer. The exact packed-pixel reason is display-specific and remains to be tied to the HGC overlay.

The object overlay file IBM_OBJS.OVL is loaded at segment 0x09db, so its first bytes appear at near offset 0x9db0. The three entry jumps in that overlay line up with the main executable’s calls:

Near offsetOverlay entryObserved role
0x9db0jmp 0x9db9Save a screen rectangle into node backing buffer +0x0e.
0x9db3jmp 0x9df8Restore a screen rectangle from node backing buffer +0x0e.
0x9db6jmp 0x9e35Draw an object’s selected frame data into the graphics buffer.

Helper 0x0488(root), called by 0x6aab, walks a render/update list after drawing. For each node’s object it first calls 0x5762(object) to refresh the dirty rectangle. It then compares object byte +0x01 with reload byte +0x00; only when those bytes match does it compare current X/Y +0x03/+0x05 with saved X/Y +0x16/+0x18. If position is unchanged it sets flag bit 0x4000; otherwise it copies current X/Y to saved X/Y and clears 0x4000.

Two later autonomous-direction helpers consume 0x4000:

  • 0x3f5a(object), called from motion/control mode byte +0x22 == 1, chooses a new random direction through 0x3fa3 when its local countdown expires or when bit 0x4000 says the object stayed in place.
  • The helper around 0x0bb3, called from the +0x22 == 2 path, also consults bit 0x4000 before replacing direction byte +0x21 with a random nonzero direction. This looks like stuck recovery for a directed movement mode.

So 0x4000 is not just an optimization marker. It records that the object was stationary at the last saved-position comparison, and the motion code uses that fact to choose fresh directions.

The currently observed values of object byte +0x22 are:

ValueStarted byPer-cycle behavior
00x55 (stop_motion_mode), completion helpersNo autonomous mode is active.
10x54 (start_random_motion)Helper 0x3f5a picks direction 0..8 with helper 0x3fa3, keeps a random countdown in +0x27, and reseeds when the countdown expires or the stationary bit 0x4000 is set. A QEMU property probe confirms the mode renders the object at a valid final position.
20x53 (approach_first_object_until_near)Helper 0x0b36 computes a direction from the object’s center/Y toward the first object entry’s center/Y using threshold +0x27; when the direction helper returns zero it clears the mode and sets completion flag +0x28. Stuck recovery temporarily chooses a random nonzero direction and stores a retry delay in +0x29. QEMU confirms direct completion at (50,80) in the threshold-35 fixture.
30x51 (move_object_to), 0x52 (move_object_to_var)Helper 0x1672 computes direction toward target X/Y in +0x27/+0x28; completion helper 0x16b9 restores saved step +0x29, sets completion flag +0x2a, and clears the mode. QEMU probes confirm both script-reissued setup and countdown-gated one-shot setup can complete.
4Gold Rush / AGI v3 internal stateGR dispatcher 0x068a routes this value to the same target-direction helper as mode 3. An instrumented QEMU probe changes only the copied GR action-0x51 setup byte from mode 3 to mode 4; the mode-4 capture matches unmodified mode 3, while a stationary control does not. The natural mode-4 seeding path is not exposed by ordinary bytecode in the current evidence.

The render/update helpers around 0x5528..0x5762 bridge the interpreter’s logical graphics buffer to the selected graphics overlay:

  • 0x5528 clears the logical graphics buffer with fill word 0x4040, calls graphics-overlay entry 0x980f, rebuilds the default priority/control table through 0x56a2, and calls graphics-overlay entry 0x9800.
  • 0x5546 performs a full-screen refresh. If word [0x1755] has bit 0 set it first swaps the high and low nibbles of every byte in the logical graphics buffer, calls the HGC-only helper 0x9899 when display mode [0x1130] == 2, then calls graphics-overlay entry 0x980c for the whole screen.
  • 0x5624 converts the common coordinate tuple into a display-memory offset, using display-mode globals [0x1130] and [0x112e].
  • 0x5685 maps picture visual color bytes for display adapters. On the non-CGA/EGA-target path it returns the input color in both AL and AH. When hardware selector [0x112e] == 0 and mode [0x1130] is not 2 or 3, it calls the graphics overlay’s 0x9815 entry as a color mapper.
  • 0x5762(object) is the object dirty-rectangle refresher. If word [0x1216] is zero it returns without display work. Otherwise it compares the object’s current frame pointer +0x10, current X/Y +0x03/+0x05, saved frame pointer +0x12, and saved X/Y +0x16/+0x18; stores the current frame pointer into +0x12; computes the union rectangle covering both old and new frame footprints; and calls graphics-overlay entry 0x980c.

The dirty rectangle uses baseline-style object coordinates. The current footprint is:

current_left   = object[+0x03]
current_bottom = object[+0x05]
current_width  = current_frame[+0x00]
current_height = current_frame[+0x01]
current_top    = current_bottom - current_height + 1

The saved footprint uses object[+0x16], object[+0x18], and the saved frame pointer from object[+0x12]. The display rectangle is:

left   = min(current_left, saved_left)
right  = max(current_left + current_width, saved_left + saved_width)
bottom = max(current_bottom, saved_bottom)
top    = min(current_top, saved_top)

AH = left
AL = bottom
BL = right - left
BH = bottom - top + 1

This is source-modeled locally by dirty_rect_union() in tools/agi_graphics.py.

The common rectangle arguments to graphics-overlay entries 0x980c and 0x9812 are:

AH = left X
AL = bottom Y
BL = width
BH = height

Entry 0x980c copies a rectangle from logical graphics buffer segment [0x136f] to display memory segment [0x1371]. Entry 0x9812 fills a display rectangle; in the EGA and VGA overlays the low byte of DX supplies the fill value. The main executable’s helper 0x5590 uses 0x9812 to draw/fill several rectangular UI borders, while 0x560c is a small wrapper around 0x980c.

The EGA graphics overlay (SQ2/EGA_GRAF.OVL, loaded at 0x9800) exposes this entry table:

Near offsetOverlay entryObserved role
0x9800jmp 0x9815Set graphics mode 0x0d, configure palette/register state, and store video segment 0xa000 in [0x1371].
0x9803jmp 0x9835Return to text mode, configure cursor/palette, and clear the text screen.
0x9806jmp 0x986fReinitialize graphics mode, then call 0x5546 for a full refresh.
0x9809jmp 0x9884No-op entry in the EGA overlay.
0x980cjmp 0x9885Copy a rectangle from [0x136f] to EGA display memory.
0x980fjmp 0x9983Initialize row-offset table 0x137b and clear a display-memory range.
0x9812jmp 0x9907Fill a rectangle in EGA display memory.

The CGA graphics overlay (SQ2/CGA_GRAF.OVL) uses the same entry table shape but gives entry 0x9815 a different role from EGA. In CGA, 0x9815 is a color mapper used by code.display.map_visual_color_for_adapter (0x5685). It indexes three bytes per AGI color at AGIDATA.OVL:0x1d36: mode [0x1130] == 0 returns one byte duplicated into AL and AH, while mode [0x1130] == 1 returns the following two-byte word. Picture command 0xf0 stores those bytes into the active visual draw value and the two write masks. Therefore action 0x8c, which is guarded to hardware selector [0x112e] == 0, can redraw the same recorded picture through a different CGA mapping after it toggles bit 0 of [0x1130]. The row-interleaved replay fixture should be treated as evidence for this CGA adapter path, not as a requirement for the EGA implementation target.

Transient and preview objects

Two logic-action families use the same view and object drawing machinery without adding a normal persistent object-table entry.

Actions 0x7a (setup_transient_object) and 0x7b (setup_transient_object_var) build a transient object-like record at fixed address 0x0eb4. Action 0x7a (setup_transient_object) reads seven immediate operands; action 0x7b (setup_transient_object_var) reads the same seven values through variables. The values are staged in globals 0x0eae..0x0eb3 before helper 0x2d52 interprets them:

Staged byteObserved role
0x0eaeView-like resource number.
0x0eafTop-level subresource/group index.
0x0eb0Derived subresource/frame index.
0x0eb1X coordinate.
0x0eb2Y coordinate.
0x0eb3 low nibbleVisible overlay priority nibble.
0x0eb3 high nibbleStaged control/secondary priority nibble; not used for visible overlay gating in the current QEMU probes.

Helper 0x2d52 logs several staged pairs through 0x70b1, then initializes the record at 0x0eb4 through the normal view/object helpers:

0x3ae7(0x0eb4, staged_view)
0x3bb7(0x0eb4, staged_group)
0x3ccb(0x0eb4, staged_frame)

It copies the selected frame pointer to saved-frame field +0x12, writes the staged X/Y to both current and saved coordinate fields, sets object byte +0x24 from the staged priority/control byte, places the object through 0x593a, then wraps the actual draw/update path with:

0x6a54
0x57cf(0x0eb4)
0x6a8e
0x5762(0x0eb4)

The fixed record’s flag word is initialized as 0x020c before placement, which sets the observed fixed-priority bit 0x0004, horizon-exempt bit 0x0008, and collision-skip bit 0x0200. If the staged priority/control low nibble is zero, the helper later replaces the flag word with 0x0008 before calling 0x57cf. That special case is stable in the code but its user-facing meaning remains open.

Actions 0x81 (display_view_resource_text_like) and 0xa2 (display_view_resource_text_like_var) display or preview a view-like resource using a stack-local 43-byte record. Action 0x81 (display_view_resource_text_like) uses an immediate resource number; action 0xa2 (display_view_resource_text_like_var) reads the resource number from a variable. Helper 0x5edb:

  1. Records whether the resource was already cached through 0x3979.
  2. Temporarily sets word [0x0f18] = 1 while calling loader 0x39f7.
  3. Initializes a stack-local object record with group/frame zero through 0x3ae7.
  4. Centers it horizontally with x = (0x9f - width) / 2, sets y = 0xa7, sets fixed priority/control byte +0x24 = 0x0f, and sets grouping byte +0x02 = 0xff.
  5. If enough memory is available, allocates a render/update node with 0x9097, saves the backing rectangle through 0x9db0, draws through 0x9db6, and calls 0x5762.
  6. Displays a string pointer derived from the loaded payload through 0x1ce8. The pointer is payload + u16(payload + 0x03), which gives the first observed consumer of view payload bytes +0x03..+0x04.
  7. If a backing rectangle was saved, restores it with 0x9db3, calls 0x5762, and frees the node with 0x910a.
  8. If the resource was not cached before the preview action, releases it through 0x3f0d.

Current system model

The graphics/interpreter path now looks like this:

  1. Directory files and VOL.* records provide raw logic, picture, view, and sound payloads.
  2. Logic bytecode actions request resource loads and mutate object records.
  3. Picture actions select a cached picture payload, decode command bytes 0xf0..0xfa, and write into the graphics buffer through shared pixel helpers.
  4. View actions bind view-like payloads to 43-byte object records and derive object dimensions and frame pointers.
  5. Transient display actions can build temporary object records and route them through the same placement/draw/update helpers.
  6. Object activation and movement actions flush, rebuild, and process sorted update lists rooted at 0x16ff and 0x1703.
  7. Render/update nodes capture backing rectangles, draw selected frame data from object field +0x10, and later restore old rectangles when lists are flushed.

The largest remaining unknowns in this area are the final user-facing names for the two update-list root partitions, any frame control-byte bits not exercised by local SQ2 resources, and the display-specific packed-buffer variants outside the full EGA target path.

Compatibility Testing

The final output of this project should include executable compatibility tests, not only prose. The test suite is part of the clean-room evidence trail: each test starts from an explicitly selected local game directory or locally captured original-engine behavior, and every expected value should be traceable to a command or observation recorded in the notes.

Test layers

The current plan has five layers:

  1. Local deterministic tests run directly against the resource files selected by --game-dir PATH or AGI_GAME_DIR=PATH. These are fast regression tests for directory parsing, resource decoding, and provisional renderers.
  2. Generated render fixtures under build/rendered/. These make picture and view hypotheses visible as simple PPM/PNG images that can be inspected or compared by image tools.
  3. QEMU validation captures from the original interpreter. As picture, view, and object drawing tests become more targeted, QEMU screenshots should become the compatibility oracle for visible output and animation behavior.
  4. Synthetic fuzz resources generated by tools/picture_fuzz.py. These create focused valid and invalid picture payloads, patch them into copied fixture directories, and compare original-engine QEMU screenshots against the local Python renderer.
  5. An opt-in exhaustive QEMU layer combines every present picture with every current movement and object-overlay case. It is separate from the practical broad sweep because it performs many more original-engine launches.

Malformed data is split into two categories. Bounded malformed resources, such as an incomplete coordinate pair that still reaches the normal picture terminator, are useful compatibility evidence. Payloads that can make the original interpreter read outside the synthetic resource and interpret unrelated memory are security behavior, not engine semantics for this specification. The fuzz harness marks those cases safe_for_qemu: false and refuses to launch them through the automated QEMU runner.

Current command set

Run all current local tests from the repository root with a game directory:

AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests

Run the current top-level compatibility suite manifest. By default this executes only deterministic local checks: the unit suite, mdbook build docs, mdbook build spec, and the opcode-evidence freshness check. QEMU smoke, broad, exhaustive, and version-specific v3 probes are opt-in so a quick local run does not unexpectedly boot the original engine:

python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --report build/compatibility-suite/local_001.json
python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --include-qemu-smoke --report build/compatibility-suite/qemu_smoke_002.json
python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --include-qemu-broad --report build/compatibility-suite/qemu_broad_002.json
python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --include-qemu-exhaustive --report build/compatibility-suite/qemu_exhaustive_001.json
python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --include-qemu-v3 --report build/compatibility-suite/qemu_v3_001.json
python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --dry-run --include-qemu-smoke
python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --dry-run --include-qemu-broad
python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --dry-run --include-qemu-exhaustive
python3 -B tools/compatibility_suite.py --game-dir games/SQ2 --dry-run --include-qemu-v3

--game-dir has no default. The runner exports the selected path as AGI_GAME_DIR only to its child commands and writes it as selected_game_dir in the JSON report, so reports from different private game inputs remain distinguishable. The environment variable remains accepted as an alternative explicit selection for existing automation.

The first checked run of the default manifest passed after running 230 unit tests, building the mdBook, and checking generated opcode evidence.

The current checked run of the QEMU smoke manifest passed in build/compatibility-suite/qemu_smoke_002.json. That run executed the local unit suite, mdBook build, opcode-evidence check, parser edge QEMU probes, unknown-word parser terminator QEMU probe, picture command-resume fuzz probes, raw-operand picture fuzz probes, and relative-line underflow fuzz probes. Every selected command returned zero.

The smoke manifest regenerates its deterministic 1,062-case picture corpus before selecting named fuzz cases. This makes smoke, broad, and exhaustive runs independent of disposable prior build/ contents.

The current checked run of the QEMU broad manifest passed in build/compatibility-suite/qemu_broad_002.json. The broad selection includes the smoke layer plus the eight-picture timed carousel and the 19-case view/object stress carousel. Every selected command returned zero; the picture carousel matched all 8 cases and the view/object carousel matched all 19 cases.

The exhaustive selection inherits local, smoke, and broad commands, then adds the chunked all-present picture carousel plus the complete current deterministic object_movement_probe.py batch and the complete current object_overlay_probe.py batch. The checked aggregate report build/compatibility-suite/qemu_exhaustive_001.json passed all 15 selected commands. Its local phase passed 403 tests with one optional historical-save case skipped and built both books. The original-engine phases matched 3 parser sequence cases, 1 unknown-word terminator case, 8 focused picture-fuzz cases, 8 broad pictures, 19 view/object stress cases, all 74 present pictures, all 36 deterministic movement cases, and all 24 overlay cases, with no mismatches or errors.

Movement and overlay snapshots are split into ten-case chunks because copying every full game fixture into one disposable DOS disk exhausted its capacity. The random_motion_visible_somewhere movement case is deliberately excluded from the exhaustive gate: autonomous random direction can validly be zero, so a stationary final capture is not a deterministic failure. The case remains available for exploratory runs, while the two cases that immediately clear random motion remain deterministic and are included in the exhaustive layer.

Portable conformance results

tools/conformance_results.py turns existing visual batch reports into a versioned, implementation-facing result bundle. For each successful capture it extracts the logical 160 by 168 game area as one row-major byte per EGA palette index, writes an optional .ega artifact, and records its SHA-256 digest. The companion comparator accepts bundles from any producer, identifies missing or unexpected cases, compares digests, and reports a pixel mismatch count and bounding box when both sides provide artifacts.

The same bundle format supports a values observation for deterministic nonvisual results. Source reports may attach a JSON object under values; the exporter preserves it without translating it through DOS memory. The comparator recursively compares objects and ordered arrays and reports JSON Pointer paths for missing, unexpected, or different values. Floating-point values are rejected so producers in different languages do not inherit host rounding or JSON-number ambiguities. This permits cases to record semantic variables, flags, parsed-input outcomes, ordered sound commands, and restored state while remaining independent of interpreter addresses and internal structures.

Export the current deterministic original-engine visual corpus with:

python3 -B tools/conformance_results.py export \
  build/logic-interpreter-probes/batches/parser_edges_suite.json \
  build/logic-interpreter-probes/batches/parser_unknown_terminator_suite.json \
  build/picture-fuzz/batches/command_resume_suite.json \
  build/picture-fuzz/batches/raw_operand_suite.json \
  build/picture-fuzz/batches/relative_underflow_suite.json \
  build/picture-carousel/batches/picture_carousel_all_suite.json \
  build/view-carousel/batches/view_carousel_stress_suite.json \
  build/object-movement-probes/batches/object_movement_all_suite.json \
  build/object-overlay-probes/batches/object_overlay_all_suite.json \
  --output build/conformance-results/sq2_2936_reference.json \
  --artifact-dir build/conformance-results/sq2_2936_frames \
  --suite-id sq2-2.936-deterministic-visual-v1 \
  --profile 2.936-full-ega --producer original-dos-2.936

The normative interchange fields and canonical-frame rules are in the clean room specification’s Conformance Results chapter. Paths and report adapters in this evidence chapter are convenience tooling, not part of engine behavior.

The first export completed in build/conformance-results/sq2_2936_reference.json. It contains 165 successful deterministic visual cases and 165 canonical frame artifacts. Comparing the bundle with itself produced 165 matches and zero failures in build/conformance-results/sq2_2936_self_compare.json. The frames have 114 distinct digests because control and equivalence cases intentionally converge on the same visible result.

The exporter also accepts completed v3 behavior-probe reports. Their stable case IDs are probe/label; dry runs and reports with an explicit failed QEMU result are not marked successful. The first Gold Rush export combines frame selection, key-map capacity, menu gating, motion mode 4, restart-marker, room-alias, signed-restore, and synthetic picture/view probes:

python3 -B tools/conformance_results.py export \
  build/gr-v3-behavior/frame_selection_gate_qemu_001.json \
  build/gr-v3-behavior/key_map_capacity_qemu_pic001_002.json \
  build/gr-v3-behavior/menu_gate_suite.json \
  build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.json \
  build/gr-v3-behavior/restart_prompt_marker_suite.json \
  build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json \
  build/gr-v3-behavior/signed_restore_roundtrip_suite.json \
  build/gr-v3-behavior/synthetic_picture_view_suite.json \
  --output build/conformance-results/gr_3002149_reference.json \
  --artifact-dir build/conformance-results/gr_3002149_frames \
  --suite-id gr-3.002.149-deterministic-visual-v1 \
  --profile 3.002.149-gold-rush-full-ega \
  --producer original-dos-3.002.149

That bundle contains 32 successful cases and 32 frame artifacts. Its 14 distinct digests capture the intentional equivalence/control groups, and its self-comparison passed all 32 cases with zero failures.

The v3 manifest layer is separate because it depends on the private local games/GR input. It currently includes separate blank-prefix and signed GR save-XOR extraction probes, a signed restore round-trip probe, a restart prompt-marker cancel probe, and a menu-gate probe. The first named suite run for the blank-prefix case passed in build/compatibility-suite/qemu_v3_save_001.json; the signed direct report build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json and suite report build/compatibility-suite/qemu_v3_signed_save_001.json confirm the GRSG.1 save path. The restore suite report build/compatibility-suite/qemu_v3_signed_restore_001.json confirms that the same generated GRSG.1 restores state by matching a restored capture to a direct saved-state control and differing from an unrestored control. The restart suite report build/compatibility-suite/qemu_v3_restart_prompt_001.json confirms that the canceled restart path redraws the prompt marker only when it was visible on entry. The menu-gate suite report build/compatibility-suite/qemu_v3_menu_gate_001.json confirms that 0xb1(0) blocks a requested menu while 0xb1(1) enters the modal menu path.

Generate current sample render outputs:

AGI_GAME_DIR=games/SQ2 python3 -B tools/render_picture.py 1 --output build/rendered/picture_001_visual.ppm
AGI_GAME_DIR=games/SQ2 python3 -B tools/render_picture.py 1 --channel control --output build/rendered/picture_001_control.ppm
AGI_GAME_DIR=games/SQ2 python3 -B tools/render_view.py 0 0 0 --output build/rendered/view_000_00_00.ppm
AGI_GAME_DIR=games/SQ2 python3 -B tools/render_view.py 11 0 0 --output build/rendered/view_011_00_00.ppm

Convert PPM output to PNG for visual inspection with ImageMagick:

magick build/rendered/picture_001_visual.ppm build/rendered/picture_001_visual.png
magick build/rendered/picture_001_control.ppm build/rendered/picture_001_control.png
magick build/rendered/view_000_00_00.ppm build/rendered/view_000_00_00.png
magick build/rendered/view_011_00_00.ppm build/rendered/view_011_00_00.png

Inspect either QEMU screenshots or local render fixtures:

python3 -B tools/inspect_ppm.py build/qemu/screen.ppm
python3 -B tools/inspect_ppm.py build/rendered/picture_045_visual.ppm

Generate a custom original-engine picture fixture:

AGI_GAME_DIR=games/SQ2 python3 -B tools/qemu_fixture.py picture 45 --output build/qemu-fixtures/picture_045

The generator copies the selected local game files, replaces VOL.3 with a custom logic resource, and patches LOGDIR[0] to point at that resource. The generated LOGIC.0 bytecode for picture 45 is:

03 fa 2d 18 fa 19 fa 1a fe fd ff

This means:

assignn(v250, 45)
load_picture_var(v250)
prepare_picture_var(v250)
show_picture_like()
jump -3

After copying the fixture directory into the DOS image and capturing a QEMU screendump, compare it with:

python3 -B tools/compare_picture_capture.py 45 build/qemu-fixtures/picture_045/qemu_picture_045.ppm

Run a reusable real-picture snapshot batch:

python3 -B tools/picture_batch.py --snapshot --dos-prefix PB --output build/picture-batch/batches/picture_base_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

The first real-picture batch matched both base cases with 0 mismatches from one QEMU snapshot run. It covers picture 1, the first present local SQ2 picture resource with pattern plots, and picture 45, the largest valid local picture payload.

Run the broader representative real-picture preset:

python3 -B tools/picture_batch.py --preset broad --snapshot --dos-prefix PB --output build/picture-batch/batches/picture_broad_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

The first broad real-picture batch matched all 8 cases with 0 mismatches. It covers pictures 1, 6, 17, 43, 44, 45, 46, and 76: a mix selected from local corpus counts for first-present coverage, dense fill usage, full command-family coverage, largest payload, and high pattern counts.

Run the full present-picture parity batch:

python3 -B tools/picture_batch.py --preset all --snapshot --fixture-root build/picture-batch/all-fixtures --dos-prefix PA --output build/picture-batch/batches/picture_all_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

The first full present-picture batch matched all 74 valid local SQ2 picture resources with 0 mismatches. This run used packed picture fixtures that copy the minimal engine support files and store the generated LOGIC.0 plus the tested picture payload in each fixture’s VOL.3.

Prototype and validate a faster in-engine carousel:

python3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_mapped_fkey_001.json --boot-wait 5 --first-wait 8 --advance-wait 4

The key-driven carousel prototype matched the two-picture base smoke with 2 matches and 0 mismatches using one engine process and mapped function-key advance events. It is not broad-suite evidence: four-picture and eight-picture key-driven sweeps still stall after two displayed pictures or leave input/UI artifacts.

Use timed polling carousel mode for broader picture sweeps:

python3 -B tools/picture_carousel.py --preset broad --mode timed --poll --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-poll-fast-fixtures --dos-dir PICPOLL --output build/picture-carousel/batches/picture_carousel_broad_timed_poll_fast_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.5 --poll-timeout 15

This run matched pictures 1, 6, 17, 43, 44, 45, 46, and 76 with 8 matches, 0 mismatches, and 0 errors from one engine process. The fixture sets byte variable v10 to 1, because disassembly of the cycle-throttle helper at image 0x7f78 shows it waits for counter [0x1784] to reach byte DS:0x0013 (v10) before the next top-level cycle. A shorter delay-cycles 60 polling run missed intermediate pictures, so 120 is the current practical broad-preset setting.

For all present SQ2 pictures, use chunked timed polling:

python3 -B tools/picture_carousel.py --preset all --mode timed --poll --chunk-size 16 --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-all-poll-chunk16-fixtures --dos-dir PICALL --output build/picture-carousel/batches/picture_carousel_all_timed_poll_chunk16_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.5 --poll-timeout 20

The chunked run matched all 74 valid local SQ2 picture resources with 0 mismatches and 0 errors across five engine launches. A single all-picture carousel matched the first 19 pictures, then displayed the original engine’s disk-insert prompt over picture 19 while polling for picture 20 onward. The generated VOL.3 was present and picture 20’s packed header and PICDIR entry were correct, so this is treated as an original-engine fixture/resource lifecycle boundary rather than picture renderer behavior. Chunking keeps the fast sweep path inside the observed valid-data model.

Generate a fixture that draws one view cel over a picture:

python3 -B tools/qemu_fixture.py picture-view 1 11 0 0 20 80 15 --output build/qemu-fixtures/picture_001_view_011_00_00

The generated LOGIC.0 bytecode for this sample is:

03 fa 01 18 fa 19 fa 1a 1e 0b 7a 0b 00 00 14 50 0f 0f fe fd ff

This means:

assignn(v250, 1)
load_picture_var(v250)
prepare_picture_var(v250)
show_picture_like()
load_view(11)
setup_transient_object(view=11, group=0, frame=0, x=20, baseline_y=80, priority=15, control=15)
jump -3

After copying the fixture into the DOS image and capturing a QEMU screendump, compare it with:

python3 -B tools/compare_picture_capture.py 1 build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm --view 11 0 0 --view-x 20 --view-baseline-y 80 --view-priority 15

The same fixture command can target bit-0x80 frames whose encoded rows are rewritten for the selected group/orientation. The first validated sample uses view 0, group 1, frame 0:

python3 -B tools/qemu_fixture.py picture-view 1 0 1 0 20 80 15 --output build/qemu-fixtures/picture_001_view_000_01_00
python3 -B tools/compare_picture_capture.py 1 build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm --view 0 1 0 --view-x 20 --view-baseline-y 80 --view-priority 15

Generate a deterministic synthetic picture fuzz corpus:

python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean

Build an original-engine fixture for one fuzz case:

python3 -B tools/picture_fuzz.py build-fixture base_005_exact_edge_absolute

Run one fuzz case in QEMU and compare the capture:

python3 -B tools/picture_fuzz.py run-qemu base_005_exact_edge_absolute --dos-dir FZEDGE --boot-wait 5 --draw-wait 8

Run a serial QEMU fuzz batch and save a JSON report:

python3 -B tools/picture_fuzz.py batch-qemu --case base_016_visual_fill_box --case base_019_pattern_edge_rectangle --dos-prefix FV --output build/picture-fuzz/batches/targeted_fill_pattern.json --boot-wait 5 --draw-wait 8

Run a known-ahead fuzz batch through a disposable qcow2 boot disk and QEMU internal snapshots:

python3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_016_visual_fill_box --case base_019_pattern_edge_rectangle --dos-prefix FS --output build/picture-fuzz/batches/targeted_fill_pattern_snapshot.json --boot-wait 5 --draw-wait 8

The first snapshot-mode fuzz smoke used base_016_visual_fill_box and base_019_pattern_edge_rectangle; both matched with 0 mismatches from one QEMU boot.

Run the current view/object overlay validation batch:

python3 -B tools/view_batch.py --dos-prefix VC --output build/view-batch/batches/view_base.json --boot-wait 5 --draw-wait 8

Run the same view/object cases with one QEMU boot and internal snapshots:

python3 -B tools/view_batch.py --snapshot --dos-prefix VS --output build/view-batch/batches/view_snapshot.json --boot-wait 5 --draw-wait 8

The first snapshot-mode view/object smoke ran the original six built-in cases and all six matched from one QEMU boot. The base registry now has eight cases: the original normal/cached/mirrored/left/top/low-priority cases plus right and bottom edge-placement cases.

Run the focused right/bottom edge-placement batch:

python3 -B tools/view_batch.py --snapshot --dos-prefix VC --output build/view-batch/batches/clip_right_bottom_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case view_011_right_clip --case view_011_bottom_clip

The first right/bottom attempt used simple direct composition and mismatched the right-edge case. After the view-batch comparison was changed to use the source-derived 0x593a placement search, clip_right_bottom_002 matched both cases with 2 matches and 0 mismatches. For view 11/group 0/frame 0, request (150, 80) resolves to (140, 71), and request (20, 170) resolves to (23, 167).

Run the optional larger cel/transparent-color stress suite in the same snapshot batch:

python3 -B tools/view_batch.py --snapshot --include-stress --dos-prefix VXS --output build/view-batch/batches/view_stress_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

The first stress run used the older six-case base plus eleven large or transparent-color-focused cels; all 17 matched with 0 mismatches. The current registry has eight base cases, so --include-stress now covers 19 total cases.

Run the current base or base-plus-stress view cases through a timed polling carousel from one engine process:

python3 -B tools/view_carousel.py --fixture-root build/view-carousel/base-fixtures --dos-dir VCARBASE --output build/view-carousel/batches/view_carousel_base_001.json --boot-wait 5 --first-wait 3 --delay-cycles 120 --speed-value 1 --poll-interval 0.5 --poll-timeout 20
python3 -B tools/view_carousel.py --include-stress --fixture-root build/view-carousel/stress-fixtures --dos-dir VCARSTR --output build/view-carousel/batches/view_carousel_stress_001.json --boot-wait 5 --first-wait 3 --delay-cycles 120 --speed-value 1 --poll-interval 0.5 --poll-timeout 20

The base carousel matched all 8 current base cases, and the base-plus-stress carousel matched all 19 current cases, both with 0 mismatches and 0 errors from one original-engine process. The carousel fixture packs only the generated logic plus selected picture/view payloads into VOL.3; keep tools/view_batch.py as the simpler one-fixture-per-case reference oracle.

Run targeted object overlay priority, clipping, transparent-cel, priority-table, and persistent-object probes with controlled synthetic picture backgrounds:

python3 -B tools/object_overlay_probe.py --dos-prefix OP --output build/object-overlay-probes/batches/priority_scan_down.json --boot-wait 5 --draw-wait 8

The expanded 22-case object overlay batch matched QEMU with 0 mismatches. It covers default control priority 4 versus object priorities 3 and 4, full-screen control priority 6 versus object priorities 5 and 6, low/high staged nibble mismatches proving transient visible priority uses the low nibble, the low-control downward-scan path where a destination cell with control 2 finds a control-6 barrier one row below, right/bottom edge placement, transparent color variants, 0xae priority-table rebuild effects, and persistent object-table setup/drawing. It now also covers selected view 11 group/frame offsets: group 0 frame 1, group 1 frame 0, and group 1 frame 1.

The object overlay harness now supports repeated --case CASE_ID filters for focused runs. The follow-up edge-placement batch matched the original engine with 2 matches and 0 mismatches:

python3 -B tools/object_overlay_probe.py --dos-prefix OC --output build/object-overlay-probes/batches/clip_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case left_clip_view11_priority15 --case top_clip_view11_priority15

The local comparison now derives top/right bounds adjustments with a modeled 0x593a spiral search instead of hard-coded expected coordinates. Existing QEMU captures for top_clip_view11_priority15 and right_clip_view11_priority15 both match the source-derived placements: (18, 4) for the top-edge request and (140, 67) for the right-edge request.

Together with the earlier 22-case report, the current object-overlay registry has original-engine evidence for 24 valid synthetic cases.

Run the core logic-interpreter control-flow probes:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LJ --output build/logic-interpreter-probes/batches/control_flow_002.json --boot-wait 5 --draw-wait 8

The first four-case batch matched QEMU with 0 mismatches. It validates visible effects for structural jump 0xfe, false conditional skipping, 0xfd condition inversion, and 0xfc OR groups. These generated logic fixtures keep the final drawing state alive with the same self-loop shape used by the picture/view fixtures; ending immediately after a transient draw can produce a race where the screenshot no longer contains the intended transient object.

Run the expanded opcode-family logic probes:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LK --output build/logic-interpreter-probes/batches/opcode_families_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

The first 27-case opcode-family batch matched QEMU with 0 mismatches. It covers the original four control-flow cases plus always_false, byte-variable increment/decrement saturation, assignment, add/subtract, indirect variable loads/stores, multiply/divide low-byte behavior, flag immediate and variable-selected actions, comparison predicates 0x02..0x06, and simple object-field getter/setter pairs for position, field +0x24, and field +0x21.

The logic probe harness now supports repeated --case CASE_ID filters. Use filtered batches for larger probe sets because each case currently copies a full SQ2 fixture into the snapshot disk.

Run the five follow-up logic-interpreter batches:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LA --output build/logic-interpreter-probes/batches/step1_call_resume_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case call_logic_draws_from_called_logic --case load_logic_then_call_logic_draws --case call_logic_var_draws_selected_logic --case save_restore_resume_actions_continue_to_draw
python3 -B tools/logic_interpreter_probe.py --dos-prefix LB --output build/logic-interpreter-probes/batches/step2_var_backed_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case set_object_pos_var_getter_observes_values --case var_resource_group_frame_setup_draws_persistent_object --case setup_transient_object_var_draws_selected_cel --case move_object_to_var_sets_flag_at_existing_target
python3 -B tools/logic_interpreter_probe.py --dos-prefix LC --output build/logic-interpreter-probes/batches/step3_object_predicates_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case object_left_rect_condition_true --case object_width_rect_condition_true --case object_center_rect_condition_true --case object_right_rect_condition_true
python3 -B tools/logic_interpreter_probe.py --dos-prefix LD --output build/logic-interpreter-probes/batches/step4_string_message_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case set_string_from_message_equal_normalized --case parse_string_slot_sets_input_word_sequence
python3 -B tools/logic_interpreter_probe.py --dos-prefix LE --output build/logic-interpreter-probes/batches/step5_inventory_table_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case inventory_marker_ff_condition_true --case inventory_marker_eq_var_condition_true --case inventory_marker_ff_var_and_getter --case inventory_marker_clear_and_getter --case inventory_marker_from_var --case inventory_marker_from_var_var

All five filtered batches matched QEMU with 0 mismatches. Together they add evidence for logic load/call variants, resume-state actions as executable opcodes, variable-backed object/resource setup, variable-backed transient objects, immediate-completion move_object_to_var, object rectangle predicates, custom message-table loading, string-slot normalization, dictionary parsing for look, input-word sequence testing, inventory/object marker predicates, and marker actions 0x5c..0x61.

Run the parser edge batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix PW --output build/logic-interpreter-probes/batches/parser_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_word_sequence_matches_two_words --case input_word_sequence_wildcard_matches_word --case input_word_sequence_terminator_accepts_prefix
python3 -B tools/logic_interpreter_probe.py --dos-prefix PU --output build/logic-interpreter-probes/batches/parser_unknown_terminator_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_word_sequence_terminator_matches_unknown_word

The three-case parser_edges_001 batch and the one-case parser_unknown_terminator_001 batch matched QEMU with 0 mismatches. They validate condition 0x0e matching two parsed word IDs (look get -> 0x0002 0x0005), word ID 0x0001 as a wildcard for one parsed word, and word ID 0x270f as a successful terminator after a matching prefix. The follow-up unknown-token case confirms the source-modeled edge where parsing an unknown word sets flag 2 and a nonzero count/error-position, allowing a terminator-only pattern to match.

Local WORDS.TOK decoder tests now cover the static vocabulary format used by those probes: the 26-entry big-endian letter-offset table, 1,099 decoded local entries, a zero x bucket, prefix-compressed phrase reconstruction, and the known IDs anyword=0x0001, look=0x0002, and get=0x0005. The same test module now covers the source-modeled parser normalization tables, case- insensitive dictionary lookup, zero-ID dictionary word filtering, unknown-word output-slot reporting, the ten-word parsed-output limit, and a local input_word_sequence_matches() model for exact, wildcard, terminator, failure, flag-gate, and unknown-token terminator cases.

Run the room-switch re-entry batch:

python3 -B tools/logic_interpreter_probe.py --case switch_room_reentry_dispatches_current_room --case switch_room_v_reentry_dispatches_current_room --dos-prefix RS --output build/logic-interpreter-probes/batches/room_switch_reentry_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

The two-case batch matched QEMU with 0 mismatches. These fixtures validate the source-shaped lifecycle for 0x12 and 0x13: logic 0 sets a one-time init flag before the switch, the switch returns zero, the main cycle immediately re-enters logic 0, and the second pass dispatches the current room through call_logic_var(v0). The destination room logic owns the flag-5 entry setup, including picture/view loading and drawing.

Run the room current/previous variable update batch:

python3 -B tools/logic_interpreter_probe.py --case switch_room_sets_current_previous_and_clears_boundary --case switch_room_v_sets_current_previous_and_clears_boundary --dos-prefix RP --output build/logic-interpreter-probes/batches/room_previous_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

The two-case batch matched QEMU with 0 mismatches. These fixtures validate that both 0x12 and 0x13 copy the old current room from v0 into previous-room byte v1, write the destination room into v0, and clear boundary selector v2.

Run the room-entry boundary selector batch:

python3 -B tools/logic_interpreter_probe.py --case switch_room_boundary_1_sets_object0_bottom_y --case switch_room_boundary_2_sets_object0_left_x --case switch_room_boundary_3_sets_object0_top_y --case switch_room_boundary_4_sets_object0_right_x --dos-prefix RB --output build/logic-interpreter-probes/batches/room_boundary_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure
python3 -B tools/logic_interpreter_probe.py --case switch_room_v_boundary_1_sets_object0_bottom_y --case switch_room_v_boundary_2_sets_object0_left_x --case switch_room_v_boundary_3_sets_object0_top_y --case switch_room_v_boundary_4_sets_object0_right_x --dos-prefix VB --output build/logic-interpreter-probes/batches/room_boundary_var_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

Both four-case batches matched QEMU with 0 mismatches. These fixtures validate the v2 entry-boundary selector side effect of 0x12 and 0x13: selector 1 sets object 0 Y to 0xa7, selector 2 sets object 0 X to 0, selector 3 sets object 0 Y to 0x25, selector 4 sets object 0 X to 0xa0 - object_width, and each path clears v2. The fixture preloads view 11 before setting up object 0 so the right-edge case has a known width.

Run the room-switch persistent-object reset batch:

python3 -B tools/logic_interpreter_probe.py --case switch_room_removes_preexisting_persistent_object --case switch_room_v_removes_preexisting_persistent_object --dos-prefix RO --output build/logic-interpreter-probes/batches/room_object_reset_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure

The two-case batch matched QEMU with 0 mismatches. These fixtures activate a persistent object before the room switch and validate that only the destination room’s sprite appears afterward. They prove the pre-switch persistent object’s visible draw state does not survive either 0x12 or 0x13.

Run the inventory selection follow-up batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix IN --output build/logic-interpreter-probes/batches/inventory_selection_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case inventory_selection_enter_sets_var19 --case inventory_selection_escape_sets_var19_ff --case inventory_selection_noninteractive_ack_returns

This three-case batch matched QEMU with 0 mismatches. It validates that action 0x7c uses flag 13 to choose interactive selection, that Enter stores the selected carried-entry index in absolute byte DS:0x0022 (script variable 0x19), that Escape stores 0xff there, and that the noninteractive mode waits for acknowledgement and returns to following bytecode.

Run the object/view getter and bitfield follow-up batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LF --output build/logic-interpreter-probes/batches/object_getter_bitfield_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case object_view_metadata_getters --case object_field_24_var_getter_observes_value --case object_distance_inactive_pair_sets_ff --case clear_object_fields_21_22_clears_direction --case object_bitfield_actions_dispatch_smoke

This five-case batch matched QEMU with 0 mismatches. Four cases are value probes: view/object metadata getters 0x31..0x35, variable-backed field +0x24 setter 0x37, inactive object-distance result 0xff for 0x45, and 0x4d clearing direction byte +0x21 as observed through getter 0x57. The fifth case began as a dispatch smoke probe for bitfield/helper actions; later focused batches promote the visible bit behaviors for those rows in the evidence matrix.

Run the object root-partition follow-up batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LR --output build/logic-interpreter-probes/batches/object_root_partition_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_0010_moves_object_behind_set_partition --case set_bit_0010_moves_object_over_clear_partition

This two-case batch matched QEMU with 2 matches, 0 mismatches, and 0 errors. It validates that 0x3a moves an active object into the root 0x1703 partition drawn before root 0x16ff, 0x3b moves it back into the root 0x16ff partition, and 0x3c performs the refresh pass that makes the partition order visible. The fixtures also preserve the stale drawing left behind when 0x25 rewrites current and saved coordinates after activation; that ghost is modeled as fixture setup, not as the root-partition behavior under test.

Later source inspection of the shared update-list builder (0x0358), node inserter (0x042f), and draw walker (0x045e) pins down the in-root order used by future renderer tests: each root is selection-sorted by ascending baseline or reverse-priority key, equal keys preserve object-table order, and drawing walks from list tail toward head. Local tests in tests/test_graphics_rendering.py now model this ordering, including SQ2’s observed one-past-table sentinel for the fixed-priority reverse mapping.

Local graphics tests also model source helper 0x56b8 (code.object.control_acceptance) over extracted high-nibble scanlines. The current tests cover class-0 rejection, class-1 rejection and bit-0x0002 bypass, final-state effects for bits 0x0100 and 0x0800, the other-nonzero-class fall-through state, and priority-15 scan bypass with event flags cleared.

Run the object bit-0x2000 direction/group-selection follow-up batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix L2 --output build/logic-interpreter-probes/batches/object_bit_2000_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_allows_direction_group_selection --case set_bit_2000_suppresses_direction_group_selection
python3 -B tools/logic_interpreter_probe.py --dos-prefix L3 --output build/logic-interpreter-probes/batches/object_bit_2000_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_two_or_three_group_direction6_selects_group1 --case clear_bit_2000_two_or_three_group_direction5_is_sentinel --case clear_bit_2000_field01_countdown_eventually_selects_group --case clear_bit_2000_requires_field01_equal_one_when_forced

The first batch matched QEMU with 2 matches, 0 mismatches, and 0 errors. It validates that 0x2e leaves automatic direction-based group selection enabled for a four-group view, and that 0x2d suppresses that selection. The second batch matched QEMU with 4 matches, 0 mismatches, and 0 errors. It validates the two/three-group direction table, sentinel value 4, countdown-to-1 behavior, and the exact +0x01 == 1 gate when +0x01 is forced to 2 every logic cycle.

Run the Gold Rush / AGI v3 frame-selection gate probe:

python3 -B tools/gr_v3_behavior_probe.py --probe frame-selection-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/frame-selection-fixtures --dos-prefix GRF --run-qemu --output build/gr-v3-behavior/frame_selection_gate_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/frame_selection_gate.raw --snapshot-qcow build/gr-v3-behavior/snapshot/frame_selection_gate.qcow2 --boot-wait 5 --draw-wait 8

This eight-case copied-fixture batch passed. It compares exact-four and more-than-four auto-selection cases against group-0 and group-1 controls. GR view 177, which has exactly four groups, selected group 1 for direction 6 whether flag 0x14 was clear or set. GR view 39, which has more than four groups, remained on group 0 while flag 0x14 was clear and selected group 1 after flag 0x14 was set. This confirms the GR-specific branch at image 0x055c without modifying games/GR.

Run the object-state/random/no-op follow-up batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LG --output build/logic-interpreter-probes/batches/object_state_misc_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case object_add_pos_from_vars_getter_observes_sum --case random_equal_bounds_stores_bound --case noop_7f_continues_to_draw --case noop_9b_consumes_two_operands_then_draws --case noop_af_runtime_consumes_no_operand --case set_object_pos_dirty_getter_observes_values --case set_object_pos_dirty_var_getter_observes_values --case deactivate_object_removes_persistent_draw --case clear_all_object_bits_removes_persistent_draw

The first run matched eight cases and revealed that the original 0x22 expectation was too strong: clear_all_object_bits clears active/update bits but does not immediately unlink an already activated object from the current draw. Rerun the corrected 0x22 fixture with:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LG --output build/logic-interpreter-probes/batches/object_state_misc_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_all_object_bits_keeps_current_draw_entry

The corrected fixture matched QEMU with 0 mismatches. These probes add QEMU-backed evidence for 0x22, 0x24, 0x28, 0x7f, 0x82, 0x93, 0x94, 0x9b, and 0xaf.

Run the variable view-load and object field +0x23 follow-up batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LH --output build/logic-interpreter-probes/batches/load_view_field23_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case load_view_var_allows_following_draw --case object_field_23_mode0_dispatch_smoke --case object_field_23_mode1_clears_flag --case object_field_23_mode3_dispatch_smoke --case object_field_23_mode2_clears_flag

This five-case batch matched QEMU with 0 mismatches. It validates 0x1f loading a variable-selected view before a draw, validates the observable flag clearing side effect of 0x49 and 0x4b. Later frame-timer movement batches promote 0x48, 0x4a, and the visible mode-2 behavior of 0x4b to behavior evidence.

Run the horizon-bit placement batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LZ --output build/logic-interpreter-probes/batches/horizon_bits_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case horizon_clamps_object_when_bit_clear --case horizon_exempt_bit_keeps_object_above_horizon --case horizon_clear_exempt_bit_restores_clamp

This three-case batch matched QEMU with 0 mismatches. It validates 0x3f setting the horizon-like global, 0x3d exempting an object from the horizon clamp, and 0x3e restoring the clamp after the exemption bit was set.

Run the fixed-priority clear-bit placement batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LP --output build/logic-interpreter-probes/batches/fixed_priority_bit_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_fixed_priority_bit_uses_derived_priority

This one-case batch matched QEMU with 0 mismatches. It validates that 0x38 clears the fixed-priority bit and returns placement to Y-derived priority.

Run the resource lifecycle batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix RL --output build/logic-interpreter-probes/batches/resource_lifecycle_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case load_logic_var_then_call_logic_draws --case overlay_picture_var_composes_extra_picture --case discard_picture_var_allows_reload_and_overlay --case discard_view_allows_reload_and_draw --case discard_view_var_allows_reload_and_draw

This five-case batch matched QEMU with 0 mismatches. It validates variable-selected logic loading (0x15), picture overlay after an explicit load and visible finalization (0x18, 0x1c, 0x1a), picture discard/reload (0x1b), immediate view discard/reload (0x20), and variable-selected view discard/reload (0x99).

Run the text/message and typed numeric-input batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TX --output build/logic-interpreter-probes/batches/text_input_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_message_then_ack_continues_to_draw --case display_message_var_then_ack_continues_to_draw --case display_message_configured_then_ack_continues_to_draw --case prompt_number_to_var_accepts_digits

This four-case batch matched QEMU with 0 mismatches. It uses the SnapshotFixtureCase.post_launch_keys input path to dismiss message windows and type 42 into the numeric prompt. It validates 0x65, 0x66, 0x97, and 0x76.

Run the string prompt batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/prompt_string_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_string_to_slot_returns_after_enter --case prompt_string_to_slot_stores_typed_word

This two-case batch matched QEMU with 0 mismatches. It types look into 0x73 (prompt_string_to_slot), sends a named Enter key, then refreshes the picture before the validation draw so text-plane pixels from the prompt do not pollute the graphics comparison. The second case validates storage by comparing the edited string slot against a known message-backed string slot before drawing.

Run the formatted text and input-line state batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TU --output build/logic-interpreter-probes/batches/text_ui_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_formatted_message_then_ack_continues_to_draw --case display_formatted_message_var_then_ack_continues_to_draw --case display_message_configured_var_then_ack_continues_to_draw --case input_line_toggle_refresh_erase_dispatch_smoke --case text_rect_clear_dispatch_smoke --case close_text_window_state_dispatch_smoke

This six-case batch matched QEMU with 0 mismatches. It validates the formatted/positioned display return path for 0x67, 0x68, and 0x98, and dispatch-smokes the input-line and text-window operations 0x77, 0x78, 0x89, 0x8a, 0x69, 0x9a, and 0xa9. Later focused batches supersede the smoke-only status for 0x69, 0x77, 0x78, 0x89, 0x8a, and 0x9a.

Run the text-rectangle clear behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_rect_clear_behaviour_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_rows_removes_formatted_text --case text_rect_clear_bounds_removes_formatted_text

This two-case batch matched QEMU with 0 mismatches. It promotes 0x69 (clear_text_rect) and 0x9a (clear_text_rect_bounds) from dispatch-smoke to behavior-level coverage. The probes display formatted text, acknowledge it, then clear the affected text cells without refreshing the picture resource. The expected display surface includes the black clear rectangles validated by QEMU: 0x69(5, 6, 0) clears logical rows Y 40..55 across the screen, and 0x9a(8, 5, 8, 20, 0) clears logical X 20..83/Y 64..71.

An earlier run, text_rect_clear_behaviour_002, matched the 0x69 case but mismatched the bounded case because the expected rectangle assumed text columns were eight logical pixels wide. Measuring the capture showed that the EGA text grid maps one text column to four logical pixels and one text row to eight logical pixels.

Run the status/input single-row hide behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TH --output build/logic-interpreter-probes/batches/text_hide_clear_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_disable_clears_configured_row --case status_line_hide_clears_configured_row

This two-case batch matched QEMU with 0 mismatches. It promotes 0x77 (disable_input_line_like) and 0x71 (hide_status_line_like) from dispatch-smoke to behavior-level coverage for the normal EGA display path. The probes display formatted text on row 5, acknowledge it, configure the relevant row through 0x6f, then run the hide/disable action. The expected display surface includes logical Y 40..47 cleared to black, confirming that code.text.clear_row (0x2ba6) clears one configured text row without refreshing the picture resource.

Run the input-enable/text-attribute behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TE --output build/logic-interpreter-probes/batches/text_enable_attr_behaviour_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_enable_clears_configured_row --case text_attribute_enable_clears_visible_surface

This two-case batch matched QEMU with 0 mismatches. It promotes 0x78 (enable_input_line_like) and 0x6a (enable_text_attr_mode_1757) to behavior-level coverage for the observed EGA path. The 0x78 case confirms that enabling the input line redraws the configured input row; the prompt marker is deliberately set to an empty message so the comparison only checks the row clear. The 0x6a case confirms that entering alternate text-attribute mode clears the visible logical surface to black. A prior attempt in text_enable_attr_behaviour_001 expected the usual transient-object validation draw to appear after 0x6a, but the original capture remained black, so the promoted case treats the visible surface itself as the observable contract.

Run the input-line refresh/erase behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix IR --output build/logic-interpreter-probes/batches/input_refresh_status_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_typed_text_visible_baseline --case input_line_erase_clears_typed_buffer --case input_line_refresh_repaints_entered_buffer

This three-case batch matched QEMU with 0 mismatches. It promotes 0x8a (erase_input_line) and 0x89 (refresh_input_line) to behavior-level coverage for the observed EGA path. The baseline case first verifies that typed live-edit glyphs appear on the configured input row. The 0x8a case then runs the erase action each cycle and matches logical Y 40..47 back to black. The 0x89 case types look plus Enter before checking refresh, because a failed earlier attempt (input_refresh_status_001) showed that unaccepted live-edit characters are not replayed by this path. After Enter populates source buffer 0x0fce, 0x89 repaints visible glyph pixels from that source.

Run the status-line show behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix ST --output build/logic-interpreter-probes/batches/status_show_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case status_line_show_draws_configured_row

This one-case batch matched QEMU with 0 mismatches. It promotes 0x70 (show_status_line_like) to behavior-level coverage for the observed EGA path. The fixture configures status row 5 through 0x6f(0, 0, 5), runs 0x70, and checks that visible color-15 pixels appear in logical Y 40..47.

Run the text-attribute-pair behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TA --output build/logic-interpreter-probes/batches/text_attr_pair_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_attribute_pair_changes_attr_mode_clear_color

This one-case batch matched QEMU with 0 mismatches. It promotes 0x6d (set_text_window_pair) to behavior-level coverage for the observed EGA path. The disassembly shows 0x6d(0, 1) storing pair globals that 0x6a later recomputes with alternate text mode enabled. The original capture validates the resulting visible surface as a full-screen color-15 clear, matching packed text attribute low byte 0xf0.

Run the prompt-marker/text-attribute-exit behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TP --output build/logic-interpreter-probes/batches/text_prompt_attr_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_attribute_disable_restores_picture_draw --case input_prompt_empty_message_suppresses_marker

This two-case batch matched QEMU with 0 mismatches. It promotes 0x6b (disable_text_attr_mode_1757) and 0x6c (set_input_prompt_char) to behavior-level coverage for focused observable effects. The 0x6b case confirms that leaving alternate text-attribute mode restores the ordinary picture refresh and transient-object draw path. The 0x6c case confirms the empty-message prompt-marker behavior by first setting a nonempty marker, then setting an empty message and redrawing the input line; the captured row remains black, with no prompt-marker glyph.

Run the input-width flag behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag

This three-case batch matched QEMU with 0 mismatches. The cases set fixed string slot 0 to a long blank string, then type live input after setting or clearing [0x0d0f]. With 0xa3 set, the wrapped input row contains black glyph pixels from accepted input. With 0xa4, and with inactive 0xa9 after a prior 0xa3, the wrapped row remains blank white fill. This promotes 0xa3 and 0xa4 to behavior-level evidence for the input-width flag and validates the unconditional [0x0d0f] clear side of 0xa9.

Run the text/status configuration batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TS --output build/logic-interpreter-probes/batches/text_status_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_attribute_mode_dispatch_smoke --case screen_shake_dispatch_smoke --case input_prompt_config_dispatch_smoke --case status_line_show_hide_dispatch_smoke --case key_event_mapping_dispatch_smoke

This five-case batch matched QEMU with 0 mismatches. It originally dispatch-smoked 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, and 0x79; later behavior batches supersede the smoke-only status for 0x6a, 0x6b, 0x6c, 0x6d, 0x70, and 0x71, while 0x6f and 0x79 have their own behavior probes. Action 0x6e remains QEMU dispatch-smoked for return-to-bytecode but is source-backed for the transient CRT/display-register shake effect. The earlier batch text_status_001 used first operand 1 for 0x6f and mismatched because the interpreter shifted the later validation draw relative to the local renderer; the clean smoke fixture uses operand 0 and leaves that non-default display-offset behavior for a dedicated probe.

Run the input/key/string behavior batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix IK --output build/logic-interpreter-probes/batches/input_key_string_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_config_operand1_offsets_display_by_8 --case mapped_key_sets_status_byte --case set_string_from_table_copies_patched_pointer

This three-case batch matched QEMU with 0 mismatches. It validates the nonzero 0x6f display-offset effect, the 0x79 mapped-key path through status byte condition 0x0c, and 0x74 string-table copy semantics using a fixture-local AGIDATA.OVL patch that makes table entry 0 point at a synthetic look string.

Run the focused raw-key condition probe:

python3 -B tools/logic_interpreter_probe.py --dos-prefix RK --output build/logic-interpreter-probes/batches/raw_key_condition_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case raw_key_event_available_draws_after_typed_key

This one-case batch matched QEMU with 0 mismatches. It sends plain key x without installing any 0x79 mapping and draws only when condition 0x0d (raw_key_event_available) observes the raw event.

Run the diagnostics/system batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix DS --output build/logic-interpreter-probes/batches/diagnostics_system_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case pause_message_then_ack_continues_to_draw --case heap_status_then_ack_continues_to_draw --case interpreter_version_then_ack_continues_to_draw --case diagnostic_global_actions_dispatch_smoke

This four-case batch matched QEMU with 0 mismatches. It validates the message/ack/return behavior for 0x87, 0x88, and 0x8d, and dispatch-smokes 0xaa. Later focused probes or source passes promote 0x83, 0x84, 0x8e, 0xab, 0xac, 0xad, 0xa3, and 0xa4 beyond this original smoke batch.

The heap helper formulas are covered by:

python3 -B -m unittest tests.test_heap

This test module models the source-backed bump allocator and diagnostic formulas behind action 0x87. It checks that allocation returns the old heap top, advances the current pointer, refreshes the free-memory page byte, updates the high-water mark only when the new top exceeds it, treats overflow as the interpreter’s fatal allocation path, restores a nonzero temporary mark once, rewinds dynamic state to the room/reset mark, and computes the heap-status values from the same base/current/limit/high-water/reset globals observed in the disassembly. These are local source-model tests rather than visible QEMU UI captures; the existing diagnostics/system batch validates that the heap-status action displays and returns to bytecode.

Run the menu/list and sound dispatch-smoke batch:

python3 -B tools/logic_interpreter_probe.py --dos-prefix MS --output build/logic-interpreter-probes/batches/menu_sound_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_setup_dispatch_smoke --case menu_flag_dispatch_smoke --case sound_load_stop_dispatch_smoke

This three-case batch matched QEMU with 0 mismatches. It proves the menu/list handlers 0x9c..0xa1 and sound handlers 0x62/0x64 execute and return to following bytecode, but it is dispatch-smoke evidence rather than full interactive menu or audio semantics.

Run the focused menu, view-resource, system/dialog, file/log, and sound follow-up batches:

python3 -B tools/logic_interpreter_probe.py --dos-prefix MN --output build/logic-interpreter-probes/batches/menu_interaction_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_interactive_enter_sets_status_byte
python3 -B tools/logic_interpreter_probe.py --dos-prefix VW --output build/logic-interpreter-probes/batches/view_resource_display_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case view_resource_display_immediate_returns --case view_resource_display_var_returns
python3 -B tools/logic_interpreter_probe.py --dos-prefix SY --output build/logic-interpreter-probes/batches/system_dialog_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case signature_check_matching_message_returns --case restart_confirm_escape_continues_to_draw --case confirm_restart_like_escape_continues_to_draw --case joystick_calibration_no_joystick_returns --case display_mode_toggle_guarded_noop_continues --case trace_window_config_enable_dispatch_smoke
python3 -B tools/logic_interpreter_probe.py --dos-prefix FL --output build/logic-interpreter-probes/batches/file_log_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case log_file_append_dispatch_smoke --case save_game_escape_continues_to_draw --case restore_game_escape_continues_to_draw
python3 -B tools/logic_interpreter_probe.py --dos-prefix SN --output build/logic-interpreter-probes/batches/sound_completion_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case sound_start_stop_dispatch_smoke

All five batches matched QEMU with 0 mismatches after the trace and sound cases were narrowed to stable visual assertions. They add evidence for one-item menu Enter selection through status byte 7, immediate and variable view-resource display, signature acceptance, restart/confirmation Escape cancellation, no-joystick calibration return, guarded display-mode no-op, gated trace-window configuration dispatch, save/restore Escape cancellation, log append dispatch, and sound load/start/stop behavior. The sound case loads sound 1, starts it with completion flag 77, stops it, and reaches the validation draw only after that flag is set.

The static sound-resource parser is covered by:

python3 -B -m unittest tests.test_sound_resources

This test module scans the local SQ2 SNDDIR and present sound payloads. It checks that 49 sound resources are present, that each present resource has four sorted in-bounds channel offsets with first offset 8, that all channels parse to an in-payload 0xffff terminator, and that sound 1’s first channel matches the source-backed event record shape observed from the loader and playback tick code. These tests validate the resource container and event stream shape; they also validate the source-backed event scheduling model. The schedule checks pin the one-tick first-record delay, sound 1’s tick-40 natural termination, sound 60’s different one-channel and four-channel completion ticks, immediate completion when flag 9 is clear, the 65,536-tick countdown wrap for a synthetic zero-duration event, and the source-backed tone-output boundary: PC-speaker divisor/silence-gate behavior, non-PC high/low tone byte writes, high-byte suppression of low-byte writes, and stop-core silence bytes. They also cover the non-PC-speaker attenuation output helper: default envelope table bytes, channel masks, selector 2 low-value lift, negative/positive delta clamps, and the 0x80 envelope terminator. They do not validate analog audible synthesis, but the driver port-write behavior is now source-backed in the runtime model and covered by local tests.

The static save-file parser is covered by:

AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_save_resources

This test module parses local SQ2SG.* save files in the selected game directory using the source-backed envelope from 0x7d/0x7e: a 31-byte description/header followed by five little-endian length-prefixed blocks. It confirms that all 11 present save files have the fixed first-four block lengths 1505, 903, 328, and 200, that the fifth block is present and variable-sized, that the parsed file size exactly reaches the end of the fifth block, that every present save serializes back to identical bytes, and that truncated, trailing, or internally inconsistent data is rejected. This is structural compatibility evidence for the save-file container and a fixture-building primitive for later generated saves; it does not yet validate a full original-engine save/restore round trip from QEMU.

The same test module now covers a source-modeled code.dos.validate_path planning helper for selector path edges. It models the string handling before the source delegates to DOS: leading spaces are skipped, an empty path is filled with the current directory, a trailing slash/backslash is stripped from multi-character paths, a single slash/backslash is accepted as a root path, two-character drive paths such as A: use the drive-availability probe, and other paths use DOS find-first with directory attributes.

It also covers the source-modeled Gold Rush / AGI v3 object/inventory save transform. The helper gr_v3_object_inventory_save_xor() applies the observed repeating Avis Durgan XOR key from GR data address DS:0x072c; tests prove the original-save known vector, round-trip behavior, the 11-byte wrap point, and rejection of an empty generic key. QEMU extraction build/gr-v3-behavior/save_xor_extract_qemu_001.json now confirms that the original GR interpreter writes a five-block blank-prefix SG.1 save whose third block changes and round-trips under this helper. The signed extraction build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json corrects the fixture to use encrypted logic-message text for 0x8f("GR"), writes GRSG.1, and confirms that the first saved-state block starts with GR\0.

tests/test_restart_model.py covers the source-backed Gold Rush / AGI v3 restart prompt-marker redraw branch. The tested truth table is: accepted restart redraws the marker, canceled restart redraws only when the marker was visible before entry. QEMU report build/gr-v3-behavior/restart_prompt_marker_qemu_001.json validates the canceled branch in the original GR interpreter: hidden cancel matches a hidden control with 0 prompt-row foreground pixels, while visible cancel matches a visible control with 8 prompt-row foreground pixels.

The Gold Rush / AGI v3 menu-gate probe covers action 0xb1. The source model is that 0xb1 writes word [0x0403], and GR code.menu.interact returns immediately while that word is zero. QEMU report build/gr-v3-behavior/menu_gate_suite.json validates the visible effect: 0xb1(0) plus an 0xa1 menu request matches the blocked control, while 0xb1(1) plus the same request differs from both the control and zero-gate case by entering the modal menu path.

Run a dynamic original-engine save-write probe:

python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_010.json --capture build/save-roundtrip/qemu_capture_010.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_010.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_010.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_010.raw --save-output build/save-roundtrip/SQ2SG_010.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08

The generated fixture removes existing save files, calls 0x8f verify_game_signature with message SQ2, calls action 0x7d, drives the original save UI through path acceptance, slot selection, description entry, and final confirmation, then extracts the resulting save from the post-run DOS image. The signature action initializes the DS:0x0002 string used both as the save-name prefix and as the saved-state signature, so the original engine wrote SQ2SG.1. The extracted file parsed with description codex probe, block lengths 1505, 903, 328, 100, and 12; its first state block begins SQ2\0; and the post-save validation screen matched the expected blank picture plus view 11 overlay with 0 visual mismatches. This proves the save writer can produce a source-envelope-compatible file and return to following bytecode in the fixture.

Validate restore from that generated save:

python3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SQ2SG_010.1 --output build/save-roundtrip/restore_roundtrip_sq2stem_006.json --fixture build/save-roundtrip/restore-fixture-signed --dos-dir RST6 --capture build/save-roundtrip/restore_capture_sq2stem_006.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_006.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_006.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 8 --path-keys $'\n\n' --slot-wait 2 --slot-keys $'\n\n' --confirmation-wait 1 --confirmation-keys $'\n\n' --key-delay 0.12

The restore fixture starts with the validation X variable set to 90 and calls 0x7e. Code immediately after 0x7e is therefore the failure/cancel path, because the source shows a successful restore returns zero and ends the current logic stream. To make success observable, the save fixture sets a packed flag and X=50 before saving, and the restore fixture begins with a branch that draws from the restored variables only when that flag is present on a later cycle. Earlier restore probes that merely matched code after 0x7e were therefore ambiguous; the stronger restore_roundtrip_sq2stem_006 probe matched X=50 with 0 visual mismatches, while the failure path draws X=90. This validates an actual original-engine restore of the generated save state.

Run a representative original-engine restore-read failure probe:

python3 -B tools/save_roundtrip_probe.py --mode restore-read-error --output build/save-roundtrip/restore_read_error_002.json --fixture build/save-roundtrip/restore-read-error-fixture --dos-dir RERR --capture build/save-roundtrip/restore_read_error_002.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_read_error_002.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_read_error_002.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 8 --path-keys $'\n' --slot-wait 2 --slot-keys $'\n' --confirmation-wait 1 --confirmation-keys $'\n' --key-delay 0.12

The fixture writes a deliberately truncated SQ2SG.1: a 31-byte description header, little-endian declared first-block length 0x05e1, and only seven payload bytes SQ2\0\0\0\0. The selector still lists the save because those seven bytes pass the source-backed DS:0x0002 signature check. After one Enter at the directory prompt, one Enter at slot selection, and one Enter at the confirmation dialog, the original engine attempts the full first-block read and shows the persistent fatal dialog Error in restoring game. Press ENTER to quit. The stable 8-second capture was build/save-roundtrip/restore_read_error_002.ppm, with RGB SHA-256 556971f26fc34deb32497a9d10c08eedeb28f6bdb0957cd7676a8ef26830849c, 3 unique colors, and non-background bounding box (0, 136, 639, 399).

Run the focused enabled trace-window case:

python3 -B tools/logic_interpreter_probe.py --dos-prefix TR --output build/logic-interpreter-probes/batches/trace_window_enable_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case trace_window_enable_draws_box_when_flag10_set

This one-case batch matched QEMU with 0 mismatches. The case sets flag 10, configures the trace globals with 0x96(0, 1, 2), then runs 0x95. The comparison checks for the enabled trace window’s red border, white fill, and black opcode/operand text, superseding the older flag-clear smoke evidence for 0x95 and 0x96 while preserving that smoke case as the gated no-draw path.

Menu navigation remains source-backed rather than dynamically validated. The original-engine probes validate Enter, Escape, disabled-item Enter, and disable/re-enable behavior, but the attempted down-arrow QEMU fixture did not deliver a reusable type-2 movement event. The disassembly-backed contract is therefore recorded separately: type-2 movement values 1..8 dispatch through the table at 0x9526 to previous item, first item, next enabled heading, last item, next item, last heading, previous enabled heading, and root heading.

Run the display-mode replay cases:

python3 -B tools/logic_interpreter_probe.py --dos-prefix RV --output build/logic-interpreter-probes/batches/replay_visible_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_mode_replay_skips_flag7_unrecorded_picture --case display_mode_replay_uses_rolled_back_event_count

These cases patch the display guard words and launch the original engine with SIERRA -p -c so action 0x8c reaches the replay branch. The visual comparison expectation is deliberately the observed screen behavior: after 0x8c, the background alternates rows because the recorded picture is redrawn through the alternate CGA color/display mapping. The second picture is excluded from the replay log; it is not the source of the odd rows. The fixtures compare the background only, avoiding unrelated object-color effects in the toggled display mode. A paired manual QEMU memory probe, documented in the clean-room notes, is the stronger evidence for internal replay semantics: the event log excludes the second picture when flag 7 blocks recording or when 0xab/0xac rolls the count back. The rollback case is the behavior-level evidence for actions 0xab and 0xac. Follow-up source inspection also found the post-loop re-enable at replay finish target 0x6927, correcting the earlier unresolved recording-gate note. The current automated harness does not yet read interpreter memory, so treat these cases as CGA-only display checks plus source/memory-backed replay-log notes, not as full 16-color EGA target behavior.

The corrected two-case batch matched QEMU with 2 matches, 0 mismatches, and 0 errors.

Run the follow-up priority/diagnostics/sound and menu-edge batches:

python3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/priority_diag_sound_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case priority_screen_enter_returns --case object_diagnostics_var_enter_returns --case sound_stop_sets_completion_flag
python3 -B tools/logic_interpreter_probe.py --dos-prefix ME --output build/logic-interpreter-probes/batches/menu_edges_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_escape_exits_without_status_byte --case menu_disabled_item_enter_does_not_set_status_byte --case menu_enable_after_disable_allows_enter_status_byte

Both batches matched QEMU with 0 mismatches. They validate 0x1d priority screen return, 0x85 object diagnostics return, 0x64 setting the completion flag configured by 0x63, Escape menu exit without a status event, disabled menu-item Enter not setting a status event, and disable-then-enable restoring Enter selection.

Menu movement and delivery now also have a source-backed implementation model in docs/src/runtime_model.md. Existing QEMU keyboard attempts did not produce a stable arrow-navigation fixture, so movement validation remains a useful future compatibility-suite addition. For the current EGA target, the movement contract is based on code.menu.interact, its dispatch table, and the local AGIDATA raw-key map, while Enter/Escape/item-enable behavior is dynamically validated by the menu batches above.

To reproduce the 0x90 logfile-content check, run the log case alone, convert the post-run qcow2 disk to raw, and extract LOGFILE from the generated DOS directory:

python3 -B tools/logic_interpreter_probe.py --dos-prefix LF --output build/logic-interpreter-probes/batches/log_file_contents_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case log_file_append_dispatch_smoke
qemu-img convert -f qcow2 -O raw build/logic-interpreter-probes/snapshot/logic_interpreter.qcow2 build/logic-interpreter-probes/snapshot/logic_interpreter_after_log.raw
mcopy -o -i build/logic-interpreter-probes/snapshot/logic_interpreter_after_log.raw@@32256 ::/LF00000/LOGFILE build/logic-interpreter-probes/fixtures/log_file_append_dispatch_smoke/logfile_from_qemu.txt
xxd -g 1 build/logic-interpreter-probes/fixtures/log_file_append_dispatch_smoke/logfile_from_qemu.txt

The observed file content was two leading newlines followed by Room 0, an empty Input line: record, and the message LOG.

Regenerate the opcode evidence matrix after updating opcode labels or probe annotations:

python3 -B tools/logic_opcode_evidence.py
python3 -B tools/logic_opcode_evidence.py --check

Compare an existing QEMU capture without rerunning QEMU:

python3 -B tools/picture_fuzz.py compare-capture base_004_clamped_absolute build/picture-fuzz/fixtures/base_004_clamped_absolute/qemu_capture.ppm

Current coverage

tests/test_graphics_rendering.py currently covers these behaviors:

  • PICDIR has 74 valid picture payloads. Entry 147 decodes to VOL.0 offset 0x2ffff and fails the local volume-header check; the test records this sentinel-like entry so it is not mistaken for a renderable picture.

  • Every valid picture payload ends with command byte 0xff; this anchors the local picture scanner’s stop condition.

  • Picture 1 renders deterministically under the current provisional renderer. The full 160 by 168 cell buffer hash is recorded as a regression value.

  • Picture 45, the longest currently observed valid picture payload, also renders deterministically and is used as a heavier stress sample.

  • The all-picture command census is fixed by tests. In the current local SQ2 resources, commands 0xf4 and 0xf5 are not used by any valid picture payload even though the interpreter implements them.

  • Synthetic unit tests directly cover both rare corner-path commands: 0xf4 starts with a vertical segment, then alternates horizontal/vertical; 0xf5 starts with a horizontal segment, then alternates vertical/horizontal.

  • Synthetic picture bytecode tests lock down seed-fill channel priority: visual fill is the expansion-test channel when enabled, but accepted cells still use the normal pixel write path and can update both active nibbles; control fill is used as the expansion-test channel only when visual drawing is disabled.

  • Synthetic line tests lock down the shared absolute/relative line helper. An absolute 0xf6 line from (0,0) to (3,1) and a relative 0xf7 line with packed delta byte 0x31 both plot (0,0), (1,0), (2,1), and (3,1). A long diagonal from (159,167) to (0,0) locks down the byte-width accumulator wrap observed in the executable and in QEMU fuzz captures.

  • Every valid view resource in SQ2 parses through the local view frame walker; the aggregate pass currently finds 2,066 frames and 50,640 rows.

  • All 203 valid local SQ2 view resources have reserved header bytes 01 01 at payload offsets +0x00/+0x01; observed runtime paths begin meaningful view parsing at byte +0x02.

  • Every decoded view row stays within its declared frame width. The largest observed cel dimensions are 88 by 129.

  • Two specific view cels, view 0 group 0 frame 0 and view 11 group 0 frame 0, render to stable hashes.

  • PPM output generated by the local renderer is parseable by the same helper used for QEMU screenshots.

  • A generated picture-45 fixture was run through the original interpreter in QEMU. After nearest-palette conversion and 4x2 downsampling from the top-left of the 640 by 400 capture, the original-engine visual output matched the local renderer with 0 mismatches out of 26,880 logical pixels.

  • A generated picture-1 plus view-11/group-0/frame-0 fixture was run through the original interpreter in QEMU. With object left 20, baseline Y 80, and priority/control 15, the original-engine capture matched the local compose_frame_on_picture() output with 0 mismatches out of 26,880 logical pixels.

  • Source-modeled object-overlay priority tests cover the IBM_OBJS.OVL:0x9e35 pixel gate: higher existing priority rejects a write, equal priority permits it, low-control cells scan downward for the comparison value, no scan hit behaves like comparison value zero, and a rejected pixel does not abort the rest of the run.

  • A generated picture-1 plus view-0/group-1/frame-0 fixture was run through the original interpreter in QEMU to validate the bit-0x80 frame orientation rewrite path. The original-engine capture matched the local mirrored-frame output with 0 mismatches out of 26,880 logical pixels.

  • Source-modeled mirror edge tests now cover helper 0x587d beyond the natural resource sample: an all-transparent row rewrites to an empty row, implicit trailing transparent width is emitted before reversed visible runs, widths above 15 are chunked into multiple transparent runs, and reversal starts at the first nontransparent run while preserving later explicit transparent runs in the reversed tail.

  • The current eight-case view batch validates normal view drawing, cached and mirrored bit-0x80 orientation, left-edge clipping, top-edge placement, right-edge placement, bottom-edge placement, and a low-priority object case. The timed polling carousel view_carousel_base_001 matched all eight from one original-engine process with 0 mismatches.

  • The optional --include-stress view batch adds eleven larger or transparent-color-focused cels, including transparent colors 0, 1, 2, 5, 6, 7, 8, 10, 13, 14, and 15, plus a bit-0x80 transparent-10 frame. QEMU snapshot batch view_stress_001 matched the older 17-case set with 0 mismatches and 0 errors; timed polling carousel view_carousel_stress_001 matched the current 19-case set with 0 mismatches and 0 errors from one original-engine process.

  • Local object-frame composition tests cover baseline placement, transparent pixels, direct high-priority rejection, and downward priority/control scanning from low-control cells. They also lock down the top-edge adjustment observed through QEMU: when a cel would start above row 0, the overlay path shifts left by the negative top and raises baseline_y so the top becomes 0.

  • Targeted QEMU object overlay probes now validate direct priority gating, transient visible-priority low-nibble selection, downward scan from low-control cells, selected clipping/placement edges, transparent-color variants, auto-derived priority after 0xae, persistent object-table setup, and multiple view 11 group/frame selections. The confirmed transient draw rule is that an object pixel draws when the discovered existing priority/control value is less than or equal to the object’s low priority nibble.

  • Targeted persistent-object movement probes run through QEMU with:

    python3 -B tools/object_movement_probe.py --dos-prefix ME --output build/object-movement-probes/batches/motion_modes_004.json --boot-wait 5 --draw-wait 8
    

    The current 17-case batch matched QEMU with 0 mismatches. It covers horizontal and vertical arrival at reachable targets, plus right-edge and bottom-edge completion when the target lies outside the reachable screen area. It also covers left/up movement, diagonal movement that straightens after one axis reaches the target band, non-divisible target distances, already-at-target and already-within-step completion, a zero step-size operand preserving the object’s current zero step, and a controlled picture whose control channel was filled with zero. The fixture logic initializes the object once, then reissues 0x51 (move_object_to) each cycle while the completion flag is clear. The same batch includes two object-object collision checks: object 0 stops before crossing object 1 by default, and reaches the target when bit 0x0200 is set on object 0. It also includes one autonomous mode-2 case: object 1 starts 0x53 (approach_first_object_until_near) toward object 0 with step 5 and near threshold 35; QEMU stops object 1 at (50,80), confirming that this path can complete from the countdown-gated dispatcher without reissuing the setup action from script logic. The same run also includes a countdown-gated mode-3 case where one 0x51 setup reaches (50,80) without script reissue, and a property-style random-motion case where 0x54 renders the object exactly at some valid final position. In the recorded run that random final position was (140,112).

  • A targeted single-case movement batch validates 0x44 (clear_object_bit_0200) by setting the collision-skip bit, clearing it, and observing that object-object collision blocking returns:

    python3 -B tools/object_movement_probe.py --dos-prefix MD --output build/object-movement-probes/batches/clear_skip_bit_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_collision_clear_skip_bit_blocks_again
    

    The batch matched QEMU with 0 mismatches.

  • A targeted single-case movement batch validates the visible mode-byte effect of 0x4e (clear_object_field_22_and_global) by starting random motion, immediately clearing object byte +0x22, and observing that the object remains at its starting position:

    python3 -B tools/object_movement_probe.py --dos-prefix ME --output build/object-movement-probes/batches/clear_field_22_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_field_22_after_random_motion_stops_motion
    

    The batch matched QEMU with 0 mismatches.

  • A targeted single-case movement batch validates the same object-0 motion-byte effect for 0x84 (set_global_0139_and_clear_object0_field_22) by starting random motion, immediately executing 0x84, and observing that the object remains at its starting position:

    python3 -B tools/object_movement_probe.py --dos-prefix G84 --output build/object-movement-probes/batches/action_84_motion_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case action_84_after_random_motion_stops_motion
    

    The batch matched QEMU with 0 mismatches. The global [0x0139] write remains source-backed; this capture proves the object motion-byte side effect.

  • Source-backed main-cycle analysis covers 0x83 (clear_global_0139): it selects object0-to-global direction mirroring at the pre-logic mirror point. Disposable script-level QEMU probes showed why a direct fixture is awkward: direction bytes written by logic after that mirror are restored from global [0x000f] before the next frame/update path can use them.

  • Source-backed keyboard-IRQ analysis covers 0xad (increment_global_1530) and GR v3 0xb5 (clear_key_release_event_gate). tests/test_input_model.py models the shared tracked-key IRQ latch: scan codes 0x47..0x51 use an enable table, keydown clears the other latches and sets one latch, release clears that latch and enqueues (type=2, value=0) only when the gate byte is nonzero. The same tests pin SQ2 0xad byte-increment wraparound and GR v3 0xad/0xb5 set/clear behavior. A direct QEMU fixture would depend on raw scan-code release timing, so this remains source-modeled rather than QEMU-validated.

  • Source-backed resource-event analysis covers 0x8e (set_global_0141_and_refresh): the action writes data.event.pair_capacity and resets the pair buffer inside update-list flush/rebuild calls. Existing replay QEMU cases validate downstream pair-log behavior for 0xab/0xac; 0x8e itself is covered by the compact disassembled reset path.

  • Targeted frame-timer probes validate visible effects of actions 0x46, 0x47, and 0x4c:

    python3 -B tools/object_movement_probe.py --dos-prefix MA --output build/object-movement-probes/batches/frame_timer_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case animation_interval_mode1_reaches_frame1 --case animation_clear_bit_0020_prevents_frame_advance --case animation_set_bit_0020_restores_frame_advance
    

    The batch matched QEMU with 3 matches, 0 mismatches, and 0 errors. It validates that 0x4c seeds the frame timer, 0x46 disables the timer-driven frame advance by clearing bit 0x0020, and 0x47 restores that advance.

  • A follow-up frame-mode batch validates actions 0x48, 0x4a, and 0x4b against the static code.object.advance_frame_by_mode model:

    python3 -B tools/object_movement_probe.py --dos-prefix MF --output build/object-movement-probes/batches/frame_timer_modes_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case animation_mode0_forward_loop_wraps_to_frame0 --case animation_mode2_backward_completion_reaches_frame0 --case animation_mode3_backward_loop_wraps_to_frame1
    

    The batch matched QEMU with 3 matches, 0 mismatches, and 0 errors. It validates forward looping mode 0 (0x48), backward completion mode 2 (0x4b), and backward looping mode 3 (0x4a) on view 11/group 0.

  • Targeted movement batches validate additional object control/priority bits:

    python3 -B tools/object_movement_probe.py --dos-prefix M1 --output build/object-movement-probes/batches/control_class_1_hidden_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_blocks --case move_control_1_set_bit_0002_still_hidden --case move_control_1_clear_bit_0002_still_hidden
    python3 -B tools/object_movement_probe.py --dos-prefix MB --output build/object-movement-probes/batches/rect_bit_0002_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_rect_boundary_without_bit_0002_stops_at_edge --case move_rect_boundary_set_bit_0002_reaches_target --case move_rect_boundary_clear_bit_0002_stops_again
    python3 -B tools/object_movement_probe.py --dos-prefix M9 --output build/object-movement-probes/batches/control_bits_0900_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_2_set_bit_0100_blocks --case move_control_2_clear_bits_0900_reaches_target --case move_control_3_set_bit_0800_blocks --case move_control_3_clear_bits_0900_reaches_target
    python3 -B tools/object_movement_probe.py --dos-prefix RB --output build/object-movement-probes/batches/rect_bounds_clear_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_rect_boundary_clear_bounds_reaches_target
    

    These batches matched QEMU with 0 mismatches. They validate 0x58/0x59 through rectangle-boundary crossing behavior, 0x40/0x41 through control-class movement rejection, 0x42 restoring movement after either rejection bit is set, and 0x5b clearing rectangle bounds after 0x5a so the object can cross the former boundary. The control-class-1 batch also records that a priority-14 object on a full control-class-1 picture remains hidden even when bit 0x0002 is set.

  • tests/test_picture_fuzz.py covers deterministic fuzz generation, manifest writing, Python render-result recording, scaled synthetic capture comparison without booting QEMU, QEMU unsafe-case rejection, and mocked batch reporting.

  • The current fuzz corpus command above generates 1,062 synthetic picture cases: 38 curated base cases plus 1,024 deterministic random cases. Of those, 1,060 are marked safe for automated QEMU runs; the unsafe cases intentionally include payloads such as no terminator or missing command operands that could make the original interpreter treat garbage memory as picture data. These unsafe cases are retained only as harness guardrails and are not part of the behavioral model.

  • Curated QEMU fuzz batches currently matching the local renderer include: all safe base cases through base_020_pattern_random_sequence, plus seed_fill_edges_001 for base_021_visual_fill_full_height_barrier, base_022_visual_fill_multi_seed_boxes, and base_023_control_fill_ignores_visual_barrier, and pattern_interleaved_001 for base_024_pattern_bypass_mask, base_025_interleaved_line_fill_pattern, and base_026_pattern_random_bypass_sequence, plus pattern_channel_masks_001 for base_027_pattern_visual_control_channels, base_028_pattern_visual_disabled_control_only, and base_029_pattern_control_disabled_visual_only, plus raw_operand_001 for base_033_raw_visual_operand, base_034_raw_control_operand, and base_035_raw_pattern_mode_operand.

  • The seed_fill_edges_001 snapshot batch matched the original engine with 3 matches, 0 mismatches, and 0 errors. The first two cases validate visible full-height barrier and multi-seed fill geometry. The third validates that a control-only seed fill does not disturb the visible visual barrier; the local cell-level renderer test records the corresponding control-channel crossing behavior.

  • The pattern_interleaved_001 snapshot batch matched the original engine with 3 matches, 0 mismatches, and 0 errors. These cases validate pattern mode bit 0x10 bypassing the row/column mask test, a rectangle/fill/line/pattern sequence in one valid picture stream, and a two-plot pattern sequence with both 0x10 and pseudo-random bit 0x20 set.

  • The pattern_channel_masks_001 snapshot batch matched the original engine with 3 matches, 0 mismatches, and 0 errors. These cases validate the visible EGA surface for pattern plotting with both channels active, visual disabled, and control disabled. The local renderer tests assert the corresponding control-buffer nibbles from the source-backed common pixel-writer path.

  • The command_resume_001 snapshot batch matched the original engine with 3 matches, 0 mismatches, and 0 errors. These cases validate that command bytes terminate and remain pending after an incomplete absolute-line coordinate pair, a corner path segment list, or a seed-fill point list; the scanner then interprets the same command byte normally.

  • The raw_operand_001 snapshot batch matched the original engine with 3 matches, 0 mismatches, and 0 errors. These cases validate the complementary scanner rule that 0xf0, 0xf2, and 0xf9 consume command-looking bytes as raw one-byte operands instead of treating them as new commands.

  • The relative_underflow_001 snapshot batch matched the original engine with 2 matches, 0 mismatches, and 0 errors. These cases validate that relative line subtraction underflows in an 8-bit coordinate register and then clamps to the right or bottom edge, not to zero.

  • A 16-case random QEMU batch covering line, corner, pattern, fill, and scanner categories also matched with 0 mismatches.

  • The fuzz pass exposed a diagonal-line mismatch for the two edge-line cases. The original engine drew the long line with 8-bit accumulator wrap; after the Python renderer was corrected, both captures matched with 0 mismatches out of 26,880 logical pixels.

  • Targeted lower-right pattern fuzzing exposed that pattern plotting can feed X coordinate 160 to the linear pixel writer. The original engine stores that byte as X 0 on the next scanline rather than clipping it. After the local renderer was changed to use linear writes for pattern pixels, circular and rectangular lower-right edge cases matched QEMU with 0 mismatches.

  • Real-picture snapshot batch picture_base_001 matched picture 1 and picture 45 with 2 matches, 0 mismatches, and 0 errors.

  • Broad real-picture snapshot batch picture_broad_001 matched pictures 1, 6, 17, 43, 44, 45, 46, and 76 with 8 matches, 0 mismatches, and 0 errors.

  • Full present-picture snapshot batch picture_all_001 matched all 74 valid local SQ2 picture resources with 74 matches, 0 mismatches, and 0 errors.

  • Key-driven carousel smoke batch picture_carousel_base_mapped_fkey_001 matched pictures 1 and 45 from one engine process, but broader key-driven runs still stall or leave UI artifacts.

  • Timed polling carousel batch picture_carousel_broad_timed_poll_fast_001 matched the eight-picture broad preset from one engine process with 8 matches, 0 mismatches, and 0 errors.

  • Suite-level timed polling carousel batch picture_carousel_broad_suite matched the same eight-picture broad preset as part of qemu_broad_002.json, also with 8 matches, 0 mismatches, and 0 errors.

  • Chunked timed polling carousel batch picture_carousel_all_timed_poll_chunk16_001 matched all 74 valid local SQ2 picture resources with 74 matches, 0 mismatches, and 0 errors across five chunks. A single all-picture carousel reached an original-engine disk prompt after picture 19 and is not used as renderer parity evidence beyond those first 19 matches.

These tests are still intentionally resource-focused. They protect the parser and data model while the picture draw helpers are being matched against the executable and QEMU captures.

Provisional areas

The view cel renderer is based on the row run-length format documented from the IBM object overlay and now has both a small QEMU validation batch and an optional 19-case stress carousel for larger cels and transparent-color variants. It still needs broader priority interactions with different picture control bands and runtime animated-object state changes.

The picture renderer is still a compatibility scaffold, but it now has direct QEMU fuzz coverage for scanner behavior, exact/right-lower-edge diagonal lines, visual/control seed fill, bounded fill barriers, pseudo-random pattern plotting, lower-right pattern edge wrapping, mask-bypass pattern plotting, interleaved line/fill/pattern streams, pattern channel-mask states, safe truncated coordinate data with command-byte resume, raw command-looking operands, relative-line underflow, and every valid local SQ2 picture resource. The seed-fill traversal class is source-backed as a horizontal span fill with deferred stack state; the local renderer uses a queue because the observable contract for valid finite data is the final connected region under the selected channel target test. Full-height narrow-barrier, multi-seed, visible control-only barrier, and current all-picture SQ2 resource cases match the original engine in QEMU. Fuzz cases should still expand toward additional valid interleavings and future cross-game/interpreter real-resource coverage before picture hashes are treated as complete original-engine parity checks beyond this SQ2 executable. Odd/even mask divergence remains outside the full-EGA target unless another local interpreter version or SQ2 behavior requires it.

Future tests should prefer focused fixtures: a room or script state that draws a single picture, a single moving object, or a known cel at a known screen position. Those captures can then be compared against QEMU screenshots before being promoted from provisional renderer tests to compatibility tests.

Gold Rush / AGI v3 now has a first targeted behavior probe:

python3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --picture 1 --run-qemu --output build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json

This probe builds four copied v3 fixtures under build/: one switches to room 0x49, and the others switch to alias targets 0x7e, 0x7f, and 0x80. All fixtures patch logic 0x49 to draw the same picture and keep a minimal logic-0 dispatcher tail call_logic_var(v0). The QEMU result is promoted because all alias captures match the direct-room capture and are nonblank. Earlier equal all-black captures from a missing dispatcher tail are kept only as harness lessons in the chronological notes.

The same v3 probe tool also validates the expanded GR key-map capacity:

python3 -B tools/gr_v3_behavior_probe.py --probe key-map-capacity --game-dir games/GR --picture 1 --fixture-root build/gr-v3-behavior/key-map-capacity-fixtures --dos-prefix GRK --run-qemu --output build/gr-v3-behavior/key_map_capacity_qemu_pic001_002.json --boot-wait 5 --draw-wait 8

The fixture fills 48 dummy 0x79 key-map slots, places typed x in slot 48, and checks the resulting status byte by drawing original GR picture 1. QEMU promotes the result only when the keyed capture matches a direct picture draw and the no-key control does not. In the promoted run the direct and keyed captures are nonblank with 14 unique colors, while the no-key capture is blank.

The GR motion-mode 4 dispatcher branch has an instrumented QEMU probe rather than an ordinary unmodified-game-data probe:

python3 -B tools/gr_v3_behavior_probe.py --probe motion-mode-4 --game-dir games/GR --fixture-root build/gr-v3-behavior/motion-mode-4-fixtures --dos-prefix GRM --run-qemu --output build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.json --snapshot-raw build/gr-v3-behavior/snapshot/motion_mode_4.raw --snapshot-qcow build/gr-v3-behavior/snapshot/motion_mode_4.qcow2 --boot-wait 5 --draw-wait 8

This probe builds copied GR fixtures under build/. The mode-3 case uses the unmodified interpreter and action 0x51 to move object 0 from (20,80) to (50,80). The mode-4 case patches only the copied interpreter byte at loaded image offset 0x707f inside action 0x51, changing the mode seed from 3 to 4; the dispatcher and target-direction helper are otherwise the original GR code. The QEMU report passed because the instrumented mode-4 capture matched the unmodified mode-3 capture, while a stationary control capture did not. Because this is an instrumented interpreter probe, it validates the internal GR dispatch branch but is not counted as evidence that ordinary script bytecode can create mode 4 by itself.

The GR menu-gate probe validates action 0xb1 with ordinary generated logic in a copied GR fixture:

python3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-suite-fixtures --dos-prefix GRG --run-qemu --output build/gr-v3-behavior/menu_gate_suite.json --snapshot-raw build/gr-v3-behavior/snapshot/menu_gate_suite.raw --snapshot-qcow build/gr-v3-behavior/snapshot/menu_gate_suite.qcow2 --boot-wait 5 --draw-wait 8

This probe compares a blocked marker control with two menu-request fixtures. The 0xb1(0) request matches the blocked control; the 0xb1(1) request differs from both the control and the zero-gate case, confirming that the gate controls entry into the modal menu interaction path.

The generated GR v3 picture/view fixture probe validates the copied-fixture writer itself against the original interpreter:

python3 -B tools/gr_v3_behavior_probe.py --probe synthetic-picture-view --game-dir games/GR --fixture-root build/gr-v3-behavior/synthetic-picture-view-suite-fixtures --dos-prefix GSP --run-qemu --output build/gr-v3-behavior/synthetic_picture_view_suite.json --snapshot-raw build/gr-v3-behavior/snapshot/synthetic_picture_view_suite.raw --snapshot-qcow build/gr-v3-behavior/snapshot/synthetic_picture_view_suite.qcow2 --boot-wait 5 --draw-wait 8

This probe compares three copied GR fixtures: a blank control, a generated picture-nibble picture record, and the same generated picture with a direct v3 view record overlaid through action 0x7a. The promoted suite report build/compatibility-suite/qemu_v3_synthetic_picture_view_001.json passed: picture-only differed from blank by 215,040 pixels, and picture-plus-view differed from picture-only by 128 pixels. This promotes the fixture-writing path as reusable compatibility infrastructure; it does not replace the source-backed picture/view renderer model.

The GR save-XOR extraction probe validates the source-mapped save transform:

python3 -B tools/gr_v3_behavior_probe.py --probe save-xor-extract --game-dir games/GR --fixture-root build/gr-v3-behavior/save-xor-fixtures --dos-prefix GRS --run-qemu --output build/gr-v3-behavior/save_xor_extract_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/save_xor_extract.raw --snapshot-qcow build/gr-v3-behavior/snapshot/save_xor_extract.qcow2 --post-run-raw build/gr-v3-behavior/snapshot/save_xor_extract_after.raw --save-output build/gr-v3-behavior/SG_001.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08

This fixture omits 0x8f verify_game_signature, so the original engine writes blank-prefix SG.1. The extracted save has block lengths 1028, 989, 1811, 100, and 12; the third block differs after gr_v3_object_inventory_save_xor() and a second transform restores the emitted bytes. This confirms the v3 object/inventory block encoding without relying on GR’s verifier/save-prefix path.

The signed variant exercises that verifier/save-prefix path:

python3 -B tools/gr_v3_behavior_probe.py --probe save-xor-extract --verify-signature --game-dir games/GR --fixture-root build/gr-v3-behavior/save-xor-signed-fixtures --dos-prefix GRS --run-qemu --output build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/save_xor_extract_signed.raw --snapshot-qcow build/gr-v3-behavior/snapshot/save_xor_extract_signed.qcow2 --post-run-raw build/gr-v3-behavior/snapshot/save_xor_extract_signed_after.raw --save-output build/gr-v3-behavior/GRSG_001.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08

This run passed with expected save file GRSG.1, first-block prefix 47 52 00, and the same block lengths and third-block XOR hashes as the blank-prefix run.

Recent attempted-but-not-promoted logic fixtures:

python3 -B tools/logic_interpreter_probe.py --dos-prefix RV --output build/logic-interpreter-probes/batches/room_reentry_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_sets_new_room_flag --case switch_room_var_sets_new_room_flag
python3 -B tools/logic_interpreter_probe.py --dos-prefix RD --output build/logic-interpreter-probes/batches/room_dispatch_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_then_logic0_calls_current_room --case switch_room_var_then_logic0_calls_current_room
python3 -B tools/logic_interpreter_probe.py --dos-prefix DK --output build/logic-interpreter-probes/batches/down_key_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case mapped_down_arrow_sets_status_byte
python3 -B tools/logic_interpreter_probe.py --dos-prefix SD --output build/logic-interpreter-probes/batches/save_description_copy_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case copy_save_description_to_string_slot_copies_buffer

The early room re-entry cases used synthetic logic-0 validation around 0x12/0x13 and even restored the current logic entry IP with 0x92 before switching, but they did not reach the validation draw. The first room-dispatch cases copied the SQ2 logic-0 pattern more closely by switching rooms and then calling call_logic_var(v0) to run the current room logic, but still produced the same blank-screen mismatch. They are superseded by room_switch_reentry_001, whose destination room logic performs its own post-switch picture/view setup under flag 5. The down-arrow case attempted to map raw key word 0x5000 with 0x79 and drive QEMU monitor sendkey down; it did not set the target status byte. That case remains a useful harness experiment but is intentionally absent from the reusable probe registry. The save-description copy attempt patched the fixture’s AGIDATA.OVL bytes at 0x0e72 to look and then tried to validate action 0xaa by comparing the copied string slot against a message-backed look string. The validation draw did not occur, so the case remains absent from the reusable probe registry; the static patch targeted the fixture file, while action 0xaa reads the interpreter’s runtime data segment at 0x0e72. Source-backed disassembly now covers 0xaa; dynamic validation would need to drive the save/restore selector path that populates the runtime save-description buffer.

The remaining source-backed action rows have also been audited for whether a direct dynamic probe would be representative. 0x6e is a CRT/display-register timing effect whose visible screenshot is not a stable semantic oracle for the full-EGA target. 0x83 selects object0/global direction mirroring at a pre-logic main-cycle point; script writes after that point are overwritten by the next mirror/restore path, so a bytecode-only fixture mostly measures the harness. 0x8e resets event-pair capacity state, while the observable replay save/restore behavior already has QEMU evidence through 0xab and 0xac. 0xaa needs the runtime save selector buffer rather than a static data-file patch. 0xad depends on raw keyboard release timing in the IRQ hook. These opcodes remain covered for the current spec target by disassembly and adjacent observable behavior; promote a dynamic probe only if a future harness can drive the relevant runtime state directly.

Current QEMU screenshots captured through screendump use full VGA-sized PPM frames. A generated picture-only SQ2 fixture produced a 640 by 400 capture. For picture output, the observed normalization is top-left aligned 4x2 downsampling to the local 160 by 168 logical picture buffer. This transform has now been validated for both the picture-45 fixture and a fixture with one overlaid view cel. It has also been validated for a bit-0x80 view cel whose row data is rewritten before drawing.

QEMU graphics-text firmware compatibility

The QEMU-supplied VGA BIOS is not a faithful oracle for one graphics-text interface used by the observed 2.936 interpreter. The interpreter temporarily redirects BIOS interrupt vector 43h to an inverted glyph buffer, asks BIOS video service INT 10h/AH=09h to draw character 80h, and then restores the vector. QEMU’s VGA BIOS renders its own compiled-in character 80h instead of reading the current vector. Dialogs consequently show one repeated symbol even though picture, view, palette, positioning, and the original glyph bytes are correct.

Generate the verified compatibility ROM with:

python3 -B tools/setup_vgabios.py --force

Launch QEMU with:

qemu-system-i386 -m 16 -boot c \
  -drive file=build/freedos/freedos.img,format=raw,if=ide,index=0,media=disk \
  -vga none \
  -device VGA,romfile="$(pwd)/build/vgabios/vgabios-0.7a-int43.bin" \
  -display vnc=127.0.0.1:5 -monitor stdio

The setup tool reads the tracked pristine official LGPL VGABIOS 0.7a binary from third_party/vgabios/, checks its complete SHA-256, assembles a 44-byte planar-glyph fetch routine, verifies the exact binary patch site and unused destination area, redirects that site, recomputes the option-ROM checksum, and checks the complete deterministic output digest. The resulting ROM remains generated output under build/ and must not be committed. It changes only the graphics-mode glyph source from a private firmware array to the active INT 43h vector; all existing VGA pixel-writing code remains in place.

The pristine binary is committed with its upstream LGPL 2.1 text and a provenance file giving the official binary and complete-source release URLs, plus SHA-256 values for both. tools/setup_freedos_image.py invokes the VGA BIOS builder during normal image setup; --skip-vgabios opts out for a deliberate bundled-firmware control run.

The original and corrected FIXAGI.COM experiments are not substitutes for this ROM. Copying an 8-by-8 font to F000:FA6E addresses a different possible failure: missing bytes at the address read directly by the interpreter. In this QEMU configuration those bytes already match the font returned by BIOS, and the interpreter successfully copies and inverts them. The failure occurs later, when the VGA BIOS ignores the redirected vector.

Shared QEMU harness launch code selects the generated patched ROM automatically when it exists. AGI_VGABIOS=/path/to/rom.bin selects another option ROM, and AGI_VGABIOS=default deliberately disables the compatibility override for a control run.

One-GiB FreeDOS test image

tools/setup_freedos_image.py now creates a fresh 1 GiB raw disk rather than using the 32 MiB LiteUSB source image as the final disk. The source distribution still supplies the verified FreeDOS file tree, MBR boot code, and FAT16 partition boot code.

The builder performs these steps:

  1. Download the pinned FreeDOS ZIP to a temporary cache filename and expose it only after a complete transfer, preventing interrupted downloads from becoming persistent invalid cache entries.
  2. Verify the complete archive SHA-256 and extract its largest raw image to a temporary source path.
  3. Copy the complete source FAT tree to a temporary host directory.
  4. Create a sparse 1,073,741,824-byte raw output disk.
  5. Preserve the source MBR boot code but replace its partition table with one active type-0x0e FAT16-LBA partition beginning at LBA 2048.
  6. Format the partition as FAT16 with 32 KiB clusters while using the source FreeDOS partition boot sector as the boot-code template.
  7. Copy the complete FreeDOS tree into the enlarged volume and atomically replace the requested output image.
  8. Apply the project prompt boot files and optional explicitly selected game copy through the detected 1 MiB partition offset.

--image-size-mib accepts 64 through 2048 MiB and defaults to 1024. The generated default partition contains 2,095,104 sectors and reports roughly 1,032,781,824 free bytes before private games are copied.

The 1 GiB image was populated with all 16 then-current top-level private game directories. Recursive host/DOS file counts matched for every directory. After copying the games, the FAT volume reported 1,009,057,792 free bytes. QEMU booted the image through the generated INT-43h-compatible VGA BIOS and reached the FreeDOS C:\> prompt.

FreeDOS prints an InitDiskWARNING because its CHS consistency heuristic cannot represent the whole partition inside the legacy 1024-cylinder range. The MBR partition is explicitly LBA-addressed, the full FAT16 volume mounts, and file-count and boot checks pass. This warning is a geometry diagnostic, not a truncated filesystem or failed boot.

Persistent SQ1.22 interpreter controller

tests/test_interpreter_controller.py covers the reusable non-QEMU parts of tools/interpreter_controller.py: high-bit-first flag decoding, 43-byte object records, visual/priority nibble rendering, PPM parsing, modal-border detection, nested state predicates, keyboard mapping, runtime-image signature discovery, verified SQ1.22 hook offsets, and stack classification for the shared string and modal waits.

The live integration used a disposable qcow2 copy containing the explicitly selected games/SQ1.22 input. The controller found runtime base 0x63a0 and DS 0x102f, stopped at cycle image 0x015b, and exposed 256 variables, 256 flags, 18 object records, 25 inventory entries, four current logic-cache records, and a 160-by-168 priority PPM. Enter advanced past the title into the ordinary opcode-0x73 string editor. Its interrupted stack contained return 0x0df8; the semantic submit endpoint entered roger and returned to a cycle stop in room 2. Typing look entered a modal whose stack contained return 0x1d25; the independent screenshot detector found one border and agreed with the interpreter window-active state. Dismissal returned to the cycle hook and both modal checks cleared.

A targeted breakpoint experiment selected only the string hook and reached image 0x0df2 directly. Leaving cycle and UI hooks installed together did not reach the UI hook, establishing an observed one-execution-breakpoint restriction for this QEMU real-mode GDB path. The production controller keeps one hook active, interrupts and classifies a blocking stack when a cycle does not return, switches to the matching UI hook, and restores the cycle hook on acceptance. This emulator limitation is a harness property, not promoted AGI behavior.

Final local verification passed 13 focused controller tests and all 451 tests in the explicit AGI_GAME_DIR=games/SQ2 repository run, with four expected skips. Both mdBooks built successfully. Running discovery without an explicit game directory remains invalid by design and exits during imports that require resource evidence.

XMAS.230 / AGI 2.230 profile guards

The focused 2.230 compatibility additions are deterministic and do not require QEMU:

  • dispatch-table detection asserts data offsets 0x03e7/0x0673, 155 actions, and 19 conditions for the selected local overlay;
  • a source-byte guard derives action 0x31 from that table and asserts the handler’s low-nibble mask before the last-cel subtraction;
  • the portable view model decodes packed cel count, mutable orientation, and mirror-on-change bits, including forward and reverse orientation rewrites;
  • paired selected-resource evidence compares XMAS.230 view 10 with its 2.272 counterpart and proves the loop-header/per-cel marker migration; and
  • save tests parse plain XMAS.230 metadata and guard 18 object records, the 0x03db/0x0306/0x000f fixed blocks, and the selected logic’s 200-pair replay dimension (0x0190).

These tests prove the promoted valid-data format and selected-game dimensions. They do not establish malformed packed-loop behavior, absent-volume resources, canonical pristine reserved save bytes, non-EGA adapters, or interactive joystick paths.

Developing and Validating Playthroughs

This chapter describes a clean-room workflow for turning local game resources into a precise, repeatable playthrough. It combines static analysis with controlled experiments in the original interpreter. The aim is not merely to write a walkthrough that sounds plausible. The finished route should state the required game state, the exact player action, the expected state change, and the conditions under which the step must be retried or rejected.

The static workflow applies to every selected game. The current interpreter controller is narrower: its only implemented adapter is for the local SQ1.22/2.917 interpreter. Do not point it at another interpreter merely because the resource format looks similar. A new build needs a separately evidenced profile and adapter as described in Interpreter Controller.

What a finished playthrough must prove

A useful playthrough has three related outputs:

  1. A prose route that a person can follow.
  2. A machine-readable state graph that records preconditions, actions, automatic transitions, waits, random retries, and failure edges.
  3. Dynamic confirmation from the original interpreter, including state snapshots and enough trace data to distinguish a correct hypothesis from a lucky visual result.

Treat every important transition as a state contract:

PartQuestion
Starting stateWhich room, variables, flags, inventory, object states, coordinates, and input mode must already hold?
ActionWhich command, key, movement segment, wait, or dialog response occurs?
PostconditionWhat observable state proves that the action succeeded?
InvariantsWhat must remain true while the action is in progress?
Abort conditionsWhich death, wrong-room, modal, timer, or object state means the hypothesis failed?
BoundHow many interpreter cycles may be spent before stopping to investigate?
EvidenceWhich logic offsets, messages, object metadata, and picture/control resources support the contract?

A screenshot alone is not a sufficient postcondition. A screen can look right while the required flag, inventory item, score award, or room controller is wrong. Conversely, a correct input can open a modal or shared string editor before its eventual state change. Record both the semantic stop and the game state.

Work from static evidence first

Interactive play should test a hypothesis derived from local evidence. It should not be the primary way to discover what to try.

Select and inventory the evidence set

Always select the private input explicitly. For example:

export AGI_GAME_DIR=games/SQ1.22
python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/sq1_22/index.json
python3 -B tools/disassemble_logic.py 1 2 3 --messages

The playthrough index is a first-pass inventory of conditions, actions, parser-word groups, messages, and score-like operations. It is not a reachability proof. Inspect the full disassembly around every candidate event. A syntactic score increment may be:

  • guarded by a one-shot flag;
  • part of a mutually exclusive route;
  • canceled by a later subtraction;
  • reachable only after another room controller changes state;
  • a death, joke, or rollback branch rather than part of a winning route.

Record the selected game directory, interpreter version, resource counts, and maximum-score source in the playthrough chapter and graph provenance.

Expand every scoring action into preconditions

For each selected award, trace backward until all required conditions are explicit. Common preconditions include:

  • current and previous room variables;
  • flags and phase variables set by other logics;
  • carried, absent, consumed, or room-local inventory items;
  • parser word groups and acceptable synonym combinations;
  • ego baseline or full-width containment in a rectangle;
  • another object’s position, distance, animation frame, motion mode, or visibility;
  • an active or inactive modal/string input mode;
  • timer variables, cycle counts, or completion flags;
  • random results and the state needed to retry safely.

Do not reduce this to “go there and type the command.” Parser acceptance is a conjunction of words, state, location, and timing. The same words can be ignored, rejected, or interpreted differently in another phase.

Reconstruct room topology from pictures and logic

Render both channels for every room on the proposed route:

export AGI_GAME_DIR=games/SQ1.22
python3 -B tools/render_picture.py 3 --channel visual \
  --output build/playthrough/sq1_22/picture_003_visual.ppm
python3 -B tools/render_picture.py 3 --channel control \
  --output build/playthrough/sq1_22/picture_003_control.ppm

The visual picture explains what the scene represents. The priority/control picture explains where movement is likely to be accepted. The latter is often the more important navigation source.

Use these rules when planning movement:

  • Ego occupies a baseline footprint, not a single point. Test the entire width from its left X coordinate at its baseline Y coordinate.
  • Control values 0 and 1 are blocked by default in the current controller, but room logic can change horizons, fixed priority, block handling, object collision, or the effective geometry.
  • A base picture is not necessarily the current picture. Overlays and geometry-changing actions can alter the live priority channel, so capture it again after the relevant state transition.
  • A visually open region can be blocked, while an unremarkable colored notch can be the intended door or ramp.
  • A wall on the current screen often means the route passes through another room. Multi-level rooms commonly use an elevator, stair, corridor, or adjacent-room loop rather than a direct crossing.
  • Room exits are state transitions. Trace the room logic that recognizes the boundary instead of assuming that every screen edge leads somewhere.

If a direct route fails, do not repeatedly steer at the obstacle. Re-open the room logic and the visual/control pair, identify the actual topology, and form a new route hypothesis.

Separate deterministic waits from randomness

Static resources often reveal the condition that ends a wait even when they cannot predict when it will occur. Represent deterministic timing as a bounded state wait, such as a phase variable reaching a value or an animation completion flag becoming set. Do not replace it with an arbitrary wall-clock sleep after interpreter discovery.

Represent randomness explicitly. A random dialogue choice, gambling result, or wandering-object phase should have:

  • a success predicate;
  • a failure or continue predicate;
  • a checkpoint from which retry is valid;
  • a resource or inventory bound that prevents endless destructive retries.

Random failure is not evidence that the route hypothesis is wrong. Failure to reach either the documented success or retry state within the cycle bound is.

Build an explicit state graph

The reusable graph should distinguish states from actions. In the existing SQ1.22 graph, each award has a precondition node followed by a score_action node. Edges contain player input, movement, waiting, automatic transitions, and random retry behavior.

A minimal fragment has this shape:

{
  "game": {"maximum_score": 2},
  "score_route": ["score_example"],
  "nodes": [
    {
      "id": "ready_example",
      "kind": "precondition",
      "room": "6",
      "requirements": ["v52=0", "ego width inside target rectangle"]
    },
    {
      "id": "score_example",
      "kind": "score_action",
      "score_delta": 2,
      "evidence": [{"logic": 6, "ip": "00e0"}]
    }
  ],
  "edges": [
    {
      "from": "ready_example",
      "to": "score_example",
      "kind": "player_action",
      "instruction": "submit the statically accepted command and wait for its completion flag"
    }
  ]
}

Keep requirements machine-readable where practical, but retain evidence locations and prose for distinctions such as full-width versus left-baseline geometry. Validate that every selected score node occurs exactly once in the route and that the score ledger equals the declared maximum.

Render the graph with:

python3 -B tools/render_playthrough_graph.py \
  docs/src/games/sq1_22_success_path.json \
  --output docs/src/games/sq1_22_success_path.svg

The renderer validates node references and the score ledger before generating the vertical Graphviz diagram. A valid graph proves internal consistency, not dynamic reachability. Dynamic replay remains a separate evidence layer.

Plan each dynamic experiment before running it

Before sending input, write down:

  • the state snapshot from which the experiment starts;
  • the static logic and picture evidence;
  • the smallest action that tests the hypothesis;
  • the expected postcondition and state delta;
  • invariants and abort predicates;
  • the maximum cycle count;
  • the checkpoint to restore if the hypothesis fails.

Prefer one semantic question per experiment. “Can ego reach this side of the control opening without leaving room 3?” is useful. “Navigate the whole ship until something interesting happens” is not. Small experiments make state deltas and failed assumptions understandable.

Start an interpreter-controller session

The commands below are specifically for the implemented SQ1.22/2.917 adapter. First create a disposable play disk; never modify games/:

python3 -B tools/interpreter_controller.py prepare \
  --base-image build/freedos/freedos.img \
  --game-dir games/SQ1.22 \
  --dos-game-dir SQ122 \
  --raw-output build/interpreter-controller/session/sq122.raw \
  --output build/interpreter-controller/session/sq122.qcow2

In a persistent terminal, launch visible QEMU and the localhost API:

python3 -B tools/interpreter_controller.py serve \
  --disk build/interpreter-controller/session/sq122.qcow2 \
  --game-dir games/SQ1.22 \
  --display cocoa,zoom-to-fit=on \
  --runtime-dir build/interpreter-controller/runtime \
  --capture-dir build/interpreter-controller/captures \
  --port 8765

Add --capture-every-cycle --capture-logical-buffers when producing a complete recording. Those options deliberately trade throughput and disk space for an image/state record at every cycle.

QEMU starts paused at reset. Resume it, allow DOS to boot, launch the copied game, and discover the first interpreter hook:

curl -sS -X POST http://127.0.0.1:8765/v1/vm/continue \
  -H 'Content-Type: application/json' -d '{}'
sleep 5
curl -sS -X POST http://127.0.0.1:8765/v1/input \
  -H 'Content-Type: application/json' \
  -d '{"action":"host_type","text":"cd \\sq122\nsierra\n"}'
sleep 2
curl -sS -X POST http://127.0.0.1:8765/v1/instrument/discover \
  -H 'Content-Type: application/json' \
  -d '{"wait_for_hook":true}'

The two sleeps are bootstrap waits for firmware, DOS, and program loading. After discovery, use interpreter cycles and state predicates instead of host time.

Confirm the selected adapter and initial state:

curl -sS http://127.0.0.1:8765/v1/profile | python3 -m json.tool
curl -sS http://127.0.0.1:8765/v1/state | python3 -m json.tool

Inspect state before acting

Useful read-only endpoints are:

GET /v1/state
GET /v1/variables
GET /v1/flags
GET /v1/objects
GET /v1/inventory
GET /v1/logics
GET /v1/dialog
GET /v1/input/state
GET /v1/trace?since=0&limit=1000
GET /v1/picture/visual.ppm
GET /v1/picture/priority.ppm
GET /v1/screenshot.png

Save live images without advancing the interpreter:

curl -sS http://127.0.0.1:8765/v1/picture/priority.ppm \
  -o build/interpreter-controller/live_priority.ppm
curl -sS http://127.0.0.1:8765/v1/screenshot.png \
  -o build/interpreter-controller/live_screen.png

Compare the live room, ego position and width, current logic/resume IP, inventory, relevant variables and flags, input mode, and pending keys with the planned starting contract. If the precondition is false, do not send the action merely to see what happens.

Prefer semantic transactions to raw keys

POST /v1/transactions combines a precondition, one semantic action, postconditions, invariants, and a cycle bound. It also returns a state delta and the relevant trace slice. For example, a deterministic wait can be stated as:

curl -sS -X POST http://127.0.0.1:8765/v1/transactions \
  -H 'Content-Type: application/json' \
  -d '{
    "idempotency_key":"wait-room1-phase-33",
    "precondition":{"path":"room","op":"eq","value":1},
    "action":{
      "type":"wait_for_state",
      "predicate":{"path":"variables.33","op":"eq","value":1},
      "max_cycles":2000,
      "invariants":[{
        "name":"remain-in-room-1",
        "predicate":{"path":"room","op":"eq","value":1}
      }]
    },
    "postcondition":{"path":"variables.33","op":"eq","value":1},
    "max_cycles":2000
  }' | python3 -m json.tool

For a parser action, use {"type":"command","text":"..."}. For a shared string editor, use submit_string; for a direction or movement contract, use select_direction, move_until, waypoints, or navigate_priority. The full action list is in the controller chapter.

Interpret transaction results carefully:

  • succeeded means an expected postcondition was observed.
  • already_satisfied means no input was needed.
  • precondition_failed means the plan did not apply to the current state.
  • invariant_failed, interrupted, or timeout means stop and investigate.
  • outcome_certainty distinguishes an observed result from delivered input whose game outcome has not yet been proved.

An idempotency key prevents accidental duplicate input within the current timeline. Reusing the same key and request returns the recorded result. A different request with the same key is rejected. Restoring a checkpoint clears the cache because the game state has moved backward.

Treat input delivery and game outcome separately

QEMU may accept key-down just before the interpreter reaches its next hook and then reject key-up because the VM is already stopped. The controller records that as release_pending, retains the held-key state, and attempts bounded reconciliation before another tap. It is not proof that the game action failed.

Check /v1/input/state when input behaves unexpectedly. Use these recovery operations instead of blindly repeating a key:

curl -sS -X POST http://127.0.0.1:8765/v1/input \
  -H 'Content-Type: application/json' \
  -d '{"action":"reconcile","max_attempts":4}'

curl -sS -X POST http://127.0.0.1:8765/v1/input \
  -H 'Content-Type: application/json' \
  -d '{"action":"release_all","max_attempts":8}'

Success must come from the postcondition, not from the fact that QMP accepted a keystroke.

Handle input modes and dialogs explicitly

The shared text editor is an interpreter input mode, not a special title-screen case. The same mode can be used for a name, a machine, a code, or another game-defined prompt. Use the current stop_reason to choose the operation:

  • cycle_boundary: ordinary commands and movement are available;
  • string_prompt_wait: submit through /v1/string-prompt/submit or a submit_string transaction action;
  • modal_wait: identify and dismiss the current dialog.

Do not press Enter merely because text is visible. First query the dialog:

curl -sS http://127.0.0.1:8765/v1/dialog | python3 -m json.tool

Pass the returned stable dialog_id when dismissing it:

curl -sS -X POST http://127.0.0.1:8765/v1/dialog/dismiss \
  -H 'Content-Type: application/json' \
  -d '{"key":"ret","dialog_id":"DIALOG_ID_FROM_GET"}'

The operation is idempotent. An absent dialog returns already_absent; a different dialog returns dialog_mismatch without input. This prevents a late Enter from leaking into ordinary gameplay after a dialog has already closed.

Plan and execute movement conservatively

The live priority planner can test a local-room navigation hypothesis without moving ego. The example coordinates assume the statically documented room-3 keycard area; replace them with the supported goal for the current room:

curl -sS -X POST http://127.0.0.1:8765/v1/movement/plan \
  -H 'Content-Type: application/json' \
  -d '{"x":120,"y":70,"goal_tolerance":1}' \
  | python3 -m json.tool

It checks the full ego baseline footprint and returns both the pixel path and compressed turn waypoints. A blocked goal is useful evidence; it is not an invitation to force movement through it.

Execute an accepted local plan only when static room logic agrees:

curl -sS -X POST http://127.0.0.1:8765/v1/movement/navigate \
  -H 'Content-Type: application/json' \
  -d '{
    "x":120,
    "y":70,
    "goal_tolerance":1,
    "max_cycles_per_segment":500,
    "invariants":[{
      "name":"remain-in-room",
      "predicate":{"path":"room","op":"eq","value":3}
    }]
  }' | python3 -m json.tool

Use explicit waypoints when the desired route is known:

curl -sS -X POST http://127.0.0.1:8765/v1/movement/waypoints \
  -H 'Content-Type: application/json' \
  -d '{
    "waypoints":[
      {"x":98,"axis_order":"x_then_y"},
      {"x":98,"y":60,"axis_order":"y_then_x"}
    ],
    "max_cycles_per_segment":500,
    "preserve_room":true
  }' | python3 -m json.tool

Waypoint completion uses direction-aware crossing rather than exact coordinate equality, so a step size greater than one cannot skip the target forever.

The planner is intentionally not a whole-game solver. It does not infer:

  • another room needed to get around a wall;
  • elevators, stairs, doors, or multi-level topology;
  • dynamic collision with moving objects;
  • a horizon or control override installed by room logic;
  • a deliberate room transition.

Represent those as explicit stages with their own preconditions and postconditions. A local plan should normally preserve the current room. A room exit should instead be a separate contract whose postcondition is the expected new room and entry boundary.

Use guarded cycle waits

For animation, timers, wandering objects, or random phases, run until a state predicate while protecting assumptions:

curl -sS -X POST http://127.0.0.1:8765/v1/cycles/run-until-guarded \
  -H 'Content-Type: application/json' \
  -d '{
    "predicate":{"path":"flags.35","op":"truthy"},
    "invariants":[{
      "name":"stay-in-room",
      "predicate":{"path":"room","op":"eq","value":1}
    }],
    "max_cycles":2000
  }' | python3 -m json.tool

Choose the bound from static timing evidence where possible, with enough margin for the observed interpreter cadence. A timeout means the hypothesis or bound needs review; it should not automatically trigger a larger blind wait. Add positive abort predicates only when static logic identifies a distinct failure state; do not invent a generic death or failure flag.

Checkpoint risky and random transitions

Create a checkpoint only at a coherent stopped state:

curl -sS -X POST http://127.0.0.1:8765/v1/checkpoints \
  -H 'Content-Type: application/json' \
  -d '{"name":"before_random_retry"}'

Restore it with:

curl -sS -X POST http://127.0.0.1:8765/v1/checkpoints/restore \
  -H 'Content-Type: application/json' \
  -d '{"name":"before_random_retry"}'

The controller restores its held/pending key model with an in-session checkpoint and clears cached transaction results. After restoring, re-read the full state before retrying. Do not assume that a host-side plan cursor or an old HTTP response still describes the VM.

Useful checkpoint locations are phase boundaries, before destructive random events, and before a new uncertain route segment. Excessive checkpoints can hide a mistaken state model; they are recovery points, not substitutes for preconditions.

Diagnose failure instead of improvising

When an experiment deviates from the plan, stop sending input. Inspect the state, transition trace, current logic, screenshot, and live priority channel:

curl -sS 'http://127.0.0.1:8765/v1/trace?since=0&limit=20000' \
  | python3 -m json.tool
curl -sS http://127.0.0.1:8765/v1/logics | python3 -m json.tool
curl -sS http://127.0.0.1:8765/v1/debug | python3 -m json.tool

Then return to the static resource that owns the expected transition. Revise the hypothesis before the next attempt.

SymptomInspect firstTypical correction
Command has no effectRoom logic, parser word groups, phase flags, ego rectangle, input modeFix the missing precondition; do not try synonyms at random
Ego repeatedly hits a wallLive priority screen, full baseline footprint, room exits and adjacent-room logicUse the actual control opening or an alternate-room/elevator route
Movement never reaches an exact pointEgo step size, target direction, crossing predicate, dynamic collisionUse guarded crossing/tolerance and inspect the obstructing object
Expected room change does not occurBoundary condition, Y/X gate, entry edge, horizon, prior room variableTarget the exact exit predicate instead of the visible screen edge
Execution stops returning to cyclesstop_reason, dialog oracle, current stack classificationHandle the modal or shared string editor semantically
A key appears stuck or repeats/v1/input/state and input traceReconcile pending releases; do not repeat the action blindly
Score is wrongScore delta, one-shot flags, alternative awards, subtraction branchesRe-audit reachability and exclusivity around every score operation
Random event keeps failingRandom controller state, inventory/money bound, checkpoint stateVerify the retry loop and restore point rather than changing unrelated actions

The most productive question is usually “Which assumption in the starting contract was false?” rather than “Which key should I try next?”

Record replay evidence

Capture a semantic stop on demand with:

curl -sS -X POST http://127.0.0.1:8765/v1/captures/cycle \
  -H 'Content-Type: application/json' -d '{}'

Each immutable bundle under CAPTURE_DIR/cycles/ contains a screenshot, cycle.json, and optional visual/priority images. The metadata contains the full state, delta from the previous recorded stop, and trace/input events. cycles.jsonl is appended only after a bundle is complete and includes the screenshot hash.

For each confirmed graph edge, preserve at least:

  • starting and final room, score, coordinates, and entry boundary;
  • relevant variable, flag, inventory, and object changes;
  • the transaction result and predicate evaluations;
  • modal or string-input transitions;
  • cycle count for waits and movement;
  • checkpoint/retry identity for random edges;
  • the current logic context when the outcome differs from the hypothesis.

Do not silently rewrite the static theory to match one surprising run. Record the failed hypothesis, identify the static reason it was wrong, update the state graph, and replay from a known checkpoint. Corrections are part of the clean-room evidence trail.

End the session cleanly

Quit QEMU through the controller:

curl -sS -X POST http://127.0.0.1:8765/v1/vm/quit \
  -H 'Content-Type: application/json' -d '{}'

Confirm that neither the controller nor QEMU remains running before rebuilding or replacing the session disk.

General lessons

The main lessons from developing and exercising the controller are:

  1. Static resources should choose the experiment; interactive play should confirm or refute it.
  2. Consult the active room and controller logics regularly. Guessing becomes especially expensive when a missing phase flag or alternate-room route is mistaken for an input problem.
  3. Navigation is a topology problem. The priority screen, ego footprint, room exits, and logic-installed overrides matter more than a visual straight line.
  4. Treat player actions as state transactions, not keystrokes. Delivered input and successful game outcome are different facts.
  5. Use state predicates for timing and explicit retry graphs for randomness. Wall-clock delays and unbounded waiting conceal errors.
  6. Distinguish interpreter input modes from their game-specific purpose. A shared editor or modal loop can appear in many contexts.
  7. Make failures safe and informative with invariants, abort predicates, cycle bounds, checkpoints, trace slices, and immutable recordings.
  8. Keep automation local and conservative. A room-level priority planner is useful; an unverified whole-game planner merely guesses faster.
  9. Break long routes into independently proved transitions. Small state deltas are easier to explain, reproduce, and encode in the graph.
  10. Preserve corrections. A failed hypothesis that narrows the model is useful evidence, while undocumented trial and error is not.

Completion checklist

Before calling a playthrough complete, verify that:

  • every selected scoring action has static evidence and explicit preconditions;
  • mutually exclusive, point-loss, death, and dead-end branches are identified;
  • the score ledger reaches the declared maximum without double counting;
  • every movement segment is supported by visual/control resources and room transition logic;
  • waits have state predicates and finite bounds;
  • random events have safe, bounded retry loops;
  • parser commands include their location, state, and input-mode conditions;
  • each dynamic action has a postcondition and relevant invariants;
  • phase checkpoints restore to states that have been re-read and verified;
  • dynamic traces and recordings agree with the machine-readable graph;
  • remaining uncertainty is marked as such rather than presented as a proven instruction.

Space Quest 1 2.2 Playthrough Analysis

This chapter is a clean-room, static-data reconstruction of a maximum-score winning route for the local games/SQ1.22 evidence set. It uses the canonical resources, WORDS.TOK, OBJECT, the locally inferred interpreter behavior, and rendered visual and priority/control channels. It does not use an external walkthrough.

The reusable source of truth is sq1_22_success_path.json. Each selected point-scoring action is a score_action node, its state conjunction is a separate precondition node, and player movement, parser input, waiting, and automatic transitions are edges. The JSON contains all 45 selected awards and validates to 202 points.

Regenerate the graph with:

python3 -B tools/render_playthrough_graph.py \
  docs/src/games/sq1_22_success_path.json \
  --output docs/src/games/sq1_22_success_path.svg

Evidence and interpretation

The game uses interpreter 2.917. Its resources contain 101 present logics, 73 pictures, 238 views, and 50 sounds. Logic 104 sets the maximum score to 202. The route was reconstructed from full logic disassembly rather than only the enclosing condition ranges emitted by tools/logic_playthrough_index.py.

Coordinates below are AGI picture coordinates. “Width in a rectangle” means that the whole player width and baseline satisfy the logic predicate; “baseline in a rectangle” means the left-baseline predicate used by the logic.

Movement is not inferred from the visible picture alone. For every selected room, the visual and priority/control channels were rendered together. The player’s complete baseline footprint must remain on allowed priority values. The white/control-0 background and control-1 barriers reject ordinary movement; colored priority regions form floors, ramps, arch throats, door notches, and cave passages. Room logic can additionally change the horizon, fixed priority, ignore-block state, or effective control geometry. Consequently an instruction to use an “opening” below means the opening visible in the priority/control channel, not a straight line across scenery.

The result is precise about static predicates but remains pending dynamic confirmation. Random numbers cannot be predicted from static resources, so the graph contains save/restore retry loops. Directional key durations inside wide walkable regions are intentionally not invented.

Exact maximum-score route

Arcada: 0 to 36

  1. After entering the name, dismiss the automatic alarm message in room 2 and enter room 1. Briefly leave through the right exit to room 2 and return through the left doorway; the first room-1 entry alone does not arm the scientist sequence. Wait for the scientist to enter and collapse, dismissing both messages. Stand by him at x 98..130, y 102..115 and type LOOK SCIENTIST. Dismiss the wound description, wait until v33=1, then acknowledge the warning and “astral body” messages. This awards 2.
  2. Put the player width inside x 74..88 with baseline y 104..108 and type LOOK SCREEN. Answer ASTRAL BODY. Wait until v50=2 and f35 is set, then at y 104..114 type GET CARTRIDGE. This awards 5.
  3. Leave by the right corridor opening (x > 134 while y < 110), cross room 2, and enter room 3 upper-left. The keycard side is disconnected: take the room-3 elevator down, cross room 3 lower-right and room 4 lower-left, take the room-4 elevator up, then leave room 4 left into room 3 upper-right. At x 117..159, y 57..79 type GET KEYCARD for 1.
  4. Return through room 4 lower-right and room 2 lower-left to the doorway into rooms 5 and 6. At room-6 x 86..134, y 125..136 type PRESS OPEN BAY DOOR; wait for f30 and the 2-point award. Never issue the closing toggle, which removes those points.
  5. Room 7 can arm the common alien encounter on entry (v67=1). Inspect that state immediately; if armed, retreat to room 6 and re-enter rather than crossing the exposed floor. On a safe entry (v67=0), use the priority channel to reach x 74..95, y 125..131 and type INSERT KEYCARD for 2. Approach the elevator until its door countdown reaches v30=1, cross the doorway to the right, then move up into room 9.
  6. At the room-9 console press the left closet button for the gadget. Put the ego’s full width inside x 40..71, y 94..110 (left x 61 works for the seven-pixel ego) and type GET GADGET for 2. Press the right closet button for the suit; at x 68..100, y 94..110 type GET SUIT for 2.
  7. At the room-9 airlock console type PRESS AIRLOCK BUTTON, plan to the open doorway on the live priority screen, then cross left into room 8 as a separate dynamic transition. Enter the room-8 console pocket x 109..125, y 139..147 and type PRESS PLATFORM BUTTON exactly once for 1. Dismiss the completed-platform message, re-plan on the final priority screen, and enter the pod at x 49..60, y 88..102 with ENTER SHIP.
  8. In room 10 type, in order, CLOSE DOOR, FASTEN BELT, PRESS POWER, and PRESS AUTONAV. AutoNav awards 2. Type PULL THROTTLE.
  9. Wait through room 8’s pod-exit cutscene and room 12. Room 12 awards 15 for escape, returns to the in-flight room 10, and eventually enters room 13. The first room-13 approach from room 10 awards no points; its 25-point branch is guarded by a later return from room 37. Continue through the room-30 landing animation into room 14.
  10. In room 14 type GET SURVIVAL KIT for 2, then OPEN SURVIVAL KIT. The global inventory logic consumes the kit and carries dehydrated water (item 12) and the Xenon Army Knife (item 19). Type UNFASTEN BELT and LEAVE POD.

Checkpoint: 36.

Kerona: 36 to 110

  1. In room 30, at x 32..102, y 108..144, type GET GLASS for 3. Use the upper/east control openings through rooms 18 and 19, go north to 16, then east to 17. Do not cut across control-0 terrain.
  2. Cross the room-17 arch throat at x 76..98, y 56..60 for 2. Wait through room 32 into room 25. At x 55..84, y 144..153 type GET ROCK, then leave left to room 26.
  3. At room-26 x 2..40, y 135..160 type PUT ROCK IN GEYSER for 4 and wait for the geometry change. Never take the rock in room 26; that branch subtracts 4.
  4. Use the opened upper-left passage into room 27. Follow the upper priority channel around the acid until the baseline reaches y < 46; this awards 3. Continue left through the control opening into room 28.
  5. At x < 70 with baseline y 125..155 type USE GLASS ON BEAM. Wait for f121 and 5 points. Return through 27 and 26 to room 29 using the newly opened passages. Accept the Keronian task and wait for the surface return.
  6. Reach room 19. At x 37..64, y 56..64 type PUSH ROCK and wait for the boulder to hit the spider, awarding 5. Go east to 20, then use its lower/east control opening into room 24.
  7. Keep at least 15 picture units from the live Orat and type THROW WATER AT ORAT. Wait for f75 and 5 points. At the remains, x 112..152, y 115..142, type GET ORAT PART for 2.
  8. Return through rooms 20, 17, 32, 25, and 26 to room 29. Type DROP ORAT PART for 10. Wait for room 31.
  9. At the console, x 75..98, y 114..127, type INSERT CARTRIDGE for 5. Wait and read the displayed shutdown code 6858. After f37 is set, type GET CARTRIDGE at x 67..98, y 114..137 for another 5.
  10. Enter and start the skimmer. Wait through rooms 78 and 33. The later room-37 return into room 13 is where the deferred 25-point award occurs.

The alternative combined spider/Orat award shares f165 with the boulder award. It is not additional score.

Checkpoint: 110.

Ulence Flats: 110 to 153

  1. First arrival in room 35 awards 25. Stay near the skimmer. Answer NO to the buyer’s first offer at v58=4. Wait for his return and answer YES only at v58=7; the final sale awards 5, money, and jetpack item 9.
  2. In room 38, at x 86..114, y 108..119, type GET BUCKAZOIDS for five currency units. This changes money, not score.
  3. Enter the bar. For each round type BUY BEER near the bartender, then DRINK BEER. Repeat exactly three times. Each beer costs 2; the third drink sets f181, reveals sector HH, and awards 5.
  4. Save before gambling. The slot controller accepts wagers of 1, 2, or 3, subtracts the wager before random reel selection, and pays 0 on losses. Winning classes pay 20/40/60, 10/20/30, 5/10/15, 3/6/9, or 1/2/3 for wagers 1/2/3. Total money saturates at 250. Restore after an unacceptable loss or repeat while solvent until v124 >= 45.
  5. In room 71, cross the flight-droid selector x 93..114, y 65..69, follow the salesman, and type BUY DROID. The full-price route costs 45 and awards 4.
  6. Save again and use the same explicit slot retry loop until v124 >= 214. Go to room 37 and type BUY SHIP for 4. This route costs 214; do not select the mutually exclusive credit/coupon branch.
  7. Climb the ladder centered at x 107..111, y 132..133, enter the ship, and LOAD DROID. Answer HH to the sector prompt and wait for flight.

Checkpoint: 153.

Deltaur: 153 to 202

  1. Keep the jetpack on. In room 45, at x 70..89, y 101..106, type TURN HANDLE, fly through the opening, and wait for the room-61 maintenance cycle. The inner transition awards 1.
  2. Reach room 57. Type OPEN TRUNK, then ENTER TRUNK; wait through the transport for 3. In room 53 exit, open the trunk, type PUT JETPACK IN TRUNK, close it, PUSH TRUNK, CLIMB TRUNK, and OPEN VENT with the knife.
  3. With f82, v249=4, and outfit state v81=1, type ENTER VENT for 2. Never use the closed-vent branch, which subtracts 2. Traverse room 52’s priority channel and descend the laundry grate. Re-entry to room 53 awards 1.
  4. Open the washing-machine door. At x 60..74, y 107..111 type ENTER WASHING MACHINE. Wait until v72=5 and v81=3; this awards 5. Type SEARCH BODY to obtain Sarien ID card item 13.
  5. In a corridor other than room 50, wait until a roaming guard is within distance 30. Type TALK TO GUARD once for 1. Continue waiting and talking through random responses until the King’s Quest II question sets f218; answer YES on the next input cycle for 5. With a guard still close, type KISS GUARD for 1.
  6. In room 51, stand at x 117..133, y 135..145. Wait until observer flag f38 is clear and type GET GRENADE for 1. Wait for the second available/unwatched state and repeat for another 1. Show/use the ID card at the service droid to receive the pulseray for 3.
  7. Enter room 50, align with its guard at a safe distance, and press F6 until projectile object 11 comes within 20 units of guard object 10. The room-50 hit awards 5 and sets f171. Do not shoot a roaming corridor guard; its 3-point award is an excluded alternative.
  8. At the body box x 70..86, y 145..164 type SEARCH BODY or GET REMOTE CONTROL for 3. Move away and type PRESS REMOTE BUTTON for 3, setting f215.
  9. Enter room 65 through x 69..87, y 115..124. The keypad is operated by walking, not typing digits. Without touching another digit, walk onto: 6 at x 89..97/y 118..131; 8 at x 77..85/y 136..148; 5 at x 77..85/y 118..131; 8 again; then ENTER at x 65..97/y 80..90. Acceptance awards 10 and starts the destruction countdown.
  10. Immediately retrace room 65 -> 50 -> 49 -> 54 -> 62 through the corridor priority bands. Room-62 first entry awards 1. At x 0..24, y 120..124 type ENTER SHIP; after f35, type PRESS LAUNCH for 3.
  11. Wait through rooms 43, 63, and terminal room 64.

Final score: 202.

Timing, randomness, and failure branches

The JSON makes three retrying states explicit:

  • the two money thresholds use save/slot/restore loops because static analysis determines the distribution and payouts but not the runtime seed;
  • guard dialogue repeats after random non-question responses until f218;
  • grenade theft waits for the observer’s phase to clear.

Other waits are deterministic state waits: the scientist sequence, bay and beam animations, landing, skimmer travel, trunk transport, laundry cycle, airlock, and shuttle launch. The self-destruct edge is a deadline: after the 6858 award, perform no optional actions.

Point-loss branches are deliberately excluded: kicking bodies, closing the bay, retracting the platform, taking the room-26 rock, entering a closed vent, and the alternative roaming-guard shot. Death/dead-end branches include acid, Orat contact, spider detection, slot bankruptcy, leaving the craft without the jetpack, armory detection, guard/guardian collision, and missing the shutdown deadline.

Score proof and remaining validation

PhaseAwardsTotalCheckpoint
Arcada2, 5, 1, 2, 2, 2, 2, 1, 2, 15, 23636
Kerona3, 2, 4, 3, 5, 5, 5, 2, 10, 5, 5, 2574110
Ulence Flats25, 5, 5, 4, 443153
Deltaur1, 3, 2, 1, 5, 1, 5, 1, 1, 1, 3, 5, 3, 3, 10, 1, 349202

The graph validator requires every score node to occur exactly once in score_route and requires its sum to equal logic 104’s maximum. Original- interpreter replay has now confirmed the route through room 30’s glass pickup at score 39. Later edges still require replay with room, score, inventory, and coordinate checkpoints. Where a corridor segment is wider than its static predicates require, replay uses the live priority channel rather than treating an invented key duration as evidence.

Space Quest 2 Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a maximum- score winning playthrough for the local SQ2 evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived from canonical logic, vocabulary, object, picture, and view resources without consulting an external walkthrough.

The result is currently a static candidate route. Logic control flow, messages, parser vocabulary, inventory mutations, and score mutations support the story and puzzle sequence. Exact movement keystrokes, moving-hazard timing, and score checkpoints still require replay under the original interpreter.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/SQ2 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/sq2/index.json

The index contains 118 readable logic resources, 2,472 parser conditions, 182 room transitions, 70 score mutations, and 40 inventory slots. Directory entry 141 points at VOL.0:0x1ffff, which does not contain a valid resource header; the index records it as unreadable and continues with valid resources.

Logic 104 assigns 250 as the declared maximum score. All positive score sites sum to 283. The excess consists of alternate parser branches and puzzle solutions rather than 283 points available on one designed path.

A direct renderer sweep produced all 74 valid picture resources. The resulting contact sheet corroborates the forest, swamp, cave, shuttle, asteroid corridor, Vohaul machinery, ruptured tube, pod-bank, and escape-pod progression described below. Picture-directory entry 147 points at VOL.0:0x2ffff, where no valid resource header exists; like unreadable logic 141, it is preserved as directory evidence rather than interpreted as game content. Pictures 140 through 146 are title or interface imagery rather than additional traversable rooms.

Story State Model

The winning route has three large phases:

  1. Work aboard Xenon Orbital Station 4, then be abducted and sent to Labion.
  2. Escape Labion and take a shuttle to Sludge Vohaul’s asteroid.
  3. Infiltrate the asteroid, stop the salesman-clone launch, escape its orbital destruction, and enter suspended animation.

The terminal winning state is reached in logic 93 after entering the escape pod’s sleep chamber. Its messages state that Vohaul and the clone plan were defeated, display the closing title, and leave the player in suspended animation. The conformance scenario must reach this state with score 250.

Candidate Route

Orbital station and abduction

  1. While working outside XOS 4, answer the wristwatch call by pressing its C control. This optional response awards one point.
  2. Step onto the airlock platform when ordered inside. Completing the transfer into the airlock awards one point.
  3. Change from the EVA suit into the station uniform at the suit rack. This awards one point and retains the order form and dialect translator.
  4. Open Roger’s locker and take both the athletic supporter and Cubix Rube puzzle. Each is a separately guarded one-point acquisition.
  5. Travel through station control to the shuttle bay and enter the shuttle. The abduction sequence awards five points and eventually deposits the captive player on Labion.

End-of-phase score: 10.

Labion forest and swamp

  1. After the hovercraft crash, press the flashing, beeping button in the wreck to switch off its locator for one point. Search the dead guard and take the keycard for three points.
  2. Find the small pink being caught in a snare and free it by releasing the rope. Its escape awards five points and establishes the later cliff-dweller welcome state.
  3. In the spore clearing, take one live spore for four points. The two four- point sites are alternate near/far acquisition branches guarded by the same state, not cumulative awards.
  4. Put the order form into the clearing’s mailbox for two points. Return after its delivery cycle and take the whistle from the tray for another two.
  5. Survive the root-monster area and take the red berries for two separate four-point awards: one for completing the encounter and one for acquiring the berries. Rub the berries over Roger’s body for three points. Their odor is used to avoid detection by the searching hovercraft; completing that evasion awards five points.
  6. At the swamp, take a deep breath before diving. The first valid breath/dive state awards two points. Reach the underwater grotto for two points, take another breath there for two, and take the glowing gem for three. This is the higher-valued gem acquisition; do not later take the one-point fallback gem in the cliff-dweller cave.
  7. Allow the forest hunter sequence to capture Roger and place him in the cage. Throw the live spore at the hunter from the proper position to paralyze him for five points. Take the cage key for two, unlock and open the cage, then take the rope for two.
  8. Return to the fissure. Climb the dead snag for three points, then tie the rope to either the stump or the log for two. The two anchor awards are alternatives; untying the rope removes two points.
  9. Descend the fissure and swing on the rope. Initiating the useful swing awards two points. Release at the correct part of the arc to reach the ledge, awarding five points.
  10. The cliff dwellers recognize the earlier rescue. Follow them through their cave and, when their leader offers to reveal the exit, say the word. This awards three points and opens the descending route.
  11. In the dark tunnels, hold the glowing gem in Roger’s mouth so he can crawl while retaining light. Successfully reaching the luminous underground water awards 20 points.
  12. Follow the river and survive the forced whirlpool sequence. Reaching the waterfall pool outside awards five points.
  13. In the terror-beast clearing, blow the mail-order whistle to summon the beast for five points. Give it the Cubix Rube for ten; while it is occupied by the puzzle, take the small stone from the newly created debris for two. The two stone pickup sites describe the same inventory acquisition.
  14. At the landing platform, use the athletic supporter as a sling and fire the stone at the guard while outside his direct line of sight. The successful hit awards 20 points. The two ten-point bush-distraction branches are lower-valued alternatives and consume the same stone.
  15. Search the unconscious guard and take his keycard for one point. Insert it into the elevator slot for five.
  16. In the shuttle, power the ascent system, select vertical attitude control, activate the ascent thrusters, and use the throttle as indicated by the console. Achieving the required altitude and departure sequence awards 20 points. Vohaul then takes remote control and brings the shuttle to his asteroid.

Labion contributes 156 points. Cumulative score: 166.

Vohaul’s asteroid

  1. Explore the asteroid’s elevator levels before committing to the final security route. Collect the following janitorial and utility items:

    • Plunger from the dark closet in room 62: one point.
    • Glass cutter from the dark closet in room 72: one point.
    • Toilet paper from the human restroom in room 76: one point.
    • Wastebasket and lighter from the holding-level closet in room 83: one point each. Inspecting or taking the dirty overalls exposes the lighter.
  2. Put the toilet paper into the wastebasket for one point. Preserve the resulting basket-with-paper for the Wallbot corridor.

  3. Enter the moving-floor acid trap in room 66. Completing its first barrier sequence awards ten points. At the smooth barrier, attach the plunger and hang from it while the floor retracts; the successful plunger state awards another ten. Do not release early.

  4. In the Wallbot corridor, put down the loaded wastebasket in the accepted position for one point and ignite it with the lighter. The heat triggers the sprinklers; the water extinguishes the fire and shorts the Wallbots. The successful sequence awards ten points.

  5. Enter Vohaul’s control chamber. The miniaturization beam traps Roger in a glass jar on the console. Use the glass cutter to cut an exit and complete the miniature escape sequence for five points.

  6. Enter the console vent, reach Vohaul’s life-support machinery, and press the emergency shutoff. Disconnecting his life support awards ten points. Return to the console and use the beam sequence to regain normal size.

  7. Examine Vohaul’s hand to learn the written code SHSR. Use the control console and enter SHSR to abort the salesman-clone launch for ten points. This action starts the asteroid’s terminal orbital-decay phase.

  8. Open the wall receptacle in the clear outer passage and take the oxygen mask for two points. Wear it before traversing any ruptured, depressurized tube.

  9. Return to the escape-pod bank while avoiding the Marrow-Matic and other moving hazards. Enter the available pod for ten points and launch before the asteroid is destroyed.

  10. After launch, the pod reports rapidly dwindling oxygen. Open the sleep chamber and enter it. Sealing the chamber awards the final ten points and enters terminal logic 93.

The asteroid contributes 84 points. Final score: 250.

Score Ledger

PhaseSelected awardsPhase totalCumulative
XOS 4 and abduction1, 1, 1, 1, 1, 51010
Labion3, 1, 3, 5, 2, 3, 2, 5, 2, 2, 4, 2, 2, 4, 4, 2, 5, 2, 2, 2, 3, 3, 20, 5, 2, 5, 10, 1, 20, 5, 20, 5156166
Vohaul’s asteroid1, 10, 1, 10, 10, 1, 1, 1, 1, 10, 5, 10, 2, 10, 10, 184250

The complete positive-site sum is 283. The designed route removes 33 points:

Excluded alternativesExcess removed
Duplicate airlock supporter/puzzle parser branches4
Duplicate spore acquisition4
Second rope anchor2
One-point fallback glowing gem1
Duplicate terror-beast stone acquisition2
Two ten-point guard distraction alternatives instead of the 20-point hit20
Total33

Thus 283 - 33 = 250. No negative score action is needed. Avoid changing back into the EVA suit, untying the rope after scoring an anchor, and trying to retrieve the suspended wastebasket from an invalid position.

Deaths and Dead Ends

The logic resources expose many useful negative conformance cases:

  • Step off the orbital-station structure and drift into space.
  • Leave the airlock without the EVA suit.
  • Enter concealed forest spike pits or touch sticky/insect-covered trees.
  • Approach the root monster incorrectly or fail to escape its digestive path.
  • Enter deep swamp water without taking a breath or remain submerged too long.
  • Let the hunter recover before escaping the cage.
  • Fall from the fissure, mistime the rope release, or enter dark caves without the gem.
  • Meet the cliff dwellers without having rescued their companion.
  • Enter the cave-squid tunnel without enough light or take the fatal river branch.
  • Approach the terror beast without distracting it.
  • Let the platform guard see Roger while attacking or following him.
  • Fall from asteroid walkways or enter vacuum without the oxygen mask.
  • Release the plunger during the acid-floor trap.
  • Approach active Wallbots, Marrow-Matics, floor waxers, or caged creatures.
  • Fail to stop the clone launch or remain on the asteroid during orbital decay.
  • Set the miniaturization beam incorrectly, remain trapped without air, or fail to disable Vohaul’s life support.
  • Launch successfully but fail to enter suspended animation before the pod’s oxygen is exhausted.

Potential progression dead ends include discarding the puzzle, supporter, whistle, gem, keycards, plunger, glass cutter, lighter, paper, or wastebasket before its final use. A conformance suite should distinguish these valid but unwinnable states from immediate death states.

Next Validation Work

  1. Convert each milestone into exact movement and parser input under QEMU.
  2. Record room, inventory, and score checkpoints at 10, 166, and 250.
  3. Confirm moving-hazard timing, patrol evasion, rope release, acid-floor survival, and Wallbot placement under the original interpreter.
  4. Package the validated input stream, screenshots, and terminal assertions as a repeatable implementation-conformance playthrough.

King’s Quest 1 Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a maximum- score winning playthrough for the local KQ1 evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived from canonical logic, vocabulary, object, picture, and view resources without consulting an external walkthrough.

The result is currently a static candidate route. Logic control flow, messages, parser vocabulary, inventory mutations, score mutations, and room transitions support the complete puzzle sequence. Exact movement coordinates, random-character timing, and the final input stream still require replay under the original interpreter.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/KQ1 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/kq1/index.json

The index contains 90 readable logic resources, 1,478 parser conditions, 277 room transitions, 180 score mutations, and 27 inventory slots. No logic resource failed to decode. Logic 0 assigns 158 as the declared maximum score. The 269-point raw positive-site sum includes repeated room variants, reversible acquisitions, lower-valued alternatives, and positive halves of net score exchanges; it is not a reachable route total.

A direct renderer sweep produced all 82 present picture resources without an error. The resulting contact sheet corroborates the open Daventry map, well and dragon caves, condor flight, underground leprechaun complex, beanstalk and cloud region, giant territory, castle return, and ending imagery. Pictures 83 and 84 are title or transition imagery rather than traversable rooms.

Winning State

King Edward sends Graham to recover three objects:

  1. A magic mirror that tells the future.
  2. A magic shield that protects its bearer.
  3. A magic chest that continually supplies gold coins.

Logic 53 recognizes the completed quest when all three treasure states are present. After Graham returns to the throne room and bows, King Edward rises, commends him, dies, and grants him the kingdom. The terminal sequence names Graham as King of Daventry and displays the closing credits. The conformance scenario must reach that sequence with score 158.

Maximum-Score Principles

Several parser choices solve the same immediate problem but do not preserve the maximum score:

  • Show, rather than give, the carrot to the goat. Showing it awards five points while retaining the two-point carrot. Giving or feeding it first subtracts those two points.
  • Use water on the dragon. Consuming the two-point water and receiving the seven-point solution award gives a net five, compared with the lower-valued dagger solution.
  • Give food rather than treasure to gatekeepers. Cheese costs two points but earns four from the rat; surrendering a six-point treasure only removes it.
  • Drive the leprechauns away with fiddle music and retain the four-leaf clover as protection.
  • Wait for the giant to fall asleep. This earns seven points and leaves the eight-point chest available. Killing him with the sling gives a lower net result.
  • Answer the gnome correctly on the first guess. Later correct guesses award fewer points, while failure produces a gold key and a lower-valued alternate route instead of the beans.

The parser’s first-guess word for the gnome is ifnkovhgroghprm. The witch’s note says that it can be wise to think backwards. Mechanically mirroring each alphabet position (a with z, b with y, and so on) transforms that local vocabulary token into rumplestiltskin.

Candidate Route

The surface world is open enough that many collection steps can be reordered. The sequence below keeps dependencies explicit and avoids carrying the goat into rooms where it wanders away or causes a score penalty.

First audience and surface preparations

  1. Cross the castle bridge, open the door, enter the throne room, stand a few paces in front of King Edward, and bow. Opening the door awards one point; the first respectful bow awards three. Speak to the King to receive the three-treasure quest.
  2. Move the large rock in the birch clearing for two points. Search the exposed hole and take the dagger for five. Stand uphill or aside so the moving rock does not roll over Graham.
  3. Look inside the decaying stump for one point, take the canvas pouch for three, and open it for another three. It becomes the pouch of diamonds.
  4. Take a carrot for two points. At the goat pen, open the gate and show the carrot from close range. This awards five points without consuming it and causes the goat to follow Graham.
  5. Lead the goat to one of the guarded bridges. Step aside when the troll appears. The goat butts the troll away for four points and then leaves the active route. Do not pay the troll with treasure.
  6. Speak to the nearby elf from close range. The successful encounter awards three points and leaves Graham with the magic ring. The maximum route does not need to activate the ring; dropping or losing it can subtract points.
  7. Take the four-leaf clover for two points. It later suppresses hostile leprechaun behavior.
  8. Take one walnut from the walnut tree for three points, then open it to reveal that its interior is gold for another three.
  9. Take the ceramic bowl for three points. Read its underside for one, say fill to obtain stew for two, and give the full bowl to the starving woodcutter family. Giving the three-point bowl and receiving the six-point generosity award is a net three. Take their offered fiddle for three.
  10. Take the smooth pebbles at the river delta for one point. They are not used against the giant on the maximum route, but their acquisition still scores.

Witch, gnome, and condor

  1. At the gingerbread house, eat part of the exterior for two points. Enter only when the witch is away or position Graham so she cannot catch him.
  2. When the witch turns to tend the oven, push her into it from close range for seven points. Open the cabinet for two, take the cheese for two, and take the note for two. Read the note for one additional point.
  3. Visit the gnome and accept the three-guess challenge. Enter ifnkovhgroghprm as the first guess for five points, then take the magic beans for four.
  4. Plant the beans in one of the fertile-soil rooms for two points. The resulting beanstalk remains available for the chest route.
  5. In the condor clearing, time a jump so Graham intersects the low-flying bird. Being carried away awards three points. The condor flight passes through room 80 and deposits Graham in room 48.
  6. Before entering the underground route, visit the neighboring far riverbank and take the mushroom for one point. Return to room 48 and use its opening to fall into room 73.

Surface preparation, including the first audience, contributes 83 points: four for the audience and 79 for the surface puzzles and acquisitions.

Magic mirror

  1. At the well, use its bucket and rope to descend. Cut the bucket rope with the dagger from the valid in-water position. The route awards two points for acquiring the bucket and two for its first fill, either during this action or at the next valid water source.
  2. Entering the well shaft from room 12 awards one point. Dive from the valid swimming state for two and reach the bottom chamber.
  3. Enter the side opening into the dragon cave for one point. From the safe side of the cave, throw the bucket’s water at the dragon. Consuming the water subtracts two points and the nonviolent solution awards seven, for a net five.
  4. Take the magic mirror for eight points. Return through the opening to the bottom chamber once for two points, then use the passage exposed when the dragon moved the boulder to leave the cave system.

The mirror chain contributes 23 points. Cumulative score: 106.

Magic shield

  1. Follow rooms 73 and 74 to the rat’s door in room 75. Give the cheese from close range. Losing its two acquisition points and receiving four for the intended solution gives a net two; the rat opens the way.
  2. Enter the leprechaun antechamber with the four-leaf clover in inventory. Play the fiddle for three points. The leprechauns dance away and their King follows them out.
  3. In the throne room, take the magic shield for eight points and the sceptre for six.
  4. Continue to room 78. Eat the mushroom there: its one-point consumption and three-point room-specific award give a net two. Pass through the tiny exit to room 36 for one additional point and return to normal size after the mushroom’s timer expires.

Including the earlier one-point mushroom acquisition, the shield chain contributes 23 points. Cumulative score: 129.

Magic chest

  1. Return to the planted beanstalk and climb it carefully into the clouds. Reaching the bird-nest region for the first time awards two points.
  2. Search the hollow cloud-tree and take the leather sling for two points. Take the golden egg from the nest for six.
  3. Approach the giant’s region without attacking. Evade him until his patience state expires and he falls asleep; this awards seven points.
  4. Take the magic chest from the sleeping giant for eight points. Do not use the sling and pebble against him: that consumes a point and replaces the seven-point sleep outcome with a three-point kill outcome.
  5. Return across the cloud paths and descend the beanstalk without falling.

The chest chain contributes 25 points. Cumulative score: 154.

Return to King Edward

  1. Return to the castle after all three treasure states are complete. Opening its door on the completed-quest return awards the second one-point door event.
  2. Stand in the audience rectangle and bow again. The completion-state bow awards three points and starts the terminal commendation sequence.

Final score: 158.

Score Ledger

PhaseSelected net awardsPhase totalCumulative
First audienceDoor 1; bow 344
Surface preparationsRock/dagger 7; pouch 7; carrot/goat/troll 11; elf 3; clover 2; walnut 6; bowl/family/fiddle 12; pebbles 1; gnome/beans 11; witch 16; condor 37983
Magic mirrorBucket/water 4; well entry 1; dive 2; cave entry 1; water solution net 5; mirror 8; cave return 223106
Magic shieldMushroom acquisition/consumption/exit 4; rat solution net 2; fiddle 3; shield 8; sceptre 623129
Magic chestCloud entry 2; sling 2; egg 6; sleeping giant 7; chest 825154
Final audienceDoor 1; bow 34158

The route contains four deliberate negative score mutations, each paired with a larger positive award:

Consumed stateDeductionSolution awardNet
Full bowl given to the woodcutters-3+6+3
Water thrown at the dragon-2+7+5
Cheese given to the rat-2+4+2
Mushroom eaten in room 78-1+3+2

Showing the carrot incurs no deduction. The give/feed carrot parser branches would add an unnecessary two-point loss and make 158 unreachable on that run.

Deaths and Maximum-Score Dead Ends

The logic resources expose useful negative conformance cases:

  • Fall into the castle moat or enter swift/deep water without the correct swimming state.
  • Stand downhill from the moved rock and be crushed.
  • Let wolves, the sorcerer, ogres, dwarfs, the witch, the rat, trolls, or leprechauns catch Graham without the relevant protection or solution.
  • Enter the witch’s house at the wrong time and be caged or cooked.
  • Fall while climbing the well rope, beanstalk, cloud paths, or tree.
  • Remain underwater too long at the well bottom.
  • Approach the dragon through its flame path or remain in range after the shield melts.
  • Miss the condor jump or fall from its transport sequence.
  • Eat the mushroom in a place where Graham becomes trapped when normal size returns.
  • Approach the awake giant or walk off the clouds.

Valid but lower-scoring branches include feeding the carrot, giving treasure to the troll or rat, killing the dragon with the dagger, killing the giant with the sling, guessing the gnome’s name late, failing the gnome puzzle and taking the key route, or losing inventory to the roaming dwarf. Dropping the quest treasures can also create a terminally unwinnable state. These should be tested separately from immediate death behavior.

Next Validation Work

  1. Convert each milestone into exact movement, parser input, and jump timing under QEMU.
  2. Record room, inventory, treasure-count, and score checkpoints at 4, 83, 106, 129, 154, and 158.
  3. Confirm timing-dependent condor, witch, dwarf, giant, cloud, well, and beanstalk behavior under the original interpreter.
  4. Package the validated input stream, screenshots, and terminal assertions as a repeatable implementation-conformance playthrough.

King’s Quest 2 Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a maximum- score winning playthrough for the local KQ2 evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived from canonical logic, vocabulary, object, picture, and view resources without consulting an external walkthrough.

The result is currently a static candidate route. The resources establish a complete route to the wedding, an exact route to the declared maximum of 185, and a possible repeatable bridge award that could raise a winning score to 191. Exact movement, random-character avoidance, bridge overscoring, and the terminal input stream still require replay under the original interpreter.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/KQ2 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/kq2/index.json

The index contains 133 readable logic resources, 1,406 parser conditions, 290 room transitions, 82 score mutations, and 85 inventory slots. Slots 50 through 84 contain 35 named objects; the earlier slots are unnamed placeholders. No logic resource failed to decode. Logic 180 assigns 185 as the declared maximum score.

The 222-point raw positive-site sum is not a reachable total. It includes alternate trident, snake, candle, Dracula-key, lion, and recovered-treasure branches. It also counts the ring and cloak award twice even though a shared one-time state permits only one of those sites to add points. Conversely, the raw sum cannot assign values to two variable awards: three points reserved for the Dracula resolution and five points awarded on first reaching Valanice.

A direct renderer sweep produced all 108 present picture resources without an error. The contact sheet corroborates the Kolyma forest and coast, magic door and bridge, Neptune’s underwater kingdom, mountain cave, Dracula’s castle, dwarf and Hagatha interiors, iridescent final sea, island, and quartz tower.

Winning State

King Graham sees Valanice imprisoned in a quartz tower through the magic mirror. A magic door in Kolyma requires three successive gold keys. Its inscriptions direct Graham toward an underwater quest, a high-altitude quest, and a final quest requiring a stout heart.

After opening all three doors, Graham reaches an iridescent sea, rescues a golden fish, reaches the quartz tower, pacifies its lion, and finds Valanice. Using the amulet’s home command in Valanice’s room selects logic 107 rather than the bride-less ending. A monk declares Graham and Valanice married and the game congratulates Graham for winning her hand. The declared-max conformance scenario must reach this terminal sequence with score 185.

Maximum-Score Principles

  • Return Little Red Riding Hood’s full basket. Eating its contents subtracts two points and prevents the four-point exchange for the bouquet.
  • Give the bouquet to the mermaid. Giving ordinary treasure removes seven points; giving Neptune’s trident removes three and destroys the sea route.
  • Return the trident to Neptune for four points. Waving it personally opens the same clam for only two points, and the shared opened-clam state prevents both awards from coexisting on a surviving route.
  • Transform the snake with the bridle for five points, then speak to the winged horse for two. Killing the snake with the sword awards only two.
  • Cover Hagatha’s nightingale cage with the bottle cloth before taking it. Returning the covered bird earns the lamp without surrendering two seven- point treasures. Opening the cage loses two points and the bird.
  • Wear both the ruby ring and black cloak. The second item completing the disguise awards three points; a shared one-time flag means the two apparent three-point sites are not cumulative.
  • Pray respectfully, identify Graham by name, and wear the monk’s cross. The cross protects against awake Dracula, but the maximum route still kills the sleeping vampire with the stake and mallet.
  • Feed the smoked ham to the lion for four points. Killing it gives two points and later subtracts three, for a net loss of one.
  • Rescue the live golden fish. Killing it, letting it suffocate, or exploiting its corpse loses the route to the tower.
  • Avoid the roaming dwarf. Each stolen seven-point treasure can be recovered from his trunk for seven points, making the detour score-neutral rather than an additional award.

Candidate Route

Many Kolyma collections can be reordered. The sequence below keeps the three gold keys separate because they occupy the same inventory slot and each must be consumed at the magic door before the next is acquired.

Kolyma preparations

  1. At Grandma’s cottage, open the mailbox and take the basket of goodies. Do not eat from it. Find Little Red Riding Hood and return the basket to receive her bouquet of wildflowers.
  2. Search the opening at the base of the pond boulders and take the brooch. Move the clamshell on the beach and take the bracelet revealed beneath it.
  3. Collect the stake leaning against a tree, the rusty trident in the grass, the necklace inside the hollow log, and the mallet in the pine-tree hole.
  4. Enter the monastery chapel, kneel at the altar, and pray. When the monk asks Graham’s name, answer Graham. Accept and wear his silver cross.
  5. Enter the dwarf’s underground house when it can be searched safely. Take the pot of chicken soup and open the trunk to take the earrings. Avoid the dwarf’s roaming theft sequence.
  6. Give the soup to sick Grandma, then look under her bed. Take the ruby ring and black cloak and wear both. Only the second disguise item awards the shared three-point completion bonus.
  7. Cross the rickety bridge once on the declared-max route. Complete the traversal rather than merely entering the bridge room.

These preparations contribute 61 points.

First gold key: make a splash

  1. Give the bouquet to the mermaid and ride the magic seahorse she summons. The seahorse carries Graham underwater and supplies the breathing state needed to survive there.
  2. Approach Neptune and give him his rusty trident. He gives Graham a bottle and uses the trident to open the giant clam. Do not wave the trident first.
  3. Take the first gold key from the clam. Open the bottle and pull out the large cloth.
  4. Return to the magic door and unlock its first stage. The key disappears and reveals the inscription for the high-altitude quest.

The sea quest contributes 15 points and the first door contributes 7. Cumulative score: 83.

Nightingale, lamp, and second gold key

  1. Enter Hagatha’s cave without alerting her. Drape the bottle cloth over the nightingale’s cage before taking the covered cage. Leave without opening or uncovering it.
  2. Give the covered nightingale to the antique-shop owner. She awards the old oil lamp without taking any treasure.
  3. Rub or pat the lamp three times. Its genie successively leaves a magic carpet, sword, and leather bridle; each successful appearance awards two points. Ride the carpet once for four points.
  4. Reach the snake guarding the high route. Throw the bridle onto the live snake rather than attacking it. Speak to the transformed winged horse and take the magic sugar cube it offers.
  5. Eat the sugar cube before passing the poisonous brambles. Reach the damp cave and take the second gold key from the rock.
  6. Return to the magic door and unlock its second stage.

The nightingale and lamp chain contributes 25 points; the winged-horse key quest contributes 13; and the second door contributes 7. Cumulative score: 128.

Third gold key: stout heart

  1. Use the ring-and-cloak disguise to secure passage with the ghoul across the poisoned lake without paying a seven-point treasure. Keep the silver cross equipped for Dracula encounters.
  2. In Dracula’s castle, open the dresser and take the candle. Light it from one of the two reachable wall torches; the shared lit-candle state means the two one-point sites are alternatives. Take the smoked ham from the dining room.
  3. Find Dracula asleep in his coffin. Put the stake on his chest and pound it with the mallet. The resolution awards four explicit points plus the remaining three-point Dracula bonus.
  4. Take both the gold and silver keys left in or revealed by the coffin. The separate one-key and both-key parser branches are alternate ways to receive the same five- and two-point awards.
  5. Use the silver key on the tower chest, open it, and take the tiara. Preserve the smoked ham for the final-world lion.
  6. Return to the magic door and use the third gold key. The door reveals the iridescent world beyond it.

The castle quest contributes 27 points and the third door contributes 7. Cumulative score: 162.

Golden fish, Valanice, and wedding

  1. On the blue beach, take the fishing net. Cast it from the accepted shoreline position to catch the golden fish, then take the gasping fish.
  2. Throw the live fish back into the sea. Accept its offered ride and approach it when it calls. The beach sequence contains five guarded awards totaling eight points: net 1, catch 2, rescue 3, boarding 1, and completed ride 1.
  3. On the strange island, take the bronze amulet. Traverse the quartz tower and give the smoked ham to the hungry lion. It eats and falls asleep, allowing Graham through the guarded door.
  4. Enter Valanice’s room. First arrival consumes a reserved five-point bonus. With Valanice rescued and the amulet still carried, say home. This awards three points and enters the wedding ending in logic 107.

The final world contributes 23 points. Final declared score: 185.

Score Ledger

PhaseSelected awardsPhase totalCumulative
Kolyma preparationsMailbox/basket 3; return basket 4; brooch 8; bracelet 7; stake 2; trident 3; necklace 7; mallet 2; monastery/cross 6; soup/earrings 9; Grandma/disguise 9; bridge 16161
First keyMermaid/seahorse 4; Neptune/trident 4; key 5; bottle cloth 2; first door 72283
Lamp and second keyCovered bird/lamp/genie/carpet 25; bridle/horse/cube/key 13; second door 745128
Third keyCandle/ham 5; Dracula resolution 7; keys 7; chest/tiara 8; third door 734162
Final worldFish sequence 8; amulet 3; first Valanice arrival 5; ham solution 4; home 323185

The exact equation can also be recovered from all immediate positive sites. Starting from 222, exclude mutually exclusive sites worth 45 points: personal trident wave 2, sworded snake 2, duplicate candle site 1, duplicate Dracula-key branches 7, four score-neutral treasure recoveries 28, sworded lion 2, and the second apparent disguise-completion site 3. The resulting 177 immediate points plus variable bonuses of 3 and 5 equal 185.

Bridge Overscore Hypothesis

Logic 48 does not guard its one-point bridge award with a permanent one-time flag. Completing a traversal increments a counter, awards one point, and clears the two side markers so another traversal can score. The fatal check uses counter > 6 before initiating the collapse, which appears to allow seven successful awards and make an eighth attempt fatal.

The declared route above uses one bridge award. If the other six can be farmed and Graham can leave the bridge on the useful side, the terminal score would be 191 while the displayed maximum remains 185. Static control flow supports this hypothesis, but exact collision rectangles, side selection, and the ability to finish after the seventh traversal require original-interpreter validation. Until then, 185 is the exact declared-max route and 191 is a candidate actual maximum, not a promoted result.

Deaths and Maximum-Score Dead Ends

  • Eat the basket contents or return its empty remains to Little Red Riding Hood.
  • Frighten the mermaid, pay her with treasure, or give her Neptune’s trident.
  • Dive without the magic seahorse or anger Neptune after keeping his trident.
  • Attempt an eighth bridge crossing or fall from a cliff, staircase, carpet, or tower.
  • Kill the snake instead of transforming it, or enter the brambles without the sugar-cube protection.
  • Alert Hagatha by taking the uncovered singing bird, uncover it while still in the cave, or let her catch Graham.
  • Buy the lamp with two treasures, open the birdcage, or lose scored treasure to the roaming dwarf.
  • Enter the poisoned lake without safe transport; paying the ghoul with treasure also loses seven points.
  • Traverse dark castle rooms without a lit candle, wake Dracula with the sword, approach him unprotected, or omit the stake or mallet.
  • Kill the lion instead of feeding it, approach it too closely, or consume the ham earlier.
  • Let the golden fish suffocate, kill it, throw back its corpse, or enter the turbulent sea without the rescued-fish ride state.
  • Reach the ending without Valanice or consume the amulet before saying home in her room.

Next Validation Work

  1. Replay the bridge under the original interpreter and determine whether a winning 191-point terminal state is reachable.
  2. Convert the 185 route, and the 191 route if confirmed, into exact movement, parser input, and timing streams.
  3. Record room, inventory, key-stage, score, and terminal checkpoints at 61, 83, 128, 162, and 185.
  4. Confirm random dwarf, Hagatha, wolf, enchanter, Dracula, carpet, stair, and fish timing under the original interpreter.
  5. Package the validated stream, screenshots, and ending assertions as a repeatable implementation-conformance playthrough.

King’s Quest 3 Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a maximum- score winning playthrough for the local KQ3 evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived from canonical logic, vocabulary, object, picture, view, and locally supplied spell-text resources without consulting an external walkthrough.

The result is currently a static candidate route. Logic control flow, messages, parser vocabulary, inventory transformations, score mutations, and room transitions support a complete 210-point route. Exact movement, wizard and pirate timing, mountain navigation, and the final input stream still require replay under the original interpreter.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/KQ3 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/kq3/index.json

The index contains 125 readable logic resources, 2,994 parser conditions, 277 room transitions, 67 score mutations, and 55 inventory slots. No logic failed to decode. Logic 101 assigns 210 as the declared maximum score.

All positive score sites total 215. Logic 14 contains two four-point position branches for completing the same spider-removal sequence, guarded by one completion flag. Logic 39 likewise accepts pet dog and get dog hair as alternate one-point parser routes to the same inventory state. Removing the duplicate four- and one-point sites gives exactly 210. No negative score action is present, so maximum play depends on collecting every intended award and avoiding mutually exclusive duplicate counting.

The seven successful spell preparations are represented by separate one-time flags and award ten points each. Their resulting inventory objects occupy synthetic locations 121 through 127 before being collected or activated. The local SPELLS.TXT supplies the exact recipes and empowering verses used below.

A direct renderer sweep produced all 97 present pictures without error. The contact sheet corroborates Manannan’s mountaintop house and laboratory, Llewdor’s forest, desert, village and bandit tree, the pirate ship, landing beach, snowy mountain labyrinth, ruined Daventry, cloud-top dragon ground, and restored castle ending.

Story and Winning State

Seventeen-year-old Gwydion lives as the wizard Manannan’s slave. He must use the wizard’s absences and sleep periods to discover a hidden laboratory, collect ingredients, and prepare magic without being caught. A cat cookie hidden in porridge transforms Manannan permanently and frees Gwydion.

The Oracle then establishes that Gwydion is Prince Alexander of Daventry and that his sister, Princess Rosella, is about to be sacrificed to a three-headed dragon. Alexander buys passage across the ocean, escapes the pirates, crosses the mountains, kills the dragon with a magical storm, and frees Rosella.

Entering the castle hallway with Rosella awards the final four points and leads to logic 74. King Graham and Queen Valanice welcome both children, the magic mirror clears, Graham throws his adventurer’s hat toward them, and the game displays its completion congratulations. The conformance route must enter this terminal sequence with score 210.

Timed House State

Manannan cycles among four broad states: awake at home, away on a journey, asleep, and returned hungry. He also assigns one of five chores: clean the kitchen, empty the chamber pot, prepare food, dust the study, or feed the chickens. Failure, forbidden-room discovery, visible ingredients, a missing wand, or detected spellcraft can cause punishment, confiscation, or death.

The static route therefore follows these invariants until Manannan is transformed:

  1. Complete each assigned chore before its deadline.
  2. Enter the bedroom, study, or laboratory only while Manannan is away or safely asleep.
  3. Return the brass key to the closet top after opening the wand cabinet.
  4. Replace the wand in the cabinet, relock it, close the trapdoor, and replace the concealing book before Manannan returns.
  5. Hide all suspicious carried objects under Gwydion’s bed before an inspection, then retrieve them during the next safe interval. The first successful bulk hiding awards four points.
  6. Keep ordinary bread, fruit, or mutton available for early hunger cycles. Do not offer the poisoned porridge until the cat cookie is ready.

These rules define the safe state transitions; exact timer values and the fastest deterministic schedule remain replay work.

Candidate Route

House tools and discoveries

  1. Obey the opening order to sweep the kitchen. During safe intervals, climb to the telescope tower and take the dead fly’s wings for one point.
  2. Search Manannan’s bedroom thoroughly. Search behind the closet contents for the magic map, feel along the closet top for the brass key, and search the vanity drawers for the hand mirror and rose-petal essence. These four discoveries award 7, 3, 1, and 1 points.
  3. In the kitchen take the wooden spoon, knife, clay bowl, bread, fruit, and mutton for one point each. Take the tin cup from the dining-room table for another point.
  4. In the study, move the large book and pull the hidden lever. This opens the trapdoor and awards five points. Use the brass key on the cabinet and take the wand only while actively performing spell work.
  5. Descend to the laboratory for its four-point first discovery. Take the six required shelf ingredients, each worth one point: powdered fish bone, saffron, nightshade juice, mandrake-root powder, toadstool powder, and toad spittle.
  6. Hide the accumulated contraband beneath Gwydion’s bed before Manannan can inspect him. Preserve the four-point first-hide award while following the replacement rules above.

House tools and discoveries contribute 39 points.

Llewdor ingredients and side quests

The following collections can be interleaved with safe house visits and spell preparation:

  1. Catch a chicken and pluck one small feather. Pluck hair from Manannan’s cat. Each acquisition awards one point.
  2. In the desert take the dried snakeskin and the unusual cactus for one point each. Defeat Medusa by averting Alexander’s eyes and pointing Manannan’s hand mirror at her; the reflection solution awards five points.
  3. At the large oak, collect the dried acorns for one point and find the hidden rope to lower the ladder for three. Reach the bandit treehouse for two points. Later, while transformed into a fly, enter the root hole for another five-point discovery.
  4. Listen to the bandits’ tavern conversation until the treehouse clue completes for three points. Enter while the resident bandit sleeps and take the coin purse for four.
  5. Pet the storekeeper’s dog or explicitly take its fur. Either parser route awards the same single point. Buy salt, lard, fish oil, and an empty pouch for one point each. Make all four purchases before surrendering the purse for ship passage.
  6. Enter the Three Bears’ house while it is empty. Take the just right porridge for two points and the silver thimble for one. Outside, fill the thimble with flower dew for one.
  7. Take dried mistletoe near the coast, wet mud from the stream with the spoon, and ocean water with the tin cup. Each is worth one point.
  8. Visit the Oracle and receive the amber stone and Alexander’s identity for three points. Find and take the eagle feather for two.
  9. Complete the eagle transformation and use it to remove the giant spider guarding the cave. The two position branches converge on one four-point completion state; only one award is reachable.

These Llewdor collections and side quests contribute 48 points. Cumulative score before spell completion: 87.

The Seven Spells

Each recipe must be performed in the laboratory with the wand temporarily removed from its locked cabinet. Steps are ordered, and the empowering verse must be recited exactly. A wrong ingredient, action, sequence, or verse enters a failure branch instead of awarding ten points.

Understand creatures

  1. Put the chicken feather, dog fur, and snakeskin into the bowl.
  2. Add a spoonful of powdered fish bone and the thimbleful of dew.
  3. Mix with the hands, separate the dough into two pieces, and put them in Alexander’s ears.
  4. Recite:
Feather of fowl and bone of fish,
Molded together in this dish,
Give me wisdom to understand
Creatures of air, sea and land
  1. Wave the wand.

The dough remains in the ears and permits understanding animal speech. It is useful for optional identity clues and for the pirate-hold treasure directions.

Fly like an eagle or fly

  1. Put saffron into the rose-petal essence.
  2. Recite:
Oh winged spirits, set me free
Of earthly bindings, just like thee.
In this essence, behold the might
To grant the precious gift of flight.
  1. Wave the wand.

Dip the eagle feather or fly wings into the transformed essence to select a temporary form. Eagle begone! Myself, return! and Fly, begone! Myself, return! restore Alexander early. Use the fly form at the oak root hole and the eagle form against the giant spider.

Random teleportation

  1. Grind the salt and mistletoe separately in the mortar.
  2. Rub the Oracle’s amber stone in the mixture, then kiss it.
  3. Recite:
With this kiss, I thee impart,
Power most dear to my heart.
Take me now from this place hither,
To another place far thither.
  1. Wave the wand.

Rubbing the finished stone teleports Alexander to a random supported location. It can shorten timed returns but is not a guarantee of safety.

Deep sleep

  1. Grind the acorns, put their powder in the bowl, add nightshade juice, and stir with the spoon.
  2. Light the brazier, heat the mixture until most liquid is gone, remove it, spread it on the table, and wait for it to dry.
  3. Recite:
Acorn powder ground so fine
Nightshade juice, like bitter wine,
Silently in darkness you creep
To bring a soporific sleep
  1. Wave the wand and put the powder in the purchased pouch.

On the pirate ship, pour the powder on the floor of the dank, dark hold and recite Slumber, henceforth! to put the nearby crew to sleep.

Transform another into a cat

  1. Put mandrake-root powder, cat hair, and two spoonfuls of fish oil into the bowl. Stir the oily dough, put it on the table, shape it into a cookie, and let it harden.
  2. Recite:
Mandrake root and hair of cat
Mix oil of fish and give a pat
A feline from the one who eats
This appetizing magic treat
  1. Wave the wand.

Put the finished cookie into the just right porridge before serving it to Manannan.

Brew a storm

  1. Put the cup of ocean water in the bowl. Light the brazier and heat the bowl slowly without boiling, then remove it.
  2. Add the spoonful of mud and toadstool powder, then blow into the brew.
  3. Recite:
Elements from the earth and sea,
Combine to set the heavens free.
When I stir this magic brew,
Great god Thor, I call on you.
  1. Wave the wand and pour the brew into an empty jar.

To activate it outdoors, stir with a finger and recite Brew of storms, Churn it up!. The corresponding early-stop phrase is Brew of storms, Clear it up!.

Become invisible

  1. Cut the cactus with the knife, squeeze its juice onto the spoon, and put the juice into the bowl.
  2. Add the lard and two drops of toad spittle, then stir.
  3. Recite:
Cactus plant and horny toad
I now start down a dangerous road
Combine with fire and mist to make
Me disappear without a trace
  1. Wave the wand and return the ointment to the empty lard jar.

The single application works only where fire and mist coexist. Preserve it for the dragon’s cloud land.

Completing all seven preparations contributes 70 points. Cumulative score: 157.

Freeing Alexander

  1. Put the cat cookie into the porridge.
  2. During Manannan’s next hungry return, place the poisoned porridge before him. Do not offer another food first.
  3. Manannan eats it without detecting the substitution and becomes a cat permanently. The completed transformation awards 12 points.
  4. Retrieve all hidden possessions and the safely replaced wand as needed. Manannan no longer controls the house or its timers.

Cumulative score after liberation: 169.

Pirate voyage and treasure

  1. Finish every Llewdor score event and store purchase before arranging passage. In the tavern show or give the remaining purse to the drunken captain. He takes it and awards three points.
  2. Reach the dock before departure and board the ship for two points. The crew confiscates Alexander’s possessions and locks him in the cargo hold.
  3. With the animal-language dough still in place, listen to the mice. Their useful account locates buried treasure five paces east of a lone palm on the destination beach.
  4. Cast the prepared sleep powder in the dark hold. Climb the crates and rope ladder to escape for two points while the crew sleeps.
  5. Enter the captain’s cabin, open his chest, and recover all confiscated possessions for three points. Take the shovel beside the lifeboat for one.
  6. Wait until the ship anchors near land, leave it, and reach the beach. First arrival awards five points.
  7. From the lone palm, move five paces east and dig with the shovel. Recovering the pirates’ treasure chest awards seven points.

The passage, ship, arrival, and treasure chain contributes 23 points. Cumulative score: 192.

Mountains, dragon, and home

  1. Follow the cliff and cave route through the mountain range. Avoid falls, preserve both transformation components in the wind, and evade the Abominable Snowman. Completing the evasion awards four points.
  2. Descend into ruined Daventry and speak to the old gnome if desired. His messages confirm Alexander’s identity and Rosella’s imminent sacrifice.
  3. Reach the dragon’s cloud land. Use the invisibility ointment where the dragon’s fire and surrounding cloud mist satisfy its activation condition.
  4. While unseen, activate the storm brew. Lightning kills the three-headed dragon and awards seven points.
  5. Approach Rosella only after the dragon is dead and untie her for three points. Lead her down to the castle.
  6. Enter the castle hallway with Rosella. This awards four points and starts the terminal family-reunion sequence.

The mountain contributes 4 and the dragon/rescue/return contributes 14. Final score: 210.

Score Ledger

PhaseSelected awardsPhase totalCumulative
House toolsFly wings 1; bedroom discoveries 12; hidden possessions 4; lever/laboratory 9; kitchen food/tools 7; laboratory jars 63939
LlewdorRaw ingredients and locations 24; bandit/purse/store/tavern 16; Medusa 5; Oracle 34887
Seven spellsSeven independent successful preparations at 10 each70157
ManannanPoisoned-porridge cat transformation12169
Pirate routePassage/boarding 5; hold/cabin/shovel 6; beach/treasure 1223192
MountainsSnowman evasion4196
DaventryDragon 7; Rosella 3; castle entry 414210

The raw positive-site equation independently gives the same result: 215 - 4 - 1 = 210, where four is the second spider-position branch and one is the second dog-fur parser branch.

Deaths and Maximum-Score Dead Ends

  • Ignore a chore, miss a return deadline, disturb sleeping Manannan, or remain in a forbidden room when he appears.
  • Let Manannan see contraband, spell ingredients, the open trapdoor, or the missing wand. Repeated punishment can become terminal.
  • Perform any spell step out of sequence, use a wrong ingredient, recite an incorrect verse, or omit the wand.
  • Consume or lose a unique ingredient before its recipe is complete.
  • Look directly at Medusa or let her touch Alexander before using the mirror.
  • Touch the giant web as a human or fly, throw away the eagle transformation, or approach the spider incorrectly.
  • Wake or get caught by a bandit, lose the purse before all store purchases, or miss the ship after paying the captain.
  • Board without the sleep powder, storm brew, invisibility ointment, or other required possessions; there is no ordinary return to Llewdor.
  • Let the pirates catch Alexander repeatedly, fall from the mast, walk the plank, enter shark water at the wrong time, or fail to recover possessions.
  • Dig randomly without the mice’s coordinate, leave the shovel aboard, fall in the mountain labyrinth, or let the snowman catch Alexander.
  • Expose Alexander to the dragon, activate the storm without invisibility, let the map burn in the dragon’s heat, or untie Rosella while the dragon lives.
  • Reach the castle without Rosella; its doors remain closed and the terminal reunion cannot begin.

Next Validation Work

  1. Turn the safe-state invariants into an exact chore, absence, sleep, and inspection schedule under the original interpreter.
  2. Record parser input for every spell step and verify all seven ten-point awards, including representative wrong-step failures.
  3. Resolve exact movement and timing for Medusa, the spider, bandits, ship, treasure coordinate, snowman, dragon, and Rosella.
  4. Record score, inventory, room, and story checkpoints at 39, 87, 157, 169, 192, 196, and 210.
  5. Package the validated input stream, screenshots, and terminal assertions as a repeatable implementation-conformance playthrough.

King’s Quest 4 Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a maximum- score winning playthrough for the local full KQ4 evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived from the game’s logic, messages, vocabulary, objects, pictures, views, and local copy-protection answer text. No external walkthrough or AGI documentation was consulted.

The result is currently a static candidate route. Script control flow, parser vocabulary, inventory transformations, score mutations, room transitions, and ending messages support a complete 230-point route. Exact movement, day/night timing, random encounters, and the final input stream still require replay under the original interpreter.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/KQ4 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/kq4/index.json

The full game uses interpreter 3.002.086 and the v3 combined directory and prefixed-volume container. The index contains 177 readable logic resources, 5,148 parser conditions, 469 room transitions, 86 score mutations, and 45 inventory slots. No present logic failed to decode. Logic 101 assigns 230 as the declared maximum score.

KQ4 exposed a storage-dependent message rule in the reusable tooling. Its 174 dictionary-expanded logic records contain plain expanded message text, while its three direct records (97, 100, and 131) retain the repeating-key encoding. The local executable sets a direct-read flag at image 0x331b, clears it after dictionary expansion at 0x3348, and conditionally calls the XOR helper from logic setup at 0x1458..0x148c. The indexer and disassembler now select message decoding from that record transform.

All 146 locally readable pictures were decoded into a qualitative contact sheet. Pictures 150 and 151 cannot be read because the selected private copy does not contain KQ4VOL.6. This sheet was generated with patterned brushes disabled, before KQ4’s relocated tables and v3 brush variant were mapped. It corroborates geography and interiors but is not pixel-conformance evidence. The later cross-interpreter audit maps KQ4’s mask table at 0x1669, pointer table at 0x1689, radius-one center-row shape, and v3 horizontal clamp.

Story and Winning State

King Graham collapses while offering his adventurer’s hat to Alexander and Rosella. Genesta transports Rosella to Tamir and explains that a fruit from the Tree of Life can save Graham, but the evil fairy Lolotte has stolen Genesta’s talisman and left her dying. Rosella must recover the talisman before Genesta can send her home.

Lolotte forces Rosella through three errands: capture the unicorn, steal the ogres’ magic hen, and retrieve Pandora’s Box. The promised reward is a forced marriage to Lolotte’s son Edgar. Edgar secretly supplies the key that lets Rosella escape, recover her possessions, kill Lolotte with Cupid’s remaining arrow, and reclaim the talisman.

Returning the talisman to Genesta selects the successful ending only if Rosella also has the healing fruit. The ending restores Genesta, returns the hen, rewards Edgar, sends Rosella home, and reaches logic 138. Messages 24–29 show Rosella giving Graham the fruit, Graham recovering, and the family declaring that he is not done with the adventurer’s hat. The conformance route must enter this terminal sequence with score 230.

Global Constraints

The route crosses several persistent state boundaries:

  1. Day gives way to night, enabling the haunted-house ghost sequence. Morning eventually closes that opportunity.
  2. The shovel has limited useful digs. Dig only the five graves identified by their epitaphs; unnecessary digs risk replacing it with the broken shovel.
  3. The undead retreat only while Rosella carries the obsidian scarab. The dark cave and crypt also require the dwarf’s lantern.
  4. Cupid supplies exactly two arrows. One befriends the unicorn and the other kills Lolotte. Firing either elsewhere destroys the winning route.
  5. The board is reused at the waterfall crevice and in the swamp. Pick it up after crossing and never abandon it before obtaining the fruit.
  6. Taking or opening Pandora’s Box is dangerous story state. For maximum score, recover it from Lolotte and return it to its tomb before visiting Genesta.
  7. After the third Lolotte errand, dawn leads to the forced-wedding loss. Escape Edgar’s room and kill Lolotte during the available night.
  8. Returning the talisman without the fruit produces a completed Tamir rescue but Graham dies. Eating or losing the fruit likewise prevents the winning terminal state.

Candidate Route

Tamir tools and trading chains

  1. Accept Genesta’s request and explore Tamir. Look under the stone bridge and take the golden ball for two points.
  2. At Cupid’s pool, approach or speak closely enough to startle Cupid away, then take the abandoned golden bow and two arrows for two points. Do not fire either arrow yet.
  3. At the frog pond, catch the crowned frog. While holding it, take its crown for two points. Let the frog return, catch it again, and kiss it. The transformation sequence awards three points and the departing prince’s crown sequence awards two more. This seemingly redundant order is required for the 230-point route: taking the crown directly and kissing the frog are separate scored branches, and the catch parser does not require the crown to remain on the frog.
  4. Enter the old manor’s parlor. Take The Compleat Works of William Shakespeare for two points. Examine the portrait, follow its gaze to the left wall, and flip the small latch to reveal the secret stair for four. Take the shovel in the secret tower for two.
  5. Give the Shakespeare book to the wandering minstrel for his lute and three points. Play the lute near Pan to attract him, then give it to him for the silver flute and three points.
  6. Enter the ogres’ house only while it is safe. Take the sharp axe from the upstairs bedroom for two points. Use it against the grabbing trees; their retreat is worth four.
  7. Clean the Seven Dwarfs’ home before they return: use the broom, sweep and tidy the rooms, clean the dishes, make the beds, and hide the dirty clothes under the rug. Completion awards five points. After the dwarfs eat and leave, take the forgotten diamond pouch for two.
  8. At the mine, offer the pouch back to a dwarf. He refuses to keep it, gives Rosella an oil lantern, and awards three points. The pouch remains available for the next trade. Light or extinguish the lantern with its attached flint as needed.
  9. Give the diamond pouch to either member of the fisherman’s household. They exchange it for the fishing pole and three points.
  10. Take the earthworm when the robin exposes it for two points. Baiting the fishing pole with the worm awards one. Cast from the pier and catch the dead fish for three.

Island and waterfall chain

  1. Give the fish to the pelican on the desert island for four points. Take the dropped silver whistle for two, blow it for two, and ride the summoned dolphin for two.
  2. Search the wrecked boat and take the golden bridle for three points. On Genesta’s island, take the peacock feather for two.
  3. The ocean route eventually places Rosella inside the whale. Tickle its throat with the peacock feather to trigger the sneeze and escape for five points. Avoid the whale’s fumes, drowning, and the shark while traveling.
  4. Enter the waterfall pool and force through the current to discover the cave behind the waterfall for five points. Take the board for two and the bone from the next cave for two. Use the board across the dark crevice, retrieve it after crossing, and cross back later; the first two successful crossings advance a two-stage counter and award two points each.

Witches, graves, and crypt

  1. In the witches’ cave, seize their shared glass eye for three points. Leave and return while they are blind; they throw down an obsidian scarab. Take it for two, then return the eye for three. Carry the scarab whenever undead are active.
  2. At night, use the shovel only on the five epitaph-matched graves: the baby, old miser, sad young woman, lord of the manor, and little boy. Recover the silver rattle, bag of gold, locket, Medal of Honor, and toy horse. Each correct dig awards three points, for fifteen total.
  3. Follow each ghost into the manor and return its matching possession: rattle to the baby cradle, gold to the miser, locket to the woman, medal to the lord, and toy horse to the boy. Each resolution awards two points. Once the boy leaves the attic chest, open it and take the sheet music for another two.
  4. Sit at the dusty pipe organ and play from the sheet music. The correct tune opens its hidden drawer for four points; take the skeleton key for two.
  5. Use the skeleton key to unlock the mountainside crypt for three points. Carry the lit lantern and scarab, descend the rope ladder, and drive off the mummy. Reaching the lower tomb awards two points. Taking Pandora’s Box subtracts two and then awards four on the first valid acquisition, a net two. Preserve the box for Lolotte’s third errand.

Tree of Life

  1. Take the recovered board through the waterfall cave and across both crevices to the swamp. Place it from a safe grass tuft to the Tree of Life island for two points.
  2. Play Pan’s flute to mesmerize the cobra for four points. Cross promptly and take the magic fruit for ten. Recover the board where required and keep the fruit uneaten.

Lolotte’s three errands

  1. Approach Lolotte’s mountain domain and allow her henchmen to bring Rosella before the throne. Lolotte orders the capture of the unicorn.
  2. At the unicorn meadow, shoot exactly one Cupid arrow at the unicorn. The love arrow makes it trust Rosella and awards four points. Fit the golden bridle for three, mount, and ride it to Lolotte. The first completed errand awards seven points.
  3. For the second errand, return to the ogres’ house with the cave bone. Give the bone to the dog for four points. Wait for the ogre to sleep, take the magic hen from the locked bedroom area without waking him, and escape for four. Delivering the hen to Lolotte awards the second seven points.
  4. Deliver Pandora’s Box for the third errand. Lolotte awards another seven points, confiscates Rosella’s possessions, and locks her in Edgar’s tower room to await the morning wedding.

Escape, restoration, and ending

  1. Wait for Edgar’s red rose to slide under the door. Take it, remove the hidden gold key for two points, and use that key to unlock Edgar’s door for two.
  2. In the castle kitchen, open the cabinet and recover all confiscated possessions for four points. Use the gold key to unlock Lolotte’s tower door for two.
  3. While Lolotte sleeps, shoot the second and final golden arrow at her. Her incompatibility with the arrow’s love kills her and awards eight points. Take Genesta’s talisman from her body for five.
  4. Recover Pandora’s Box and the magic hen from the storage room for two points each. Open the stable gate and free the imprisoned unicorn for four.
  5. Return Pandora’s Box to the floor of its lower tomb for two points. Exit the crypt, lock its door, and kick the skeleton key underneath for two. These steps restore the dangerous object and complete the crypt score chain.
  6. Visit Genesta carrying the talisman, hen, and uneaten fruit. Giving her the talisman awards ten points and starts the ending. Returning the hen during the ending awards two. Genesta restores Edgar, sends Rosella home, and the final sequence gives the fruit to Graham at score 230.

Score Ledger

The following grouped ledger preserves sequential awards while collapsing alternate room copies of the same parser branch.

ChainPoints
Golden ball; direct frog crown; frog-kiss transformation and prince sequence9
Cupid’s bow and arrows2
Shakespeare book, secret latch, and shovel8
Book-to-minstrel and lute-to-Pan exchanges6
Ogre axe and grabbing-tree retreat6
Dwarf-house cleaning, diamond pouch, and honest mine return10
Diamond-pouch fishing-pole trade3
Worm, baiting, and fish catch6
Pelican, whistle pickup/use, and dolphin mount10
Golden bridle, peacock feather, and whale escape10
Waterfall discovery, board, bone, and two crevice crossings13
Glass eye seizure/return and scarab8
Five correct grave digs and five resolved ghosts25
Sheet music, organ drawer, and skeleton key8
Crypt unlock, lower tomb, first Pandora acquisition, later return, and relock11
Swamp board placement, cobra flute, and healing fruit16
Unicorn love arrow and bridle7
Dog distraction and magic hen8
Three Lolotte errands21
Edgar-room rose/key and door4
Recovered possessions and Lolotte tower door6
Lolotte death and talisman13
Recovered box and hen; freed unicorn8
Talisman and hen returned to Genesta12
Total230

The raw sum of all positive sites is 267. The excess consists of duplicated Pan, minstrel, grabbing-tree, fisherman, book-position, crossing-direction, and grave-location code paths. The direct crown action is not discarded as a duplicate: after the frog returns, it can be caught again and kissed, so both that two-point action and the five-point transformation sequence belong to the maximum route.

Deaths, Losses, and Dead Ends

The resources expose at least these replay-relevant failure families:

  • drowning, shark attack, whale fumes, and getting stranded during ocean travel;
  • entering grabbing-tree, ogre, dog, witch, zombie, mummy, troll, or cobra hazards without the corresponding axe, bone, scarab, lantern, flute, or safe timing;
  • bad grave digs that exhaust the shovel before all five ghost possessions are recovered;
  • falling in darkness, stepping into swamp quagmires, or remaining beside the cobra after its temporary trance ends;
  • wasting either golden arrow, abandoning a one-use chain item, opening or mishandling Pandora’s Box, or eating the healing fruit;
  • waking Lolotte, failing to escape before morning, or reaching the forced wedding;
  • returning the talisman without the fruit, which saves Genesta and Tamir but reaches the explicit ending where Graham dies.

Replay Requirements

A deterministic original-interpreter playthrough should record at least:

  1. score and inventory after the social/trading chains;
  2. day-to-night transition and each grave/ghost pair;
  3. crypt entry, first Pandora acquisition, and later restoration;
  4. fruit acquisition with board and flute state;
  5. each Lolotte errand at cumulative quest-state values 1, 2, and 3;
  6. confiscation, rose delivery, possession recovery, and the two arrow uses;
  7. restored unicorn, box, hen, talisman, and fruit state before Genesta; and
  8. logic 138’s healthy-Graham terminal sequence at score 230.

The replay must also settle exact movement coordinates, random encounter control, day/night timing, and whether the unusual crown-then-recatch sequence needs any delay or room transition under the original interpreter.

Gold Rush Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a maximum- score winning playthrough for the local GR evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived from the game’s logic, messages, vocabulary, objects, pictures, and views. No external walkthrough or AGI documentation was consulted.

The result is currently a static candidate route. Script control flow, parser vocabulary, inventory transformations, score mutations, route checkpoints, and ending messages support a complete 255-point route. Exact movement, timed departures, random hazards, and the final input stream still require replay under the original interpreter.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/GR \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/gr/index.json

The full game uses interpreter 3.002.149 and the v3 combined directory and prefixed-volume container. The index contains 182 readable logic resources, 6,227 parser conditions, 431 room transitions, 150 immediate score mutations, and 131 inventory slots. No present logic failed to decode.

Logic 101 initially assigns 250 as the displayed maximum. Buying the Panama ticket in logic 3 assigns route value 2, awards nine points, and raises the maximum to 255. This is not the Cape Horn branch: route 2 requires mosquito netting, invokes the Panamanian-jungle controller, and is checked against a 100-point arrival total. Route 1 requires citrus fruit, follows Cape Horn, and has a 95-point arrival total. The overland route uses route value 3.

All 186 present picture resources decode in a qualitative brush-disabled sweep. That sheet predates the cross-interpreter brush audit, so it corroborates geography and interiors but is not pixel-conformance evidence. The later audit maps GR’s mask table at 0x1430, pointer table at 0x1450, radius-one center-row shape, and v3 horizontal clamp.

Story and Winning State

Jerrod Wilson lives in Brooklyn and works for a newspaper. A letter from his long-lost brother Jake contains a gold flake and a request to come to the area drained by the American River. In California, Jake is using the name James and has concealed a trail of family clues to keep his discovery secret.

Jerrod can travel by Cape Horn, Panama, or the overland trail. The highest declared terminal score requires Panama. After crossing the isthmus, he reaches Sacramento and Sutter’s Fort, recovers the trail to James, follows James’ mule to a cabin, and enters the mine beneath it. Logic 162 reunites the brothers. Logic 193 then reveals the exceptionally pure gold vein, awards the final six points for non-overland routes, displays the success messages, and reaches the terminal thank-you state at score 255.

Global Constraints

  1. Sell the Brooklyn house before the gold-rush state lowers its value. The early sale awards nine points; the late sale awards only six.
  2. Use the Panama ship ticket and buy mosquito netting. Citrus fruit belongs to Cape Horn, while the ferry and stage ticket out of New York belong to the overland route.
  3. Stay off the Brooklyn park grass. Repeated violations subtract up to three points and prevent the exact 60-point departure checkpoint.
  4. Cooperate when the armed Panamanian group demands valuables. Refusal is fatal. The robbery intentionally advances the route while preserving the family evidence needed later.
  5. Keep moving through the jungle, retain the mosquito net, use the vine when ants cover the trail, and avoid snakes, alligators, swamp, and disease delays.
  6. Keep the ancient gold disk. Spending it on a mule or shovel subtracts two points and removes a unique object. Ordinary California gold can fund the mule; the Brooklyn gold coin can buy the shovel.
  7. Brand Jerrod’s mule before stabling it. Select the same branded mule when retrieving it; choosing an unbranded or differently branded animal leads to rejection or execution for theft.
  8. Preserve the note, magnet, string, matches, lantern, steel key, and pick. They form the cabin and mine dependency chain.
  9. Gold accumulation is score-bearing. Logic 110 awards one point for each successful increase in the gold-amount state, up to 50 outside the deep mine and 70 in mine rooms 147–163. The maximum route must reach the shared 70-award cap without triggering the robbery penalty.
  10. The score is byte-sized. Avoid penalties and unintended extra/alternate branches so the final six-point award lands on 255 rather than wrapping.

Candidate Route

Brooklyn: 60 points

  1. At home, inspect the family album and take the family photograph. Retrieve the bank statement from the roll-top desk, read it, and retain the account number.
  2. Obtain Jake’s letter from the post office. Open and read it, inspect its postmark, then lift the stamp to expose the hidden gold flake.
  3. In the parks, take the gold coin and pick the flowers without walking on the grass. At the cemetery, read both Wilson family stones and place the flowers on the family graves.
  4. Read the travel sign. At the newspaper office, examine the California clipping and formally quit while speaking to the boss at close range.
  5. Sell the house while property values are still at their premium. This must precede the state change that reduces the award from nine to six.
  6. At the bank, use the statement’s account number and complete the withdrawal sequence. Do not lose the statement before reading it.
  7. At 12 Front Street, choose Panama and buy the ticket. Buy mosquito netting from the hardware store. Both the purchase path and the dock check share a one-time flag, so the four equipment points are awarded only once.
  8. Board the Sea Farer directly in Brooklyn. Do not take the ferry sequence, which belongs to the overland departure. The ship’s checkpoint must report 60 out of 60.

Panama crossing: 40 points

  1. Disembark near the Rio Chagres and proceed with the guide. Wear or retain the mosquito net whenever the route checks it.
  2. When armed people demand the party’s valuables, answer yes and cooperate. The completed negotiation awards four points. Refusal reaches the spear death.
  3. Continue from the river boat onto the old Spanish road. When jungle ants cover the path, escape by the hanging vine. Completing that event awards four points.
  4. Speak to the resting traveler under the tree and accept his Bible. The conversation and acquisition are alternate parser entries for the same seven-point award, not two awards.
  5. Take the higher-valued successful jungle traversal branch. Its five-point award replaces the three-then-two alternative state sequence.
  6. Search the densely overgrown old gold route, expose the ancient Spanish gold disk, and take it for ten points.
  7. Cross the later stream hazard successfully for ten points. Avoid the swamp and concealed alligator branches.
  8. Reach the California arrival sequence at exactly 100 points. The checkpoint explicitly excludes the optional Psalm 23 award. Once the Bible read is enabled, read Psalm 23 for the separate five points.

Sacramento, Sutter’s Fort, and the hotel

  1. Board the one available Sacramento stage to Sutter’s Fort before it leaves. The approach/boarding sequence contains two separate one-point awards.
  2. Read the relevant Sutter’s Fort gravestone for two points. Hold Jake’s open letter against the Wilson Marshall stone so its holes reveal ROOM 11; this clue sequence awards five.
  3. At the blacksmith, answer the family questions with Jerrod’s identity and Jake’s name. Receive the branding iron and learn that Jake is known locally as James. The completed recognition sequence awards two points.
  4. Obtain a gold pan, lantern, and shovel. Pay for the shovel with the Brooklyn gold coin for one point; never offer the ancient disk. Find enough ordinary California gold to buy the mule, then take the purchased mule for three.
  5. Heat and use the branding iron on the mule for three points. Stable it and later identify the same branded animal; the successful corral check awards seven.
  6. In the City Hotel, deliver the confidential message to the person emerging from room 11 before he walks away for three points.
  7. Recover the robbery trail in the hotel room. Rotate the decorative cannon wheel to raise the fireplace wall for five points. Recover the note from Jake, magnet, string, and gold coin for one point each. Complete the bird capsule exchange: obtain the aerogram for one point and place the family photograph in the capsule for three. The duplicate cannon score in the adjacent room is the same one-time event.
  8. Follow the clue state until James’ distinctive old mule appears. Begin following it in an eligible wilderness room for seven points and keep close through each direction change. Losing either mule can strand the route.

Cabin and mine

  1. Enter James’ cabin and take the matches from the table for one point. Move the central rug, expose the trap door, and descend through the outhouse/mine approach; the committed descent sequence awards three.
  2. At the locked mine door, tie the string to the magnet for two points. Put it through the hole, lower it, and pull it back up, receiving two points at each stage and retrieving the steel key. Unlock the double lock for one. The complete door chain is nine points.
  3. Keep a light available. The mine scripts distinguish lantern light, a briefly lit match, and darkness; darkness hides traversable geometry and introduces falls. Take the pick when it appears for one point.
  4. Traverse each distinct scored mine boundary. Rooms 151, 152, 154, 156, 157, 158, and 160 each contribute one first-crossing point; room 161 contributes two independent crossings; room 162 contributes three. Opposite-direction code paths set the same per-boundary state and are alternatives, not extra awards.
  5. Search, strike, and collect gold at valid sites throughout California and especially the deep mine. Each successful increase advances the displayed gold amount and awards one point until the shared counter reaches 70. Do not leave this grind until all 70 awards have been obtained.
  6. Continue to the end of James’ mine. Avoid the five-point failure branch in logic 162. Break through toward the sound of picking, reunite with James, and work the final wall until the pure-gold vein sequence begins. Logic 193 adds six points on the Panama route and ends the game at 255.

Score Ledger

The ledger groups sequential awards and collapses route alternatives and duplicate room copies.

ChainPoints
Brooklyn documents: postmark, stamp, statement read4
Early house sale9
Gold coin and flowers6
Family gravestones and flower placement5
Travel sign2
Post-office letter5
Family photograph, statement retrieval, and album8
Newspaper clipping and quitting5
Bank withdrawal3
Panama ticket9
Mosquito net4
Panama negotiation, ant/vine escape, and Bible15
Best jungle traversal, ancient disk, and stream crossing25
Psalm 235
Sacramento stage2
Blacksmith recognition and mule branding5
Mule purchase, shovel purchase, and corral identification11
Gravestone and perforated-letter clue7
Hotel message delivery and recovery/capsule puzzle16
Begin following James’ mule7
Cabin matches and committed descent4
Magnet/string/key door puzzle9
Mine pick and distinct boundary discoveries13
Gold-amount progression cap70
Final Panama-route ending award6
Total255

The raw score-site list is much larger because it combines three travel routes, opposite-direction copies, parser synonyms, alternate payment media, and penalty branches. In particular, the four ferry points belong to the overland departure, the Cape ship survival puzzles do not occur on Panama, and spending the ancient disk subtracts points.

Deaths, Losses, and Dead Ends

The resources expose at least these replay-relevant failure families:

  • missing timed departures in Brooklyn or Sacramento;
  • walking repeatedly on park grass or selling the house after its value falls;
  • boarding without the correct ticket or required route-specific supply;
  • refusing the Panamanian demand, losing time to jungle disease, falling to ants, snakes, alligators, swamp, or other route hazards;
  • spending the ancient disk, buying insufficient supplies, losing a mule, or selecting an incorrectly branded mule;
  • missing the room-11 recipient, losing the hotel/capsule clue chain, or abandoning the magnet, string, matches, lantern, key, or pick;
  • entering dark mine geometry, falling, taking the explicitly scored failure branch, or reaching the ending before exhausting the 70 gold awards;
  • continuing to add score after the intended byte total, which risks wrapping the displayed score rather than producing a higher terminal result.

Replay Requirements

A deterministic original-interpreter playthrough should record at least:

  1. the pre-rush nine-point house sale and the 60-point ship checkpoint;
  2. each Panama route hazard and the 100-point California checkpoint;
  3. the Psalm 23 award as a separate post-checkpoint event;
  4. robbery inventory before and after the hotel recovery sequence;
  5. mule purchase, branding, corral identity, and James-mule follow state;
  6. every magnet/key transition and first-time mine boundary award;
  7. gold amount, gold-award counter, and score at the outside-50 and mine-70 thresholds;
  8. reunion, final six-point mutation, displayed 255 maximum, and terminal thank-you state.

The replay should also deliberately exercise one missed departure, one jungle death, one ancient-disk payment, one wrong-mule selection, and one dark-mine fall. These are useful negative conformance scenarios, but are not steps in the winning route.

Police Quest 1 Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a winning playthrough for the local PQ1 evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived only from the game’s logic, messages, vocabulary, objects, pictures, views, and two read-only local save files. No external walkthrough or AGI documentation was consulted.

The story route is complete from the opening roll call through the terminal parade. It is currently a static candidate route, rather than a validated input replay. The game declares a maximum score of 245. The positive score operations sum to 270 before alternatives are removed; several shared and losing branches are classified below, but the exact 245-point ledger still needs a source-level reachability pass and an original-interpreter replay.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/PQ1 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/pq1/index.json

The selected game identifies itself as Police Quest: In Pursuit of the Death Angel, version 2.0G dated December 3, 1987. It uses interpreter 2.917. Its split directories contain 118 readable logic resources, 71 pictures, 220 views, and 36 sounds. The index found 3,147 parser conditions, 108 explicit room transitions, 25 inventory slots, and 114 positive score operations. No present logic resource failed to decode.

Logic 101 assigns 245 as the displayed maximum. Logic 79 captures Jessie Bains after the successful hotel-room confrontation. Logic 103 depicts his trial and sentencing and awards four points late in that sequence. Logic 104 then shows the ticker-tape parade, presents Sonny with the Key to the City, and displays Congratulations. You've won Police Quest!. That is the terminal winning state used by this analysis.

Two bundled saves were inspected as independent consistency evidence only. Both preserve score 156 of 245 near the hotel operation, with the Tribune, Hoffman file, trick cane, marked money, room key, and Cadillac keys in inventory. They corroborate the decoded save-state and inventory interpretation, but they do not define the route or prove a maximum score.

All 71 present pictures decoded in a qualitative sweep with patterned brushes disabled. The resulting sheet covers the station, patrol map, traffic and accident sites, Blue Room, cafe, jail, courthouse, park, Cotton Cove, Hotel Delphoria, poker rooms, and ending locations. PQ1’s own patterned-brush table was not mapped when the sheet was generated, so these renders establish geography only and are not pixel-conformance evidence. The later audit maps its v2 mask and pointer tables at 0x15ef and 0x160f.

Story and Winning State

Sonny Bonds is a veteran patrol officer in Lytton. Routine patrol exposes a connected series of crimes: murdered drug dealer Lonny West, cocaine reaching local students, a stolen Cadillac driven by Marvin Hoffman, and a street informant’s warning about a trafficker called the Death Angel. Fingerprints and records identify Hoffman as Jason Taselli and connect the organization to Jessie Bains.

After a park narcotics arrest and Kathy Cobb’s fatal overdose, Sonny transfers to Narcotics. Hoffman/Taselli is later found murdered at Cotton Cove. Sonny works undercover at the Hotel Delphoria as Jimmy Lee Banksten, called Whitey, using bleached hair, a white suit, a cane concealing a derringer, marked money, and a transmitter pen. Sweet Cheeks introduces him to bartender Woody Roberts. Sonny buys entry to the back-room poker game, wins an invitation to the high-stakes game, identifies Bains, and transmits the room number to backup officers. The successful confrontation captures Bains alive. His conviction, sentence, and Sonny’s parade constitute the winning state.

Global Constraints

  1. Attend both patrol briefings and the later Narcotics briefings. Lateness or insubordination can terminate employment, while missed briefings suppress the state transitions needed for later calls.
  2. Follow police procedure. Keep the service revolver loaded on patrol, unload and secure it before entering the jail, search suspects, read rights, handcuff behind the back, and preserve evidence. Several shortcuts are immediately fatal.
  3. During the first traffic stop, reject Helen Hots’ offer and complete the citation. Accepting her proposition is a scored but corrupt branch and does not belong to the successful police-procedure route.
  4. Inspect and call in clues while they are available. The bullet hole, license plates, vehicle identification number, gun, drugs, fingerprints, Hoffman file, wanted poster, computer record, and phone calls advance separate one-time states.
  5. Obtain the no-bail warrant before Hoffman’s attorney completes bail, and deliver it at the jail. The warrant delays his release long enough for the investigation even though the later story still finds him murdered.
  6. Search and question the park suspects before completing the arrests. Ask for the friend’s name and question Colby about his supplier; these select the higher-valued shared information branch and expose Leroy Pierson.
  7. Preserve the undercover equipment and use the alias Jimmy Lee Banksten at the hotel. Pay for the room with marked money and do not reveal Sonny’s identity to hotel personnel.
  8. Send Sweet Cheeks away before the high-risk portion of the operation. Buy Woody’s cooperation, follow him to the back room, and win the preliminary poker sequence to obtain the invitation/password.
  9. Contact Lieutenant Morgan and transmit Room 404 before entering Bains’ room. The earlier transmitter path awards five points; the later telephone fallback awards only two and is an alternative guarded by the same state.
  10. Do not confront Bains without armed backup. The successful room sequence requires the prior transmitter, backup setup, poker invitation, and Bains identification states.

Candidate Route

First patrol shift

  1. At the station, attend Sergeant Dooley’s briefing in the assigned place. Check Sonny’s pigeonhole, take and later read the Lytton Tribune, and collect the patrol equipment: revolver, ammunition, briefcase, keys, radio extender, wallet, and nightstick. Load the revolver, take the notebook, pen, and ticket book from the briefcase, and perform the patrol-car walk-around.
  2. Respond to the red-light runner. Stop the car, approach the driver, identify Sonny, request and inspect Helen Hots’ license, and call in the plate. Say no to her attempted bargain. Write the citation, hand her the ticket book and pen for a signature, recover them, return her license, and complete the stop without accepting the proposition.
  3. At the traffic accident, secure and inspect the scene. Examine the victim, car, window, bullet hole, and identifying details; call Dispatch and treat the site as a homicide rather than an ordinary collision.
  4. Meet Jack at the Blue Room and remain for the scored conversation and shift reminder. At Carol’s Caffeine Castle, answer Detective Hamilton’s phone call, resolve Carol’s complaint, and deal with the bikers. Question Sweet Cheeks about cocaine and information until she identifies the Death Angel and the flower-tattoo clue.

DUI and Hoffman arrests

  1. Return for the next briefing and read the transfer memo. On patrol, stop the erratically driven vehicle. Check the license and plate, administer the field sobriety test, arrest the driver for DUI, read his rights, search him, and handcuff him behind his back.
  2. Before entering the jail, lock the revolver in the gun locker. Book the drunk, transfer his property, remove the handcuffs only when directed, and retrieve the secured weapon afterward.
  3. Respond to the stolen light-blue Cadillac report using felony-stop procedure. Draw the revolver, order the driver out, control his position, handcuff him, read his rights, search him, and recover his handgun. Inspect the door-jamb vehicle number and trunk drugs. Call in the vehicle and gun.
  4. At the jail, secure Sonny’s gun before booking Hoffman. Complete the booking and uncuffing sequence. Search the station evidence and records, take the Cadillac keys and Hoffman file, inspect Laura’s note and mug shot, take the wanted poster, and use the computer query.
  5. Submit the transfer memo to Narcotics. Follow the homicide, fingerprint, and Chicago leads by telephone; the calls establish the Hoffman/Taselli/ Pierson aliases and connect Taselli to Jessie Bains.

Warrant, drug bust, and murder investigation

  1. Use the emergency-number clue and case evidence to request a no-bail warrant from Judge Palmer. Take the issued warrant to the jail before the release sequence completes and hand it to the jailer.
  2. Join the park drug operation. Observe the sale, question Victor Simms and the other students, ask the friend’s name, question Colby about his source, and search the suspects. Arrest, read rights, handcuff, and search Sonny’s assigned suspect, then book the arrested students and recover the cuffs.
  3. Call Dispatch with the Colby/Pierson result. Speak with Sweet Cheeks in jail and obtain her agreement to help establish the hotel cover.
  4. At Cotton Cove, inspect the body and clothing and call in the identification information. The body is Hoffman/Taselli, murdered execution-style. Read the Hoffman file and complete the computer and telephone research before proceeding to the undercover briefing.

Undercover operation

  1. Attend the full Narcotics briefing. Receive the white suit, trick cane, marked money, hair bleach, and planned transmitter. Shower, bleach Sonny’s hair, change into the white suit, and leave uniform-only equipment secured at the station.
  2. At Hotel Delphoria, register as Jimmy Lee Banksten, pay for the room, take the room key, and enter the assigned room. Contact Lieutenant Morgan, send Sweet Cheeks away by cab, and tell backup the later operation will be in Room 404. Meet the detectives and take the transmitter pen.
  3. In the cocktail lounge, order the drink that signals Sweet Cheeks. Let her introduce Whitey to Woody. Use marked money to buy Woody’s cooperation, then follow him into the back room.
  4. Complete the preliminary poker sequence and retain the resulting invitation or password. Enter the high-stakes game, continue until the script identifies Jessie Bains, and preserve the transmitter and backup states.
  5. Follow Bains to Room 404. During the confrontation, let the backup officers respond to the transmitted evidence and room location. The successful branch wounds and arrests Bains rather than killing Sonny.
  6. Allow logic 103’s trial and sentencing sequence to finish; its late four- point award precedes room 104. The parade and You've won Police Quest! message are the terminal checkpoint.

Score Evidence

The following is an evidence ledger, not yet a claimed closed 245-point replay. It prevents syntactic score sites from being mistaken for independent awards.

ConstraintRaw pointsWinning-route pointsReason
Helen proposition and citation conversation97The two one-point corrupt responses are outside the citation route
Gun-locker alternatives42Revolver and loaded-revolver parser branches share one guard
Station phone-number discovery42Examine-phone and ask-number branches share one guard
Drug-source questioning across logics 56/5795Three parser paths share one information flag; choose the five-point branch
Newspaper availability in briefing rooms 44/4521Both room versions share one availability flag
Room 404 notification75Early transmitter and late phone fallback share one guard

These proved constraints remove 13 points from the raw 270. Further phase-incompatible positive branches must remove 12 points to reach the declared 245. Their exact grouping remains open because flattened condition reports lose the distinction between sequential phases and mutually exclusive room states. The next pass must follow the controlling phase variables through logics 43–47, 56–60, 67–71, and 109, then validate score checkpoints in the original interpreter. Until that is done, 257 is only a static upper bound, not an asserted obtainable score.

Failure and Dead-End Map

  • Missing or disrupting briefing can result in firing or administrative leave.
  • Entering the shower clothed or leaving it running subtracts points.
  • Mishandling a loaded revolver can shoot Sonny; carrying it into the jail lets prisoners seize it.
  • Failing to search, restrain, or control suspects creates fatal attacks or invalid bookings.
  • Accepting Helen’s proposition abandons correct procedure.
  • Missing the warrant timing permits Hoffman’s release sequence to advance.
  • Revealing the undercover identity, omitting backup, or entering Bains’ room without the transmitter chain leads to failed or fatal confrontation paths.
  • Leaving required evidence or equipment behind can create a late operational dead end even when the room graph remains navigable.

Replay Work Remaining

  1. Close the remaining 12-point reachability gap directly from phase-variable and room-entry control flow.
  2. Record exact parser text for every route action, especially booking charges, warrant evidence, computer searches, hotel registration, and poker input.
  3. Map deterministic movement coordinates and timing for patrol stops, the park bust, briefing animations, poker, and Bains’ room.
  4. Run the route under the original interpreter with score, room, inventory, and phase checkpoints, including a terminal 245/245 capture.
  5. Package the resulting input stream and checkpoint manifest as a reusable whole-game conformance scenario.

Leisure Suit Larry 1 Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a maximum- score winning playthrough for the local LSL1 evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived only from the game’s logic, messages, vocabulary, objects, pictures, and views. No external walkthrough or AGI documentation was consulted.

The result is currently a static candidate route. All score operations, inventory dependencies, money gates, major parser actions, and the terminal sequence are accounted for at 222 points. Exact movement, random age questions, casino outcomes, and the final timed input stream still require replay under the original interpreter.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/LSL1 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/lsl1/index.json

The selected game identifies itself as Leisure Suit Larry in the Land of the Lounge Lizards, version 1.00 dated June 1, 1987. It uses interpreter 2.440. The split directories contain 46 readable logic resources, 43 pictures, 151 views, and 28 sounds. The index found 2,274 parser conditions, 16 explicit room switches, 21 inventory slots, and 49 positive score operations. No present logic resource failed to decode.

Logic 51 assigns 222 as the displayed maximum. The 49 positive score operations also sum to exactly 222, and the game contains no negative score operation. Direct inspection shows that every award belongs to the maximum route; there is no duplicate score-site reduction analogous to the other games analyzed so far.

Logic 45 contains the terminal sequence. After the final penthouse encounter, it awards 25 points, displays Congratulations, Larry!!!, states that Larry has completed the evening and emerged victorious, reports his performance, and presents the closing credits and sequel promotion. This is the winning terminal state used by this analysis.

All 43 present pictures decoded in a qualitative sweep with patterned brushes disabled. The resulting sheet covers Lost Wages streets, Lefty’s, the hooker’s room and fire escape, convenience store, disco, casino, chapel, hotel, honeymoon suite, upper-floor security desk, penthouse, rooftop spa, and final bedroom. This historical sheet was rendered with brushes disabled and remains qualitative evidence. The later interpreter audit maps LSL1’s v2 mask and pointer tables at 0x1575 and 0x1595; the renderer can now discover them without an SQ2-specific offset.

Story and Winning State

Larry Laffer enters Lost Wages with $94 and attempts to end the night no longer a virgin. His route passes through Lefty’s bar and its upstairs room, the convenience store, disco, casino, wedding chapel, and casino hotel. The first encounter does not satisfy him. Fawn accepts gifts and money, marries Larry, then robs him after tying him to the honeymoon-suite bed. The rope from that escape enables Larry to recover pills from Lefty’s fire escape. Those pills distract hotel guard Faith and expose the private penthouse control. In the penthouse, Larry meets Eve in the rooftop spa, gives her an apple, follows her to the bedroom, and reaches the terminal winning sequence.

Global Constraints

  1. Pass the initial adult-verification quiz. The game chooses questions at runtime, so a deterministic replay must either answer from the decoded question/answer table or control the random sequence.
  2. Preserve enough cash for cab fares, store purchases, the apple, Fawn’s $100 request, and the separate $100 chapel fee. The casino must fund the route; both slot machines and blackjack are random without a controlled replay strategy.
  3. Do not give wine to the cab driver. He becomes impaired and crashes into a bridge abutment. Pay each fare before leaving the cab.
  4. Buy and use the prophylactic before the first upstairs encounter. That encounter transforms it into the used item; dispose of the used item for the separate one-point award.
  5. Carry rather than drink Lefty’s whiskey. The hallway drunk exchanges the drink for the remote control needed to bypass the pimp without paying $100.
  6. Keep the rose, candy, and diamond ring for Fawn. Each is a separate five-point gift, and the ring is also required by the wedding ceremony.
  7. Obtain the pocket knife by giving the convenience-store wine to the outside drunk. Without the knife, Fawn’s bed ropes form a terminal dead end.
  8. Turn on the honeymoon-suite radio and hear the Ajax Liquor advertisement before leaving. It supplies 555-8039, needed to order Fawn’s wine.
  9. After escaping Fawn, take the rope. Combine its reach with the hammer from Lefty’s trash to break the remote window and recover the pills.
  10. Complete the inflatable-doll sequence before the final encounter. Taking, inflating, and using it account for 18 points independent of the 25-point winning sequence.
  11. The game is real-time. Logic 19 ends the night at sunrise if the route takes too long, so a replay must bound casino play and travel delays.

Candidate Route

Lefty’s and the first encounter

  1. Enter Lefty’s and visit the restroom. Sit on the toilet, read the graffiti until it reveals the password Ken sent me, examine the sink, and take the diamond ring.
  2. At the bar, sit down and order whiskey. Do not drink it; carry the filled glass. In the back hallway, take the rose and give the whiskey to the drunk. He eventually gives Larry the remote control.
  3. Return to the bar and use the restroom password at the naugahyde door. In the storage room, use the remote on the television and change channels until the program distracts the pimp. Pass him and go upstairs without paying his $100 demand.
  4. In the bedroom, take the box of candy. Put on the prophylactic before approaching the hooker, then complete the encounter. Afterwards, dispose of the used prophylactic and read the centerfold when the magazine is available.
  5. Leave through the window to the fire escape. Search the trash and take the hammer. The pills in the other window cannot yet be reached safely; Larry must return after obtaining Fawn’s rope.

Store, telephone, and travel preparation

  1. At the convenience store, take the bottle of wine and the Jugs magazine. Ask the clerk for a prophylactic, complete his option sequence, and pay for it. Read the magazine for its separate one-point award.
  2. Outside, examine the telephone and its markings. Call 555-6969, complete the survey prompts, and later answer the returning call. Call Sierra at the decoded number for the five-point promotional conversation.
  3. Give the bottle of wine to the drunk outside the store. He gives Larry his pocket knife. Preserve it through the wedding and hotel sequence.
  4. Take at least one cab ride and pay the fare; the first completed ride awards one point. Never offer the driver wine.

Casino, disco, and Fawn

  1. At the casino entrance, buy the apple from the vendor. In the hotel lobby, take the disco pass from the ashtray.
  2. Gamble until Larry can preserve at least $200 beyond remaining travel and purchase costs. Slot-machine and blackjack outcomes do not award score, but they gate Fawn’s $100 request and the chapel’s independent $100 fee.
  3. Show the pass to the disco bouncer. Inside, trigger the comedian’s scored whoopie-cushion event, then sit with the blonde woman. Establish eye contact, ask her name, and dance with her.
  4. Give Fawn the rose, box of candy, and diamond ring. Give her $100 when she requests the honeymoon-suite money. She directs Larry to the wedding chapel.
  5. Outside the chapel, speak to the man in the trench coat for the one-point encounter. Inside, stand in the required position with Fawn, retain the ring state, and pay the minister the separate $100 fee. Complete the wedding ceremony.

Fawn and the rope

  1. Go to the casino hotel’s heart-marked honeymoon suite. Turn on the radio and listen through the Ajax Liquor advertisement, learning 555-8039 for one point.
  2. Return to an outside telephone, call Ajax, order wine, and specify the honeymoon suite. The completed delivery call is worth five points. Return to Fawn after the delivery.
  3. Pour the wine for Fawn and follow her instructions. She ties Larry to the bed, takes his visible wallet money, and leaves. Use the pocket knife to cut the ropes, then take the freed rope. Cutting and taking it award 10 and 3 points respectively.
  4. Return to Lefty’s fire escape. Tie the rope for safety, reach toward the remote window, use the hammer to break the glass, and take the bottle of pills. The hammer and pill acquisitions are worth 3 and 8 points.

Faith, Eve, and the penthouse

  1. Ride the hotel elevator to the guarded upper floor. Establish eye contact and speak with Faith, then give her the pills. After she consumes them and leaves, press the previously guarded unlabeled desk button. The two actions award five points each and open the private penthouse elevator.
  2. Enter the penthouse and explore the bedroom closet before completing the rooftop encounter. Take the inflatable doll, inflate it, and use it as the parser permits. These steps award 5, 5, and 8 points.
  3. Follow the gurgling sound to the rooftop spa. Enter the water, establish contact with Eve, and give her the apple. The completed apple sequence awards 15 points. Follow Eve from the spa into the penthouse bedroom.
  4. Complete the bedroom sequence. Logic 45 awards the final 25 points and enters the congratulatory terminal state at 222 of 222.

Score Ledger

Every positive score site is included exactly once.

ChainPoints
First completed cab ride1
Read centerfold and dispose of used prophylactic2
Lefty’s restroom: sit, password graffiti, ring6
Hallway rose and whiskey-for-remote exchange3
Carry Lefty’s whiskey1
Remote-control television and bypass pimp11
Candy, protected encounter, and first completion23
Fire-escape hammer and pills11
Store wine, magazine, and prophylactic purchase6
Telephone number, drunk exchange, and four call events23
Casino-lobby disco pass and bouncer admission6
Lounge comedy event1
Sit and dance with Fawn6
Eye contact, name, three gifts, and Fawn’s money24
Apple vendor and chapel stranger4
Wedding ceremony12
Radio advertisement, rope escape, and rope acquisition14
Faith’s pills and penthouse control10
Eve’s apple sequence15
Doll acquisition, inflation, use, and final encounter43
Total222

Failure and Dead-End Map

  • Failing the adult quiz prevents play from starting.
  • Sunrise ends the game while Larry remains unsuccessful.
  • Losing all casino money leaves Larry unable to pay travel and story gates; the zero-money sequence ends with Larry on skid row.
  • Giving wine to the cab driver causes a fatal crash.
  • Shoplifting prompts the store clerk to shoot Larry.
  • Omitting the prophylactic makes the first encounter unsafe and prevents the protected-item score chain.
  • Drinking the hallway whiskey or discarding Fawn’s gifts blocks later item dependencies.
  • Entering Fawn’s suite without the pocket knife leaves Larry tied permanently.
  • Missing the radio advertisement leaves no in-game source for Ajax’s number.
  • Using the aphrodisiac pills on Larry produces an unrelated fatal/failure branch; they must be given to Faith.
  • Touching Faith’s button before distracting her causes her to shoot Larry.

Replay Work Remaining

  1. Extract the randomized adult-question answer table into a deterministic replay helper.
  2. Choose and validate a bounded casino strategy that reliably finances the route before sunrise.
  3. Record exact parser strings for store options, calls, cab destinations, Fawn’s conversation, Ajax delivery, and the final bedroom sequence.
  4. Map movement coordinates and timing around the hooker’s bed, fire escape, disco, elevator, Faith’s desk, spa, and penthouse bedroom.
  5. Run the complete route under interpreter 2.440 and package room, inventory, cash, score, and terminal-state checkpoints as a conformance scenario.

The Black Cauldron Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a maximum- score winning playthrough for the local BC evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived only from the game’s logic, messages, vocabulary, objects, pictures, and views. No external walkthrough or AGI documentation was consulted.

The result is currently a static candidate route. The declared 230-point maximum, all score alternatives, inventory dependencies, major transitions, and terminal branches reconcile exactly. Exact movement, action-menu input, hazard timing, and a complete original-interpreter replay remain to be done.

Evidence Method

The reusable playthrough index can be generated with:

AGI_GAME_DIR=games/BC \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/bc/index.json

The game identifies itself as The Black Cauldron, version 2.00 dated June 14, 1987, and uses interpreter 2.439. Its split directories contain 85 readable logic resources, 69 pictures, 178 views, and 28 sounds. The index found 21 parser predicates, 213 explicit room switches, 61 inventory mutations, 26 inventory slots, and 40 positive score operations. No present logic failed to decode.

Logic 107 assigns 230 as the maximum score. The 40 award sites sum to 463 when mutually exclusive branches are counted together. Direct condition and state analysis reduces that total to exactly 230:

  • feeding Hen Wen corn or gruel shares one five-point state;
  • two ledge directions, two mountain faces, and two castle-road screens share their respective five-, six-, and five-point completion states;
  • the wagon and window castle entrances share one 18-point state;
  • four methods of getting Hen Wen to safety are alternatives worth 20 points;
  • three sword-assisted castle outcomes share one 13-point state;
  • four dungeon escape discoveries share one six-point state;
  • three first meetings with Eilonwy share one ten-point state;
  • an apple or cookies makes Gurgi a friend for the same ten points;
  • the three secret-chamber sacrifices/resolutions are alternatives; and
  • the two final rewards are alternate 15-point endings.

All 69 present pictures decoded in a qualitative sweep with patterned brushes disabled. The resulting sheet corroborates Caer Dallben, the forest and river, the Eagle Mountains, the castle exterior and interior, its dungeon and hidden passages, the Fair Folk dwellings, Morva Marsh, and the witches’ house. Because the brush-disabled renders are not pixel-conformance evidence, they are used only to check geography and scene identity. A later audit maps BC’s v2 mask and pointer tables at 0x1575 and 0x1595; this does not retroactively turn the historical disabled-brush sheet into conformance evidence.

Story and Winning State

Taran begins as Assistant Pig-Keeper at Caer Dallben. Hen Wen’s visions could reveal the Black Cauldron to the Horned King, so Dallben sends Taran to place her with the Fair Folk. The maximum route also befriends Gurgi, enters the Horned King’s castle, explores its dungeon and passages, frees Fflewddur Fflam, finds the magic sword Dyrnwyn, reaches the Fair Folk king, and trades Dyrnwyn to the witches for the Black Cauldron.

The cauldron ultimately reaches the Horned King’s secret chamber. In the selected resolution Taran holds the Fair Folk’s mirror before the Horned King. The king sees his evil self, leaps into the cauldron, and destroys himself and the Cauldron-Born threat. The witches then offer Taran several rewards for the now-powerless cauldron. Selecting the returned magic sword adds the final 15 points and enters ending logic 71.

Logic 71 computes a congratulatory threshold of maximum minus 15, or 215. The selected reward raises the score from 215 to 230 of 230, so the ending displays Congratulations!! You've played a tremendous game!!. This is the maximum-score terminal state used by this analysis.

Global Constraints

  1. Feed Hen Wen either corn or gruel before beginning the quest. The two actions are alternative realizations of the same five-point award.
  2. Take the knapsack, rope, water flask, bread, and apple from Dallben’s area. Hunger and thirst progress during play and can become terminal; carry food, refill the flask only from safe water, and eat and drink before the final warnings expire.
  3. Take the lute from the forest tree and the dagger from the no-trespassing sign. Preserve one instrument for King Eiddileg, the dagger for vines or ropes, and Dyrnwyn for the witches.
  4. Befriend Gurgi with either the apple or Gwystyl’s cookies. The two gifts are alternatives and both set the state needed for later Gurgi scenes.
  5. Place Hen Wen with the Fair Folk exactly once. The selected route uses Gwystyl; the drawbridge, parapet, and Eilonwy routes are alternate 20-point solutions and cannot be added to it.
  6. Learn and retain Gwystyl’s password, bmmpxf. It opens the concealed waterfall route to the Fair Folk realm.
  7. Give either the lute or Fflewddur’s harp to King Eiddileg. Both gifts enter the same ten-point branch and yield the magic mirror and flying dust.
  8. Keep the mirror. Offering it to the witches prevents transformation but is insufficient to buy the cauldron; Dyrnwyn completes that bargain. The mirror is then the highest-valued secret-chamber resolution.
  9. Obtain only one award from each shared mountain, castle-entry, dungeon-clue, and Eilonwy-introduction group. Revisiting their alternate locations does not increase the score.
  10. Do not accept the witches’ book, gold, shield, or armor endings. They end Taran’s adventure below the selected maximum. After the Horned King is defeated, continue rejecting offers until Dyrnwyn is offered.

Candidate Route

Caer Dallben and the Fair Folk way station

  1. At Dallben’s cottage, take the knapsack, water flask, bread, apple, and gruel. At Hen Wen’s shed, open the attached shed and take the corn. Feed Hen Wen either the corn or gruel for five points, then return to Dallben so her vision starts the quest. Take the rope Dallben supplies.
  2. Search the nearby world before committing to the long journey. Take the food wallet from beneath the bridge, the lute from the hollow tree, and the dagger holding the no-trespassing sign.
  3. Meet Gurgi and give him the apple. He becomes Taran’s friend for ten points. The cookies found shortly afterwards are an equivalent alternative and need not be consumed for score.
  4. Lead Hen Wen through the overgrown briar path. Reaching the concealed clearing awards four points. Enter Gwystyl’s way station and let him take Hen Wen through the Fair Folk passage. His conversation awards 20 points, confirms her safety, and supplies the password bmmpxf. Open his cupboard and take the cookies if they are needed as food.

Fair Folk and the Eagle Mountains

  1. Reach the waterfall, use the magic word, and enter the revealed cave. The descent reaches King Eiddileg’s hidden realm and awards 13 points.
  2. When Eiddileg asks for proof that Taran represents Dallben, give him the lute. The gift awards ten points. Keep the magic mirror and flying dust that he supplies.
  3. Travel toward the Eagle Mountains. At the vertical rock wall, use the rope on the dead branch and complete one scored ledge transition. Continue across one of the paired mountain-face routes, then onto one of the paired castle road screens. The three shared groups award 5, 6, and 5 points, in addition to the five-point first arrival at the rock-wall sequence.
  4. Keep the dagger usable while crossing the mountain. Unsafe reaches, letting go of the rope at the wrong time, or stepping from the ledge cause a fatal fall; scraping the dagger on rock also dulls it.

Castle, dungeon, and Dyrnwyn

  1. At the castle approach, hide in the henchman’s wagon. Riding through the gate awards the selected 18-point castle-entry branch. Avoid the moat and alligators.
  2. Explore the courtyard, halls, and dungeon. Take the ring of keys, then use it to open the locked cells and free Fflewddur Fflam. Freeing him awards nine points and makes his harp available, although the selected route has already given Eiddileg the lute.
  3. Enter the prison-cell and hidden-passage sequence. Use the tin cup against the door or floor to expose the loose flagstone; this is the selected six-point dungeon-clue branch. Meet Eilonwy through that opening for the single ten-point introduction award and follow her into the passages.
  4. Push the loose wall blocks until the route into the burial chamber opens, earning ten points. In the king’s tomb, take Dyrnwyn from the carved stone king for eight points. Preserve the sword for Morva Marsh.
  5. Return through the dungeon and castle. Use Dyrnwyn on the drawbridge chain, or leave by the old window while carrying it, to claim the single 13-point sword-assisted escape award. The selected replay should use one branch and record it explicitly; the shared flag prevents scoring both.

Morva Marsh and the Black Cauldron

  1. Cross the swamp by jumping only onto stable rocks and logs. Reach the hidden area outside the witches’ house for the 15-point Morva arrival award.
  2. Enter the house and confront Orddu, Orwen, and Orgoch. The mirror prevents the immediate frog ending but does not buy the cauldron. Offer Dyrnwyn when the item choice becomes available. The witches accept the sword, award 18 points, and place the Black Cauldron outside.
  3. Keep the cauldron from the gwythaint as long as the current sequence permits. The story eventually carries the cauldron toward the Horned King’s castle; follow the resulting route back to the secret chamber.

Horned King and maximum ending

  1. In the secret chamber, act before too many skeletons emerge. Select and use the magic mirror on the Horned King. He sees his true nature and leaps into the cauldron. This is the selected 25-point resolution; jumping personally or allowing Gurgi to sacrifice himself are alternate score branches.
  2. Survive the castle’s collapse and the moat escape. The ending returns Taran and the powerless cauldron to the lake where the witches bargain for it.
  3. Reject the book of knowledge, pot of gold, warrior’s shield, and suit of armor. Accept Dyrnwyn when the witches finally offer to return the magic sword. This awards the final 15 points and enters the congratulatory ending at 230 of 230.

Score Ledger

The ledger deliberately selects one member of every shared alternative group.

ChainPoints
Feed Hen Wen corn or gruel5
Reach Gwystyl’s concealed clearing4
Deliver Hen Wen to Gwystyl20
Befriend Gurgi with apple or cookies10
Enter King Eiddileg’s realm13
Give Eiddileg the lute or harp10
Rock-wall arrival and ledge traversal10
Complete one mountain-face route6
Complete one castle-road route5
Enter the castle by wagon18
Obtain one dungeon escape clue6
Meet Eilonwy10
Open the hidden passage10
Free Fflewddur Fflam9
Take Dyrnwyn8
Complete one sword-assisted castle outcome13
Reach the hidden Morva area15
Trade Dyrnwyn for the Black Cauldron18
Defeat the Horned King with the mirror25
Recover Dyrnwyn in the final bargain15
Total230

Alternate Winning Branches

The 230-point arithmetic is not tied to every physical choice above. The logic deliberately makes several routes score-equivalent:

  • Hen Wen can reach safety through Gwystyl, the castle drawbridge, the parapet, or Eilonwy’s tunnel; each realization contributes 20 points.
  • The wagon and old-window castle entries share the 18-point award.
  • Multiple dungeon discoveries can reveal an escape route, but the first is worth six points and suppresses the rest.
  • Eilonwy can first appear in three nearby passage/cell contexts; only the first meeting awards ten points.
  • The drawbridge, window, and secret-chamber sword checks share one 13-point state.
  • In the secret chamber, the mirror and Taran’s own sacrifice are worth 25 points, while Gurgi’s sacrifice is worth 20. Lower-valued resolution paths require a compensating story branch or finish below the maximum.
  • The final 15-point reward is either Dyrnwyn when Gurgi lives or Gurgi’s return after his sacrifice. These outcomes are mutually exclusive.

These alternatives should eventually become separate replay scenarios. The selected route above is useful because its score ledger is simple and keeps both Gurgi and Taran alive.

Failure and Dead-End Map

  • Starvation and dehydration become terminal if their warning sequences are ignored. Rapids can also tear away the flask or drown Taran.
  • Leaving Hen Wen exposed allows a gwythaint or the Horned King’s forces to capture her. If she reveals the cauldron’s location, evil wins.
  • Bog tiles, unsafe ledge moves, rope mistakes, mountain falls, and the castle moat kill Taran immediately.
  • Entering the castle openly, waiting near henchmen, or failing timed escapes leads to capture. Some captures are recoverable dungeon routes; others let the Horned King win.
  • The cook, throne-room observers, gwythaints, falling stones, alligators, and released Cauldron-Born impose movement or timing failures.
  • Releasing the witches’ prisoners without a viable bargain can transform Taran into a frog or trap him in the chest.
  • Accepting a lesser final reward is a terminal but non-maximum conclusion.
  • Allowing too many skeletons to emerge in the secret chamber ends the game, so the mirror action must be selected promptly.

Replay Work Remaining

  1. Record exact action-menu and active-object input for every non-movement step, especially feeding, the waterfall password, dungeon interactions, the witches’ bargain, mirror use, and final reward selection.
  2. Resolve the exact movement coordinates and timing for swamp jumps, rope and ledge traversal, wagon entry, dungeon routes, castle escape, and the secret chamber.
  3. Validate that the selected ordering can collect every common score group without an unnoticed phase-state exclusion; checkpoint after 5, 39, 62, 83, 157, 190, 215, and 230 points.
  4. Run the complete route under interpreter 2.439 with the patched VGA BIOS and capture score, room, inventory, Hen Wen/Gurgi state, and ending frames.
  5. Package the validated input stream and checkpoints as a deterministic game-level compatibility scenario, then add shorter replays for the score-equivalent rescue and ending branches.

Mixed-Up Mother Goose Playthrough Analysis

This chapter records a game-specific, clean-room reconstruction of a complete winning playthrough for the local MG evidence set. It is intended to become a repeatable conformance scenario, not part of the portable AGI engine specification. The route was derived only from the game’s logic, messages, pictures, views, sounds, and state transitions. No external walkthrough or AGI documentation was consulted.

Unlike the other games analyzed so far, Mixed-Up Mother Goose does not use a traditional adventure score or inventory list. It randomly distributes nursery-rhyme props around its map, lets the player carry one prop at a time, and counts completed rhymes. A winning game completes all 18 of 18 rhymes. The result below is a complete static strategy, but exact movement and a fixed randomized starting layout still require original-interpreter replay.

Evidence Method

The reusable logic index can be generated with:

AGI_GAME_DIR=games/MG \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/mg/index.json

The selected game identifies itself as Mixed-Up Mother Goose and uses interpreter 2.915. Its split directories contain 73 readable logic resources, 49 pictures, 100 views, and 53 sounds. The index found 159 explicit room switches and only one named inventory-table entry, ?; the game implements its portable props through custom variables and animated objects instead of the ordinary AGI inventory table. No present logic resource failed to decode.

Logic 0 assigns 18 as the displayed maximum and initializes the ordinary score variable to zero. It increments that variable once after each successfully completed rhyme animation. When the value reaches 18, it starts the completion sequence in logic 102. Thus 18 is both the maximum displayed value and the number of solved rhymes, rather than a sum of differently valued puzzle awards.

All 49 present pictures decoded in a qualitative sweep with patterned brushes disabled. They corroborate the outdoor grid, cottages, castle, shoe house, pumpkin house, school, barn and bedrooms, plus the cloud-framed finale. These renders are geographical evidence rather than pixel-conformance evidence.

Runtime Game Model

Map

Rooms 1 through 35 form a regular outdoor grid of seven columns and five rows:

 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 32 33 34 35

The edges generally connect north, east, south, and west to the corresponding neighbor. Nine special interiors or enclosed scenes branch from this grid:

ExteriorAttached room
437
1036
1238
1339
1540
2144
2441
2643
3242

The exact doorway trigger is positional, so a replay must record coordinates as well as room numbers.

Randomized props

A new game creates a shuffled placement of twenty props. Logic 0 repeatedly chooses eligible rooms, rejects occupied or disallowed combinations, and stores one prop in each accepted room. Consequently, there is no single fixed sequence such as “go to room 8 for the pail.” The player must explore the map, recognize visible props, and either deliver each immediately or remember its room.

The player carries at most one prop. Approaching a loose prop transfers it into the carried-prop state; approaching the correct rhyme character with that prop starts the corresponding completion logic. A wrong prop does not increment the completion count. Most successful deliveries consume one prop. Old King Cole is the exception: his one rhyme requires three deliveries in a fixed sequence.

Completion protocol

Each correct final delivery performs the same observable sequence:

  1. Player control is suspended and the relevant rhyme animation begins.
  2. The full nursery rhyme is displayed while its sound and character animation run.
  3. The rhyme logic signals that its animation has finished.
  4. Logic 0 waits for the common sound/animation completion state, increments the solved-rhyme count exactly once, clears the temporary carried-prop and interaction state, and returns control.
  5. After the eighteenth increment, normal exploration is replaced by the finale.

This sequencing matters to a compatible implementation: the counter changes after the completion presentation, not at the first moment the correct prop is recognized.

Rhyme and Prop Table

The complete winning condition is one completion for every row. The numeric prop identifiers are included as evidence labels for this game-specific route; they are not portable AGI inventory numbers.

Recipient or rhymeRoom logicRequired propProp id
Jack and Jill1Pail26
Little Tommy Tucker3Breadknife20
Humpty Dumpty5Ladder29
Cat and the Fiddle7Fiddle18
Little Miss Muffet9Tuffet12
Peter, Peter, Pumpkin-Eater12Wife11
Mary Had a Little Lamb13Lamb17
Crooked Man15Sixpence28
Ride a Cockhorse18White horse30
Old Woman Who Lived in a Shoe21Broth24
Little Bo Peep23Sheep15
Where Has My Little Dog Gone?27Dog16
Mary, Mary, Quite Contrary31Watering can27
Jack Be Nimble33Candlestick25
Jack Sprat36Platter21
Old King Cole, stage 137Pipe22
Old King Cole, stage 237Bowl23
Old King Cole, final stage37Fiddlers three14
Hickory, Dickory, Dock41Mouse13
Little Jack Horner43Pie19

The table has twenty prop rows but only eighteen completed rhymes because the three Old King Cole rows contribute one completion together. His logic records the pipe first, accepts the bowl only after the pipe, and accepts the fiddlers only after both earlier stages. Delivering the fiddlers in the final state starts logic 87 and contributes the single Old King Cole increment.

Candidate Winning Strategy

Start and survey

  1. Start a new character, enter a name, and choose one of the available player appearances. A new character causes the prop layout to be randomized.
  2. Traverse rooms 1 through 35 in a serpentine sweep: move east across one row, descend, move west across the next, and continue. Enter each of the nine attached rooms while passing its exterior.
  3. Record every visible portable prop and its room. The twenty-prop table above supplies the eventual destination for each. Also note the fixed recipient rooms; their requests identify missing rhyme components on screen.

Delivery loop

  1. Choose any unsolved recipient other than Old King Cole, travel to the room containing its prop, approach the prop to carry it, then travel to the recipient room and approach the character.
  2. Wait for the complete rhyme animation and sound. Do not begin the next movement sequence until normal control returns and the displayed count has advanced.
  3. Repeat for the 17 single-completion rhymes. The order is otherwise free; completion logic is keyed by recipient and carried prop rather than a global story phase.
  4. For Old King Cole in room 37, deliver the pipe, then the bowl, then the fiddlers three. The first two deliveries update his staged request but do not increase the solved-rhyme count. The fiddlers trigger the full rhyme and the one associated increment.
  5. If a prop’s original room is forgotten, repeat the systematic map sweep. Correctly completed props no longer participate in later loose-prop searches, so the remaining set becomes progressively smaller.

Terminal sequence

After the eighteenth rhyme animation, logic 0 observes a solved count of 18, suspends normal control, and starts the completion state. Logic 102 assembles the nursery-rhyme cast into a multi-stage celebration, displays thanks using the chosen player name, transitions to the going-home scene, and finally displays:

Mother Goose and her design team hope you enjoyed Mixed-Up Mother Goose. Congratulations on a job well done!

The finale then restores the status line but disables the ordinary save, restore, restart, display, joystick, and speed menu entries. The congratulatory scene with 18 of 18 completed rhymes is the winning terminal state used by this analysis.

Completion Ledger

Completion classRhymesCounter increase
Single-prop rhymes1717
Old King Cole after pipe, bowl, and fiddlers11
Total1818

There are no higher-valued actions or optional bonus points. The conventional one-point increment found by the generic index is the shared post-animation increment in logic 0, executed once for each completed rhyme.

Failure and Recovery Model

  • The decoded game contains no adventure-style death sequence or negative score operation. Exploration can be repeated indefinitely.
  • Taking a prop to the wrong recipient does not enter that recipient’s completion branch and does not increase the solved count.
  • The one-prop carrying limit means another prop cannot be collected until the current one is delivered or otherwise released through the normal object interaction.
  • Old King Cole cannot be completed out of order. The bowl depends on the pipe stage, and the fiddlers depend on both earlier stages.
  • Stopping during a completion animation can make an automated replay lose synchronization even though game state is not lost. A harness must wait for the count increment or restored player control.
  • Save and restore provide recovery from an interrupted survey. Because the randomized placement belongs to game state, a fixed save is also the simplest way to make the eventual compatibility replay deterministic.

Replay Work Remaining

  1. Create one original-interpreter save immediately after randomized placement and record the resulting prop-to-room map without committing the private save.
  2. Record exact coordinates for collecting every prop, entering all nine attached rooms, and activating every recipient.
  3. Capture checkpoints after each count increment, with special checks that Old King Cole remains unchanged after pipe and bowl and advances only after the fiddlers.
  4. Verify wrong-recipient and repeated-recipient behavior with short targeted replays.
  5. Run the complete 18-rhyme route under interpreter 2.915 and package its input stream, randomized-state description, counter sequence, and terminal frames as a deterministic game-level compatibility scenario.

Manhunter: New York Playthrough Analysis

This chapter is a fresh clean-room reconstruction of a winning route through the replacement local MH1 evidence set. It was derived from the current game’s logic, messages, vocabulary, object list, pictures, and views. It does not reuse the earlier analysis of the incomplete copy, and no external walkthrough or AGI documentation was consulted.

The result is a static route specification, not yet a replay script. The assignment order, required portable objects, late-game computer and ship chains, and terminal predicate are source-proven. Exact movement through every arcade sequence and the complete original-interpreter input stream still need dynamic reconstruction.

Evidence Method

The reusable index for this analysis was generated with:

AGI_GAME_DIR=games/MH1 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/mh1-replacement/index.json

The selected game uses interpreter 3.002.102 and the v3 combined resource layout. All 66 present logic resources decode. The game also contains 237 pictures, 138 views, and 99 valid script-addressable sounds. Every resource reference made by readable logic resolves. Two malformed sound-directory tail entries are not referenced by any script and are irrelevant to the route.

The inventory table contains 28 slots. The route analysis traced every inventory mutation, every constant room switch, the travel map, the assignment phase variable, all recognized report names, and every incoming edge to the ending logic. Representative ending pictures were rendered locally to check scene identity; they show the antagonist, ship explosions, and the final aftermath, but the logic remains the authority for ordering.

No logic assigns or changes a conventional numerical score or maximum-score value. For this game, “maximum score” therefore contributes no additional criterion: winning means reaching the unique continuation ending without entering a retry or death branch.

Story and Terminal State

The player is a Manhunter in Orb-occupied New York. The Orb assignments begin with an explosion at Bellevue Hospital, continue with a missing maintenance robot at Grand Central Terminal and a dead Orb at Greenwood Cemetery, and end with illegal access to the Alliance computer. The investigations expose a resistance plot, Phil’s double role, four ship modules, and the means to use an Orb ship against four targets.

Logic 162 is the winning terminal sequence. It has no outgoing room transition and eventually displays To be continued.... Its only ordinary incoming edge is from the ship/bomb logic, logic 151. That edge is taken only when all of the following are true:

  • the bomb flight is in its completion phase;
  • the late-story authorization flag is set;
  • the flight state has reached stage 12; and
  • all four distinct target-completion flags are set.

The four flags are set by separate bomb-drop regions. A single successful drop cannot satisfy more than its own region, so the terminal predicate proves that the winning flight requires all four targets.

Global Route Constraints

  1. Keep the MAD, the game’s tracking and travel interface. Major assignments are reached through its map rather than a single continuous room graph.
  2. Preserve the Data Card until the report sequence has consumed its clue. The card states: Destroy the Lady, Before They are Ready and identifies Phil as a double agent and an Eye.
  3. Collect Module A, Module B, Module C, and Module D. The cockpit installation logic removes each module from inventory at a separate control position; all four positions are therefore genuine prerequisites.
  4. Preserve the key, crowbar, badges, medallion, and accumulated keycards until their local access checks have been passed. The keycard inventory is encoded as mutually exclusive “One Keycard” through “Thirteen Keycards” entries, not thirteen independently carried cards.
  5. Treat deaths as retry branches rather than forward progress. Many fatal scenes enter a shared rewind logic that explicitly returns to a few minutes before the mistake.
  6. Complete all four bombing regions. Leaving the ship sequence after fewer than four successful regions cannot reach logic 162.

Assignment and Report State

The assignment phase is a four-state progression. The observable assignment messages are:

PhaseAssignment
1Investigate the explosion at Bellevue Hospital
2Investigate the missing maintenance robot at Grand Central Terminal
3Investigate the dead Orb at Greenwood Cemetery
4Investigate illegal access at the Alliance computer

Completing the first report advances the phase from 1 to 2. Later handoffs advance phases 2 to 3 and 3 to 4. The final assignment message promises a transfer to Chicago after the investigation, but the actual route instead continues into the Alliance computer and ship climax.

The report input uses the same full-name parser as the MAD occupant database. Recognized names include Harvey Osborne, Anna Osborne, Harold Jones, Phillipe Cook, Louis Redman, and Reno Davis, with spelling variants recorded in the game vocabulary. Recognition alone is not proof that a name is a correct answer for a particular report: recognized names set separate evidence flags, and the surrounding assignment state decides their consequence. The complete name order for an automated replay remains a dynamic validation item.

Candidate Winning Route

Opening investigation and Bellevue

  1. Start the game, retain the MAD, and accept the Bellevue Hospital assignment.
  2. Use the MAD’s occupant records, notes, tracking display, and travel map to inspect the named people and assignment locations. The database recognizes the suspects listed above and can track available targets.
  3. Follow the Bellevue investigation through its signal and room puzzles. Obtain the key in logic 106 when the close-up prompt makes it available.
  4. Complete the first reporting cutscene. The report consumes typed names and returns the player home for new orders; successful progression changes the assignment phase to the missing-robot investigation.

Grand Central and the maintenance robot

  1. Travel to Grand Central Terminal and follow the maintenance-robot trail. The scripts warn that direct tampering makes the robot self-destruct.
  2. Complete the associated movement and close-up sequences rather than waiting through the fatal branches.
  3. Recover Module B from the robot-remains area in logic 135. The module is placed in inventory only after the appropriate action prompt and local state are both active.
  4. Finish the report/assignment handoff that advances the story toward the Greenwood Cemetery case.

Greenwood, Coney Island, and the city evidence chain

  1. Investigate Greenwood Cemetery and the dead Orb. Continue following MAD tracking targets and the travel destinations opened by the current phase.
  2. Obtain the Data Card and read its complete clue before reporting. The report sequence can request up to three full names in this phase.
  3. At Coney Island, complete the carnival games needed by the prize logic. The scripts expose Balloons and Darts, Kewpie Doll Baseball, and Rings and Bottles, and make the Data Card, stuffed Orb items, and related prizes part of this chain.
  4. In Central Park, use the MAD-derived route through the mined area. Wrong paths trigger the mine death; the valid path leads past named landmarks. Take the crowbar when its action becomes available.
  5. Complete the keycard/medallion and badge-selection chains. The game replaces the current keycard-count item as the count changes and later offers badge and badges inventory states for access checks.

Collect the four ship modules

The modules are acquired in four different room logics and installed at four different cockpit controls:

ModuleAcquisition evidenceRequired later behavior
ALogic 111, after the candle/match room sequenceRemoved at its cockpit position
BLogic 135, in the maintenance-robot chainRemoved at its cockpit position
CLogic 113, after the button/close-up sequenceRemoved at its cockpit position
DLogic 150, after the handle and guard-robot sequenceRemoved at its cockpit position

The acquisition order can vary where the travel map permits it, but cockpit completion cannot be faked by carrying only one or two modules. The installer tests and consumes each named inventory entry independently.

Alliance computer

  1. Reach the illegal Alliance computer after the fourth assignment opens.
  2. Navigate the restricted security and operations interface. Its visible branches include Repair, Supply, Air Defense, Ground Patrol, Security, Operation, Site Selector, transmitter control, and ship checks.
  3. Complete the repair and authorization states needed to make the main computer operational. The interface reports four bombs loaded and exposes the ship only after its internal state has advanced.
  4. Survive the room-security failures. Incorrect restricted access reaches a fatal security response rather than a useful alternate route.

Cockpit and bombing flight

  1. Enter the cockpit and install Modules A through D at their four matching control positions. The visible controls use button and slider prompts.
  2. Complete the keypad/ship-control sequence. A wrong control can enter a death or retry branch; the valid sequence reaches logic 151.
  3. Fly over each target region and press Enter to drop one bomb while the ship is inside the region’s valid bounds. Each successful region sets one of the four completion flags.
  4. Continue until all four completion flags are set and the flight reaches stage 12. The script then switches directly to logic 162.
  5. Allow the ending cutscene to complete. To be continued... is the unique winning terminal marker for this evidence set.

Required Inventory Ledger

Item or stateRoute role
MADOccupant records, tracking, notes, and travel
KeyEarly locked-room access
Data CardReport clue and suspect-name sequence
CrowbarCentral Park/later physical access chain
Keycard count and medallionProgressive access state
Badge or badgesSelection and access chain
Modules A, B, C, DFour independent cockpit installations

Stuffed Orb and Stuffed Orbs are carnival prize states. Their exact necessity on the shortest winning route remains to be confirmed by replay; they should not be omitted from an exploratory capture merely because the terminal logic does not test them directly.

Deaths, Retries, and Dead Ends

  • Tampering with the maintenance robot incorrectly allows it to self-destruct.
  • Losing timed action scenes, knife/arcade sequences, or guard encounters enters the shared rewind presentation.
  • Wrong paths in Central Park detonate mines.
  • Incorrect badge, keycard, password, security, or cockpit choices can kill the player or return to an earlier checkpoint.
  • The Alliance computer can eliminate an unauthorized user.
  • Incorrect ship keypad actions and missed bomb regions prevent the ending.
  • Reaching a visually advanced flight state with only one to three target flags is not a winning terminal state.

Replay Work Still Required

The static evidence is sufficient to define the terminal contract and the major dependency chain, but not yet a deterministic input file. A QEMU replay must still record:

  • exact movement coordinates and timing for each arcade room;
  • the accepted suspect-name order for every report phase;
  • the shortest valid carnival/keycard/badge route;
  • the Alliance computer menu selections and cockpit control order; and
  • the flight path and frame windows for the four bomb drops.

Those observations should refine this chapter without weakening the proven terminal predicate above.

Manhunter: San Francisco Playthrough Analysis

This chapter is a fresh clean-room reconstruction of a winning route through the replacement local MH2 evidence set. It was derived from the current game’s logic, messages, vocabulary, object list, pictures, and views. It does not reuse the earlier analysis of the incomplete copy, and no external walkthrough or AGI documentation was consulted.

The result is a static route specification. The complete replacement input now exposes the late game that was absent from the former copy: the Rat and Orb item chain, the lava/slave controller, helicopter sequence, final control-room puzzle, wraparound movement maze, and ending. Their state dependencies and terminal edge are source-proven. Exact movement and a complete original-interpreter replay remain future work.

Evidence Method

The reusable index for this analysis was generated with:

AGI_GAME_DIR=games/MH2 \
  python3 -B tools/logic_playthrough_index.py \
  --output build/playthrough/mh2-replacement/index.json

The selected game uses interpreter 3.002.149 and the v3 combined resource layout. All 96 present logic resources decode. The game also contains 248 pictures, 181 views, and 193 valid script-addressable sounds. Every resource reference made by readable logic resolves. Two unreferenced sound-directory tail entries select an absent volume and are inert for valid gameplay.

The analysis traced every inventory mutation, room switch, recognized full name, assignment phase, and incoming terminal edge. Representative ending pictures were rendered locally and corroborate San Francisco, the antagonist, the Orb ship, and the ending montage. They are qualitative scene checks; logic control flow supplies the ordering evidence.

No script assigns or changes a conventional numerical score or maximum-score value. Winning is therefore defined by the unique continuation ending rather than a points total.

Story and Terminal State

The story continues with a Manhunter operating in Orb-controlled San Francisco. Two explicit assignments direct the player to a burning boat at Pier 5 and a human displayed on the sign at Ghirardelli Square. The MAD database and tracker connect named occupants to Pier 5, Ghirardelli Square, Chinatown, and the Alliance computer. The investigation expands into Rat prizes and disguises, a historical Orb-on-a-Stick sequence, an Orb access card, captive humans, lava controls, a helicopter flight, and a final Orb facility.

Logic 187 is the winning terminal sequence. It has no outgoing room transition and displays To be continued.... Its only incoming edge is from logic 183. The edge requires:

  • the maze to be in active movement phase;
  • maze state 4;
  • player X strictly between 129 and 144; and
  • player Y less than 24.

Thus the player must reach the narrow top exit while the maze is in state 4. Merely touching the top boundary in another state wraps the player elsewhere and does not end the game.

Global Route Constraints

  1. Take both the ID Card and MAD in the opening sequence. The MAD provides the occupant database, tracker, notes, and travel interface used by the two assignments.
  2. Keep state-bearing evidence until its explicit use: the Rat’s Paw, ring, flask, Rat Mask 1, hatchet, statue, Orb Card, and Orb-on-a-Stick all have later consumption or access checks.
  3. Prize choices are not equivalent. Rat Mask 1 is explicitly consumed in the late chain; selecting only Rat Mask 2 does not satisfy that check.
  4. Preserve the Full Flask until the same late sequence that consumes Rat Mask
    1. Entering its dependent scene with a broken or missing flask reaches an explicit restore/death warning.
  5. Do not lose the Orb Card before the cell-access sequence. The game can remove and later reacquire it, but the access machine still requires the appropriate card state.
  6. Treat the many back up a few minutes branches as failures. They are useful retry checkpoints, not alternate progress paths.

Assignments and Name Input

The assignment controller advances a small phase value and presents two observable orders:

OrderAssignment
1Investigate the burning boat at Pier 5
2Investigate the human on the Ghirardelli Square sign

Reports request full names and parse them through the MAD occupant database. Recognized names are Peter Brown, Noah Goring, Mic Stone, Tad Timov, and Zac West, with spelling variants in the vocabulary. The records associate targets with the Bank of Canton, Ghirardelli Square, Pier 5, and the Alliance computer. As in the New York game, recognition and correctness are separate: individual names set evidence flags, while assignment phase decides whether a resulting branch advances or punishes the player. Exact report-answer order remains a replay validation item.

Candidate Winning Route

Opening and city investigations

  1. Start the game and take the ID Card and MAD in logic 124.
  2. Accept the Pier 5 assignment. Use the MAD records and tracker to inspect the available named occupants and travel to the tracked location.
  3. Complete the burning-boat investigation, collect its evidence, and return through the report sequence rather than a fatal or premature exit.
  4. Accept the Ghirardelli Square assignment and investigate the human on the sign. Continue through the report and home-order sequences until the later city locations become available.

Evidence and tool chain

The complete route draws on several city puzzles. Their proven inventory effects are:

ItemAcquisitionLater dependency
RingLogic 132 movement roomConsumed in logic 168
Rat’s PawLogic 133Consumed by the associated Rat chain
Empty FlaskEarlier city roomFilled to become Full Flask in logic 136
Rat Mask 1First row of the Three Aces prize logicConsumed in logic 164
HatchetLogic 165Used in logics 165 and 175
StatueLogic 128Consumed in the access chain in logic 176
Orb CardLogic 147Required and transformed around logics 176/179
Orb-on-a-StickLogic 170Central late-story evidence
  1. Complete the movement rooms that yield the ring and Rat’s Paw.
  2. Obtain the Empty Flask and fill it during the laboratory/evidence sequence, producing the Full Flask.
  3. Win the Three Aces game and choose Rat Mask 1 from the first prize row. The first row offers Flashlight, Rat Mask 1, and Key; the second offers Lantern, Rat Mask 2, and Medallion. Additional play may supply another prize, but Rat Mask 1 is the source-proven route requirement.
  4. Acquire the statue and Orb Card. The card scene presents Orb propaganda and a direct take-card action; retain the resulting access state.
  5. Follow the Rat Mask 1 and Full Flask branch in logic 164. Both items are removed by separate local states, proving that the scene requires more than simply entering the room.
  6. Take the hatchet in logic 165 and use it where the next sequences consume it. Follow the girl/inside transitions rather than leaving the chain.
  7. Use the ring in logic 168. That route switches directly to logic 170, where the historical July 2001 scene makes the Orb-on-a-Stick available.
  8. Take the Orb-on-a-Stick and continue through the outside/inside chain in logics 172 and 175.

Orb access and captive route

  1. Reach the cell-door access machine in logic 176. It displays SCANNING, then either ACCESS DENIED...ELIMINATE! or ACCESS APPROVED.
  2. Supply the required statue/card state. The logic removes the statue in one access phase and manipulates the Orb Card in later phases; an absent or wrong access item follows the elimination branch.
  3. Continue into the Orb facility rather than returning to an earlier city room. Subsequent rooms lead to the lava, robot, and captive-human control board.

Lava, robots, and slaves

Logic 178 implements a board with visible locations and states labelled Slavery, Hell - You are here, Freedom, Life, and The Digger. Controls move robots or slaves, open and close gates, and move lava between sections. The script rejects any attempt to move robots or slaves into a section flooded with lava.

  1. Use the destination sensors to relocate lava away from a section before moving occupants into it.
  2. Open only the gate needed for the current move, transfer the selected group, and close or reposition the gate before changing the lava layout.
  3. Continue until the board’s completion state enters logic 185. Numerous partial states return from logic 178 to logic 185; these are controller substeps rather than independent rooms.
  4. In the connected late rooms, recover the Orb Card state when offered and follow the transitions through logics 179, 180, and 181. Logic 180 warns the player to save before the stuck-handle sequence.

Helicopter and final facility

  1. Logic 181 feeds into the helicopter game in logic 161. Press Enter for more lift while steering clear of the tower. A collision displays You hit the tower!; the successful route displays You won!!! and continues.
  2. Return through the late controller in logic 185. Complete the button and limited-flash interactions until its phase reaches the transition to logic 186.
  3. Logic 186 arranges a row of seven controlled objects and tests a multi-step button sequence. Complete that sequence, then enter its lower target region; the script assigns the next room as logic 183.

Final wraparound maze

Logic 183 is a toroidal movement puzzle. The arrow directions change when the player crosses an edge:

Edge crossedIncoming directionsWrapped destination and new direction
Top1 or 2Bottom; 1 becomes 3, 2 becomes 4
Bottom3 or 4Top; 3 becomes 1, 4 becomes 2
Right1 or 3Left; 1 becomes 2, 3 becomes 4
Left4 or 2Right; 4 becomes 3, 2 becomes 1
  1. Use the arrow keys to traverse the wraparound board and advance its internal maze state to 4.
  2. In state 4, approach the top edge with X between 130 and 143 inclusive.
  3. Cross above Y 24. The script switches immediately to logic 187.
  4. Allow the ending montage to complete. To be continued... is the winning terminal marker.

Other Inventory and Optional Branches

The object table also contains a Dragon Note, Newspaper, Scroll, Laundry Receipt, Muzzle, Fang, Mallet, Driver’s License, Cloth, Camera, two letters, Matchbook, Walking Stick, Empty Gun, Flashlight, Lantern, Rat Mask 2, Key, and Medallion. Several are required in earlier local puzzles even though the final logic does not inspect them directly. The Matchbook is initialized or acquired in logic 180, and the Walking Stick has a direct acquisition action in the late-city chain.

Until a complete replay chooses and records every prize and side branch, these items should be classified as route candidates rather than discarded as optional solely from terminal reachability.

Deaths, Retries, and Dead Ends

  • Wrong report names can route to the shared failure/retry logic.
  • Movement rooms around the ring and Rat’s Paw contain fatal branches.
  • Entering the late chain without the intact Full Flask triggers an explicit warning that continuing will kill the player.
  • Choosing Rat Mask 2 instead of acquiring Rat Mask 1 leaves the source-proven late consumption check unsatisfied.
  • Failed Orb-card scanning produces ACCESS DENIED...ELIMINATE!.
  • Moving robots or slaves into lava is rejected; incorrect board states can strand the controller sequence.
  • The stuck-handle sequence explicitly warns the player to save.
  • Insufficient helicopter lift or lateral error hits the tower.
  • Wrong final-control button sequences do not enter the maze.
  • The maze’s top edge is not an exit before state 4, or outside X 130..143.

Replay Work Still Required

The replacement resource set proves a complete terminal route exists, but an automated conformance replay still needs:

  • exact report-name order for both assignments;
  • the shortest city movement path and necessary side-item selections;
  • the number of Three Aces wins and complete prize choice sequence;
  • the exact lava/gate/robot/slave solution;
  • helicopter steering and lift timing;
  • the seven-object button solution in logic 186; and
  • an arrow-key sequence that reaches maze state 4 and the narrow top exit.

QEMU observations should be used to resolve those inputs and to capture retry behavior, while the static predicates above remain the source of truth for what constitutes the winning terminal state.

Interpreter Controller

tools/interpreter_controller.py is a persistent, localhost-only controller for cycle-stepped runs of a private original interpreter under QEMU. It wraps QEMU’s QMP lifecycle/input/screenshot interface and its GDB remote memory and register interface behind JSON HTTP requests. The selected game is always explicit, and preparation copies it to a disposable disk under build/. Nothing under games/ is modified. The disposable SQ1.22 interpreter copy is patched to initialize its shared pseudorandom generator from the fixed seed 0x5eed rather than the BIOS tick count.

For the static-first analysis workflow, experiment design, failure diagnosis, and guidance on turning controller observations into a reusable state graph, see Developing and Validating Playthroughs.

Warning: this is not yet a generic AGI controller. Only its QMP/GDB and HTTP orchestration layers are intended to be version-neutral. The current executable anchor, hooks, stack returns, DS layout, object/inventory/logic decoders, logical-buffer interpretation, and modal screen oracle are validated only for SQ1.22’s AGI 2.917 build. A future profile requires fresh evidence for all of those assumptions, not merely a different executable hash.

The initial profile is limited deliberately to SQ1.22 and its observed 2.917 interpreter. Startup validates the SHA-256 of the locally decoded executable before any offsets are used. A mismatching interpreter is rejected rather than being treated as a compatible build.

Runtime control model

The controller discovers the paragraph-aligned loaded executable from a relocation-free byte signature. In the observed SQ1.22 run the executable base was physical 0x63a0 and DS was 0x102f. These are observations, not fixed launch assumptions.

The normal debugger hook is image 0x015b, at the start of each repeated interpreter cycle. QEMU pauses before the instruction executes, the controller reads coherent state, and an explicit API request steps off the hook and runs to the next hook. --capture-every-cycle writes an immutable bundle containing the screenshot, full state, state delta, trace events, and input transitions at every cycle stop. --capture-logical-buffers also stores the visual and priority channels from that same stop.

This QEMU real-mode GDB path honored only one execution breakpoint reliably in the live SQ1.22 experiment. The controller therefore does not leave the cycle, string-input, and modal hooks active simultaneously. It keeps the cycle hook active normally. If execution does not return because the current cycle has entered a blocking UI, the controller interrupts it and examines aligned near return addresses on the interpreter stack:

  • return 0x0df8 identifies the shared string editor reached from the visible edit loop at 0x0df2;
  • return 0x1d25 identifies the modal message wait reached from the visible loop at 0x1d1b.

The appropriate single UI hook is then selected. Accepting a string or dismissing a modal restores the cycle hook. This switching was confirmed with logic 69’s ordinary 0x73 string input, a normal room command that opened a modal message, and a return to cycle control after each UI was accepted.

Preparing and launching a session

After deleting build/, recreate the base image first. This also rebuilds the derived VGA option ROM used by the controller:

python3 -B tools/setup_freedos_image.py --force

Then create a disposable raw clone and qcow2 play disk. The RNG patch is generated during this command; it is not copied from an earlier build artifact:

python3 -B tools/interpreter_controller.py prepare \
  --base-image build/freedos/freedos.img \
  --game-dir games/SQ1.22 \
  --dos-game-dir SQ122 \
  --raw-output build/interpreter-controller/session/sq122.raw \
  --output build/interpreter-controller/session/sq122.qcow2

Preparation validates the pristine decoded interpreter hash and the original RNG seed instruction bytes before applying the patch. Rebuild play disks made with an older controller; runtime discovery rejects an interpreter that does not contain the fixed-seed instructions. The patch changes only initialization: the original state transition and output mixing remain intact, so random operations produce a repeatable sequence rather than one repeated value.

All four source-backed game-randomness callers use this shared generator: approach-motion recovery, random-motion direction, random-motion duration, and the random_range_to_var action used by game scripts. The other two BIOS timer reads in the executable bound startup display-adapter detection and do not feed the random state. Checkpoint restore naturally restores the generator state as part of the VM snapshot. Repeating the same cycle-relative inputs from the same checkpoint therefore repeats subsequent random outcomes.

Launch the persistent controller and visible Cocoa QEMU window:

python3 -B tools/interpreter_controller.py serve \
  --disk build/interpreter-controller/session/sq122.qcow2 \
  --game-dir games/SQ1.22 \
  --display cocoa,zoom-to-fit=on \
  --runtime-dir build/interpreter-controller/runtime \
  --capture-dir build/interpreter-controller/captures \
  --capture-every-cycle \
  --capture-logical-buffers \
  --port 8765

QEMU starts paused at reset. Resume it, wait for DOS, and use the bootstrap host-time input only to launch the interpreter:

curl -X POST http://127.0.0.1:8765/v1/vm/continue -d '{}'
sleep 5
curl -X POST http://127.0.0.1:8765/v1/input \
  -H 'Content-Type: application/json' \
  -d '{"action":"host_type","text":"cd \\sq122\nsierra\n"}'
sleep 2
curl -X POST http://127.0.0.1:8765/v1/instrument/discover \
  -H 'Content-Type: application/json' \
  -d '{"wait_for_hook":true}'

The DOS wait remains an explicit caller responsibility because firmware and disk startup are outside the interpreter cycle model. After discovery, cycle-relative input methods should be used.

Architecture and profile adapters

QMP/GDB lifecycle, transactions, traces, input reconciliation, movement, and recording are generic controller services. Interpreter packaging and memory semantics are owned by an explicit InterpreterAdapter. The current SQ122Adapter is responsible for validating/decrypting its selected input, classifying blocking stacks, decoding variables, flags, objects, inventory and logic-cache records, interpreting the logical screen, and supplying the modal screen oracle. It also owns the observed direction codes, movement keys, stop key, and default blocked-priority set. InterpreterSession no longer contains those profile-specific layouts or control values.

A future profile must register both an InterpreterProfile and an adapter. A hash-only profile is insufficient and fails before the VM is interpreted. GET /v1/profile reports the selected profile and adapter. It also reports the controller’s fixed randomness mode and seed.

HTTP surface

All endpoints bind to 127.0.0.1 by default. Mutating requests use JSON POST bodies; state and image requests use GET.

EndpointResult
GET /v1/stateFull coherent state, current logic/resume context, and held/pending key state
GET /v1/profile, /input/state, /traceAdapter identity, keyboard reconciliation state, or sequenced transition events
GET /v1/variables, /flags, /objects, /inventory, /logicsIndividual state families
GET /v1/picture/priority.ppm, /visual.ppmCurrent profile-decoded logical channels
GET /v1/screenshot.ppm, /screenshot.pngCurrent VGA output through QMP
GET /v1/dialogModal boxes, stable dialog ID, and interpreter-memory oracle
POST /v1/cycles/step, /run, /run-untilBasic cycle execution
POST /v1/cycles/run-until-guardedPredicate wait with invariants and abort predicates
POST /v1/inputDown/up/tap/type, bootstrap typing, release reconciliation, or release-all
POST /v1/semantic/commandSubmit one command-line string and Enter
POST /v1/string-prompt/submitSubmit shared-editor text and restore cycle control
POST /v1/dialog/dismissIdempotently dismiss a matching dialog instance
POST /v1/semantic/direction, /stop-movement, /waitDirection selection, explicit stop, or named state wait
POST /v1/movement/run-until, /waypointsGuarded single segment or multi-waypoint movement
POST /v1/movement/plan, /navigatePlan or execute a local priority-aware path
POST /v1/transactionsExecute an idempotent state-contract action
POST /v1/captures/cycleWrite a cycle-aligned recording bundle on demand
POST /v1/checkpoints, /checkpoints/restoreSave/restore VM, keyboard state, and active semantic hook
GET /v1/debugRegisters, stack, image-relative IP, and active hook
POST /v1/vm/quitQuit QEMU and shut down the server

GET /v1/trace accepts since and limit query parameters. Every event has a monotonic sequence, cycle, revision, kind, compact state, and details. Events cover semantic stops, state deltas, requested/delivered/deferred input, transactions, reconciliation, and checkpoints.

Predicates, guards, and transactions

Predicates address nested fields with dotted paths. Supported operators are eq, ne, lt, le, gt, ge, in, contains, between, truthy, and falsy, with recursive all, any, and not forms.

POST /v1/transactions is the preferred mutation interface. A transaction contains an optional precondition, one semantic action, acceptable postconditions, invariants, a cycle bound, and an idempotency key:

{
  "idempotency_key": "open-bay-once",
  "precondition": {
    "all": [
      {"path": "room", "op": "eq", "value": 6},
      {"path": "score", "op": "eq", "value": 8}
    ]
  },
  "action": {"type": "command", "text": "press open bay door"},
  "postconditions": [
    {
      "name": "door-state",
      "predicate": {"path": "variables.52", "op": "eq", "value": 1}
    },
    {
      "name": "score-award",
      "predicate": {"path": "score", "op": "eq", "value": 10}
    }
  ],
  "postcondition_mode": "all",
  "invariants": [
    {
      "name": "stay-in-room",
      "predicate": {"path": "room", "op": "eq", "value": 6}
    }
  ],
  "max_cycles": 100
}

The result separates status from outcome_certainty, lists every condition evaluation, includes compact start/final states and a semantic delta, and returns the trace slice. Reusing an idempotency key with the identical request returns the cached result without input. Reusing it with a different request returns idempotency_conflict. Restoring a checkpoint clears this cache because the game state has moved backward. The desired postcondition is checked before the precondition, so retrying an already-achieved operation remains a no-input already_satisfied result even when the transition’s original precondition no longer holds.

Actions include tap, key_down, key_up, release_all_keys, type_text, command, submit_string, dismiss_dialog, select_direction, stop_movement, move_until, waypoints, navigate_priority, wait_for_state, and wait_for_animation.

Reliable input and semantic UI operations

Physical key delivery and game outcome are separate. The controller tracks held_keys and pending_releases. If key-down completes an action and the VM reaches its next hook before QMP accepts key-up, the response is HTTP 200 with input_delivery.status = release_pending; it is not a false action failure. The next tap first makes bounded reconciliation attempts, and callers can use {"action":"reconcile"} or {"action":"release_all"} explicitly.

A transition that loses the race before key-down is reported as not_delivered. Transactions determine success from postconditions, so a blind retry is never required. Their outcome_certainty distinguishes an observed postcondition, a deferred release, and an unverified result.

GET /v1/dialog hashes only the detected dialog rectangle, excluding animated background pixels. Passing that dialog_id to /v1/dialog/dismiss makes the operation idempotent: an absent dialog is already_absent, while a different dialog is dialog_mismatch and receives no input.

Guarded movement and priority planning

movement/run-until preserves an already-active matching direction, because repeating it is a game-level stop toggle. It accepts invariants and positive abort predicates, then explicitly stops movement at the target or interruption. movement/waypoints composes cardinal segments with x_then_y or y_then_x ordering, per-segment cycle limits, coordinate tolerance, and optional initial-room preservation. Segment completion uses direction-aware crossing predicates rather than exact equality, so a step size greater than one cannot skip a coordinate forever.

movement/plan performs a breadth-first search over the live priority channel. Acceptance tests every pixel in ego’s baseline footprint, not only ego’s left coordinate. Priorities 0 and 1 are blocked by default; callers can override the set for a room-specific hypothesis. The result contains the complete pixel path and a compressed list of turns. movement/navigate executes those turns with room preservation and the same guards.

This planner is intentionally local to the current logical screen. It does not infer elevators, room transitions, dynamic object collision, horizons, or room-logic control overrides. Multi-room hypotheses should be supplied as explicit waypoint/transaction stages, and unexpected behavior should trip an invariant rather than trigger speculative movement.

Transition traces and cycle recordings

Every semantic stop records the compact state and a delta covering scalar state, changed variables and flags, and changed object fields. The state also contains the current logic-cache record and resume IP visible at the stop. Transactions record their own predicate evaluations. This provides useful logic context without claiming to trace every opcode or internal condition branch inside a cycle.

Cycle recording happens while QEMU remains stopped. Each directory under CAPTURE_DIR/cycles/ contains screen.png, cycle.json, and optionally visual.ppm and priority.ppm. cycle.json contains the full state, delta from the previous recorded cycle, and all trace/input events since that cycle. The controller builds each uniquely sequenced directory under a temporary name and publishes it atomically. Only after the bundle is complete is a row appended to cycles.jsonl, including the screenshot SHA-256. Capture sequence, cycle, and state-revision numbers make image/state pairing explicit and permit multiple immutable captures of the same semantic stop.

SQ1.22 state mapping

The SQ122Adapter uses the locally verified 2.917 data layout: variables at DS 0x0009, packed high-bit-first flags at 0x0109, object-table pointer globals at 0x0963/0x0965, inventory globals at 0x0969 through 0x096d, logic-cache globals at 0x096f and 0x0979, and the live logical-buffer segment at 0x1365. Object records are the observed 43-byte layout. The combined logical buffer is 160 by 168 bytes; its low nibble is visual color and its high nibble is priority/control.

The border detector is an independent screen oracle. It finds the observed red rectangular border with a predominantly white interior and does not rely only on interpreter memory.

Limits

The controller is a research tool, not a replacement engine. Only SQ1.22’s 2.917 adapter is implemented. State reads are coherent at semantic stops; cached state while the VM is running is explicitly marked stale. Captures, checkpoints, and prepared disks are disposable artifacts under build/. Checkpoint restore reinstalls the semantic breakpoint reason saved with the controller state before it accepts another mutation. This prevents a later modal or string interaction from leaving the restored VM paired with the wrong blocking hook.

QEMU’s observed real-mode debugger path still permits one reliable execution breakpoint. Stack-classified hook switching handles the known cycle, modal, and shared-string modes, but unknown blocking loops still require new profile evidence. Deferred key releases preserve every cycle boundary and remain visible until QMP accepts them; the controller does not remove breakpoints or run through unrecorded cycles merely to manufacture an input window.

QEMU VGA BIOS compatibility

Purpose

The full 16-color EGA target uses QEMU as an observation environment. QEMU’s bundled VGA BIOS does not reproduce one BIOS font-vector behavior required by the observed 2.936 interpreter, causing inverse text to appear as repetitions of one glyph. This chapter documents the fault, the evidence distinguishing it from game corruption, and the reproducible firmware workaround used by the test harnesses.

The workaround patches emulator firmware only. It does not alter a game, interpreter executable, resource, FreeDOS disk image, or clean-room behavioral specification.

Observable symptom

Ordinary white-on-black graphics text renders correctly. Black-on-white dialog text instead contains repeated copies of one symbol. Pictures, views, palette, window borders, and text placement remain correct.

The repeated symbol is significant: it is the VGA BIOS’s compiled-in glyph for the character code selected by the interpreter. It is not random memory and is not evidence of damaged game data.

Rejected causes

The investigation tested and rejected these explanations:

HypothesisEvidence
The font at F000:FA6E is absent or corrupt.The system BIOS already contains the correct 1024 bytes, matching the font returned by the VGA BIOS.
The interpreter cannot read the font because of i440FX shadow settings.Read-only, write-only, and read/write PAM0 experiments did not change the result; live memory retained the correct bytes.
Copying the font after DOS boot repairs the problem.A corrected and byte-verified copy still produced the repeated glyph.
The selected emulated VGA card is responsible.QEMU standard VGA and Cirrus VGA reproduced the same behavior.
SeaVGABIOS alone is responsible.The older LGPL VGABIOS 0.7a binary reproduced the failure too.

The first FIXAGI.COM experiment also contained an independent bug: it changed DS before reading its saved source offset and consequently copied unrelated ROM bytes. Correcting the order and verifying the copy repaired the helper but not the dialog, which separated that helper defect from the actual firmware incompatibility.

Decisive interpreter trace

For an inverse character, the interpreter performs this observable sequence at the BIOS boundary:

  1. Read the selected eight-byte glyph from the platform font.
  2. Copy it to a scratch glyph and invert all eight bytes.
  3. Temporarily redirect interrupt vector 43h so character 80h addresses that scratch glyph.
  4. Ask INT 10h/AH=09h to draw character 80h with the requested attribute.
  5. Restore vector 43h.

On the first dialog S, the source bytes were:

78 cc e0 70 1c cc 78 00

The scratch bytes after inversion were:

87 33 1f 8f e3 33 87 ff

Immediately before the BIOS call, vector 43h, the eight-byte character height, and character code 80h resolved exactly to the inverted scratch bytes. A hardware read watchpoint on those bytes did not trigger during the BIOS call. The game-side data and vector were correct; the VGA BIOS did not consult the live vector.

Inspection of LGPL VGABIOS 0.7a independently confirmed that its planar, CGA, and linear graphics character renderers use private compiled-in font arrays. The project’s EGA-only correction therefore changes the planar renderer’s glyph-byte source while retaining its existing plane, mask, attribute, destination, and pixel-writing behavior.

Repository artifacts

PathPolicy
third_party/vgabios/vgabios-0.7a.binTracked pristine upstream binary; never modify in place.
third_party/vgabios/COPYINGTracked upstream LGPL 2.1 license.
third_party/vgabios/README.mdTracked provenance, source availability, and checksums.
tools/vgabios_int43_patch.asmTracked 16-bit patch source.
tools/setup_vgabios.pyTracked deterministic validator and patch builder.
build/vgabios/vgabios-0.7a-int43.binGenerated patched ROM; ignored and never committed.

The pristine binary is 41,472 bytes with SHA-256:

cd9fdd6a789dcd22b8a6b3b152788d43238de49cce674cff57bdeb94580246c6

The deterministic patched binary has SHA-256:

cfbbc5e3f97cb40cbc315b68e1e52d4488e6e27a47b339452a6a4ebf00f01247

Patch construction

The builder performs all of these checks and transformations:

  1. Verify the complete pristine-ROM SHA-256.
  2. Assemble tools/vgabios_int43_patch.asm with NASM and require exactly 44 bytes.
  3. Verify the original planar glyph-fetch instruction bytes at ROM offset 0x5251.
  4. Verify that the destination range beginning at 0xa1a4 is unused padding.
  5. Replace the original glyph calculation with a near call and padding.
  6. Install the patch routine at 0xa1a4.
  7. Recalculate the final option-ROM checksum byte so the complete byte sum is zero modulo 256.
  8. Verify the complete generated-ROM SHA-256 shown above.

The inserted routine reads the current offset and segment from vector 43h, adds the selected character, character-height, and glyph-row offsets, fetches the corresponding byte, applies the existing pixel mask, and returns to the unchanged renderer.

Reproduction

Build or verify the generated option ROM directly:

python3 -B tools/setup_vgabios.py --force

Normal FreeDOS environment setup performs the same step automatically:

python3 -B tools/setup_freedos_image.py --force

Use --skip-vgabios only for an intentional control environment using QEMU’s bundled VGA BIOS.

Launch QEMU manually with the generated ROM:

qemu-system-i386 -m 16 -boot c \
  -drive file=build/freedos/freedos.img,format=raw,if=ide,index=0,media=disk \
  -vga none \
  -device VGA,romfile="$(pwd)/build/vgabios/vgabios-0.7a-int43.bin" \
  -display cocoa -monitor stdio

The monitor/VNC variant replaces -display cocoa with:

-display vnc=127.0.0.1:5

Shared QEMU harnesses automatically select the generated ROM when it exists. Set AGI_VGABIOS=/absolute/path/to/another.bin to test another option ROM, or set AGI_VGABIOS=default to force QEMU’s bundled firmware for a control run.

Validation

The corrected ROM was validated by booting FreeDOS, running the immutable SQ2 input without FIXAGI.COM, and advancing through the introduction, station scene, and default-name prompt. The first playable-room dialog rendered its complete black-on-white text with distinct glyphs.

Automated validation includes:

  • complete pristine binary size and digest checks;
  • exact call-site and unused-area guards;
  • oversized-patch rejection;
  • generated option-ROM checksum verification;
  • deterministic final digest verification;
  • QEMU argument selection, override, and explicit-default tests; and
  • the complete local compatibility suite and both mdBooks.

If inverse text regresses, first run python3 -B tools/setup_vgabios.py --force and compare the printed output digest with the value above. Then check the QEMU command for both -vga none and the explicit VGA,romfile=... device. Merely placing a ROM in build/ does not make a manually constructed QEMU command use it.

License and source availability

VGABIOS 0.7a is redistributed under the GNU Lesser General Public License, version 2.1. The repository stores the unmodified upstream binary and license. third_party/vgabios/README.md identifies the official Savannah binary and complete source archive and records SHA-256 values for both. The modified ROM is generated locally from the tracked patch source and is intentionally not stored in version control.

Logic resource notes

This page records clean-room observations about logic resource payloads after they have been loaded from VOL.* by the generic resource reader. It is based on the local SQ2 resource files, SQ2/AGIDATA.OVL, and the decrypted executable. Current tools require the game directory to be selected explicitly with --game-dir PATH or AGI_GAME_DIR=PATH.

Loader path

The high-level logic loader starts at image offset 0x119a.

Logic cache records are 10 bytes and are linked from word [0x0977]:

+0x00: next logic cache record, or 0
+0x02: logic number byte
+0x03: message count byte
+0x04: bytecode base pointer, payload + 2
+0x06: current interpreter instruction pointer
+0x08: message offset table base pointer

Helper 0x110f(logic_number) scans the list. While scanning, it also stores the link slot that led to the current record in [0x0983]. On a miss, that slot is where a newly allocated record should be linked. For the first record the slot is the global root [0x0977]; for later records it is the previous record’s +0x00 field.

Observed load path:

load_logic(number):
    existing = find_cached_logic(number)        # 0x110f
    if existing:
        return existing

    suspend_update_lists()                      # 0x6a54
    record = allocate(10)
    *last_link_slot = record                    # [0x0983]
    record[0x00] = 0
    record[0x02] = number

    dir_entry = logic_directory_entry(number)   # 0x4371
    payload = read_volume_resource(dir_entry, 0) # 0x2e32 -> 0x2e56

    record[0x04] = payload + 2
    record[0x06] = payload + 2

    code_length = payload[0] | (payload[1] << 8)
    count_position = payload + 2 + code_length
    message_count = *count_position
    record[0x03] = message_count
    record[0x08] = count_position + 1

    if message_count != 0:
        old_current = current_logic             # [0x0981]
        current_logic = record
        end = message_pointer(0)                # 0x21f0
        text_start = record[0x08] + (message_count + 1) * 2
        xor_range(text_start, end)              # 0x07ab
        current_logic = old_current

    rebuild_update_lists()                      # 0x6a8e
    return record

The payload pointer returned by the volume reader begins after the 5-byte VOL.* record header. The first two bytes of a logic payload are not executed as bytecode; they are a little-endian length used to find the message metadata.

Room-switch cache reset

Room switching does not simply clear every resource cache root. The helper at image 0x10d0 performs a cache reset tuned for room transition:

reset_room_caches():                 # 0x10d0
    truncate_logic_cache_to_head()    # 0x10f7
    clear_view_cache_root()           # 0x396d -> [0x0ffa] = 0
    clear_sound_cache_root()          # 0x50cc -> [0x125a] = 0
    clear_picture_cache_root()        # 0x49dc -> [0x120e] = 0

The logic helper at image 0x10f7 is narrower than a root clear. If [0x0977] is nonzero, it treats that word as the first logic cache record and stores zero at record offset +0x00. This preserves the first linked logic record while unlinking later records. In SQ2’s normal room-switch path that matches the observed control-flow model: logic 0 survives the switch and later dispatches the destination room, while old room-specific cached logic records are discarded.

Payload layout

The loader supports this layout:

payload + 0x0000: u16 little-endian code_length
payload + 0x0002: bytecode, length code_length
payload + 0x0002 + code_length: u8 message_count
next byte: u16 little-endian message offsets, message_count + 1 entries
after table: encrypted message text bytes

The message offset table base is the byte immediately after message_count. Routine 0x21f0 reads u16le(table_base + message_number * 2) and returns table_base + offset. This makes message offsets relative to the table base, not relative to the payload start.

Offset entry 0 is used by the loader as the end pointer for the encrypted text area. Entries 1 through message_count are the game-visible message pointers. An offset value of zero is treated as an error path by 0x21f0 when requested.

Message text decryption

Routine 0x07ab XORs a memory range in place. It uses a zero-terminated key at DS:0x08f1, restarting from the first key byte when it reaches the zero byte. At runtime DS points at AGIDATA.OVL, whose offset 0x08f1 contains:

Avis Durgan

The loader calls this XOR routine over:

start = table_base + (message_count + 1) * 2
end = message_pointer(0)

So the message table itself remains unencrypted, while the message text region is decrypted in place after loading.

Version note: v3 message storage depends on the volume-record transform. Direct logic records retain the repeating-key message encoding and the loader applies the XOR pass after reading them. Dictionary-expanded logic records contain plain text after expansion and skip that pass. KQ4 image 0x311b sets word [0x0f5e] on the direct-read branch at 0x331b, clears it after dictionary expansion at 0x3348, and logic setup 0x13d9 tests it at 0x1458 before calling the XOR helper over the message range.

The local full KQ4 set has 174 dictionary-expanded logic records and three direct records (97, 100, and 131). Expanded logic 0 begins with readable message text, while direct logic 97 decrypts to Like a heavy blanket, darkness enfolds you. Gold Rush exhibits the same distinction: most original dictionary- expanded messages are plain after expansion, while direct logic 101 message 3 is encoded and decrypts to GR\0. Generated direct v3 fixtures should therefore keep tools/qemu_fixture.py’s encrypted-message default. A fixture that is itself dictionary-compressed would store the corresponding expanded message region as plain text before compression.

Local samples

Examples from SQ2:

LogicPayload lengthCode lengthMessage countTable baseText startEnd offset
19118682871877911
24536197235197520474536
33564142438142715053564
43614130324130613563614
74702771280284470
82325110515110811402325

For logic 1, the payload bytes at the message area decrypt to a null-terminated string beginning with Type RUN.

For logic 2, table entry 0 is 0x0a01, and table_base + 0x0a01 equals the payload length. Entry 1 is 0x0048, which points exactly at the first decrypted message byte because table_base + 0x0048 == text_start.

The bytecode beginning at payload + 2 is interpreted by the main logic loop at image offset 0x293c; see the logic bytecode notes for opcode dispatch and condition parsing.

Call and cache lifetime

Action helpers 0x113d and 0x1159 load a logic resource through 0x117d(logic_number). That wrapper calls the loader above and records the pair (0, logic_number) through helper 0x70b1. It leaves the logic cache record linked from [0x0977].

Action helpers 0x125a and 0x1280 use a different path, 0x12ae(logic_number), for invoking another logic resource as a subroutine:

call_logic(number):
    old_current = current_logic                 # [0x0981]
    cached = find_cached_logic(number)          # 0x110f

    if cached:
        current_logic = cached
        loaded_for_call = false
    else:
        saved_link_slot = last_link_slot        # [0x0983]
        current_logic = load_logic(number)      # 0x119a
        loaded_for_call = true

    if word[0x1d10] == 2:
        word[0x1d10] = 1
    if number == 0:
        word[0x1d20] = 1

    result = interpret(current_logic)           # 0x293c

    if loaded_for_call:
        *saved_link_slot = 0
        suspend_update_lists()                  # 0x6a54
        heap_rewind_to(current_logic)           # 0x143c
        rebuild_update_lists()                  # 0x6a8e

    current_logic = old_current
    return result

The action handlers propagate the interpreter result: if 0x12ae returns zero, the action dispatcher receives zero as the next instruction pointer and the current logic loop stops. A normal opcode 0x00 termination returns the callee’s nonzero instruction pointer, so the caller advances to its next action. Zero is reserved for action paths that deliberately abort/restart the current logic flow, such as room switching.

This shows two distinct lifetimes:

  • Logic loaded through 0x117d remains cached.
  • Logic that is first encountered through 0x12ae is temporary. After the nested interpreter returns, the record is unlinked and the heap top is rewound to the start of that record.

Saved interpreter positions

Routine 0x1364 serializes logic resume metadata into a table at 0x0985. Each entry is four bytes:

+0x00: logic number as a word
+0x02: current_ip - bytecode_base

It begins by treating the static head at 0x0977 as a 10-byte cache-shaped record, so the first emitted pair comes from bytes 0x0979, 0x097b, and 0x097d; in current state this produces (0, 0). It then follows the head’s next pointer and emits one entry for each linked cache record. Finally it writes terminator word 0xffff without clearing that record’s second word, and returns the total table byte count including the full four-byte terminator record.

Routine 0x13a5(record) performs the reverse lookup for one record. It scans from the first table entry, stops at the first matching logic number, and restores record[0x06] = record[0x04] + saved_offset. If no record matches before 0xffff, the loaded logic keeps its entry pointer. Resource replay, rather than this table, decides which logic records are loaded and receive this lookup.

Heap and lifetime model

The logic cache records, resource payloads, menu nodes, and selected render nodes are allocated from a bump pointer stored at [0x0a55]. No source-backed general free-list behavior has been observed for this heap. Instead, the engine uses marks and rewinds for broad lifetime changes: startup stores a room/reset mark with 0x1476 after initial setup and logic 0 load; room switch, restart, and restore paths call 0x1485 to return to that mark after freeing update-list nodes; temporary call_logic cleanup rewinds directly to the transient record pointer with 0x143c.

HelperObserved role
0x13d6(size)Allocate size bytes from [0x0a55]. If size > [0x0a5b] - [0x0a55], formats the out-of-memory message at 0x09fd, displays it, and calls the restart/exit helper 0x02ae. Otherwise returns the old heap pointer, advances [0x0a55], refreshes byte variable 8 at [0x0011], and updates high-water pointer [0x0a5f] when the new top is larger.
0x1430Return the current heap pointer [0x0a55].
0x143c(ptr)Rewind or set [0x0a55] to ptr. It does not itself refresh [0x0011]; callers that need the free-memory byte current call 0x14a0 separately.
0x144bSave the current heap pointer in [0x0a5d].
0x145aRestore [0x0a55] from temporary mark [0x0a5d] if it is nonzero, then clear [0x0a5d].
0x1476Store the current heap pointer in [0x0a59].
0x1485Free update-list nodes, clear [0x0a5d], restore [0x0a55] from [0x0a59], and refresh memory status.
0x14a0Compute free heap bytes as [0x0a5b] - [0x0a55] and store the high byte in byte variable [0x0011].

The heap-status diagnostic 0x87 formats the same pointers as offsets from heap base [0x0a57]: heap size is [0x0a5b] - [0x0a57], current use is [0x0a55] - [0x0a57], maximum use is [0x0a5f] - [0x0a57], and the room/reset mark is [0x0a59] - [0x0a57]. It also displays the maximum observed resource-event pair count [0x170f] as the script/resource-event budget line.

Logic bytecode interpreter notes

This page records clean-room observations about the interpreter for logic payload bytecode. It is based on local disassembly of build/cleanroom/AGI.decrypted.exe, the runtime tables in SQ2/AGIDATA.OVL, and decoded SQ2 logic payloads from an explicitly selected local game directory.

For a compact generated coverage index that lists every action opcode, every known condition opcode, operand shapes, and current evidence level, see Logic Opcode Evidence Matrix.

Runtime tables

After startup, DS points at AGIDATA.OVL. Both bytecode dispatchers use tables in that overlay rather than in the executable image.

The action dispatcher at image offset 0x02c4 calls through:

handler = u16le(DS:0x061d + opcode * 4)

Each action-table entry is four bytes:

u16 handler_image_offset
u8  fixed_operand_count
u8  variable_operand_bitmask

The condition dispatcher at image offset 0x07e3 calls through:

handler = u16le(DS:0x08fd + opcode * 4)

Each condition-table entry has the same four-byte shape. The condition scanner uses the third byte as the fixed operand count when it needs to skip an unexecuted condition.

The fourth byte is an operand metadata bitmask. From decoded handlers, bit 7 corresponds to operand 0, bit 6 to operand 1, and so on. A set bit means that the corresponding operand is a variable slot/reference rather than an immediate literal for table-aware decoding. The dispatcher does not apply this bitmask generically; each handler still consumes operands directly and decides whether a variable slot is read from or written to.

Examples:

EntryMetadataMeaning confirmed by handler
action 0x03 (assignn)0x80Operand 0 is a variable slot, operand 1 is an immediate literal.
action 0x04 (assignv)0xc0Operands 0 and 1 are variable slots.
action 0x26 (set_object_pos_var)0x60Operand 0 is an object index; operands 1 and 2 are variable slots.
action 0x52 (move_object_to_var)0x70Operand 0 is an object index; operands 1, 2, and 3 are variable slots; operand 4 is immediate.
action 0x7b (setup_transient_object_var)0xfeSeven operands are variable slots.
condition 0x0a (obj_table_room_eq_var)0x40Operand 0 is a table/object index; operand 1 is a variable slot.

AGIDATA.OVL contains valid-looking condition entries for opcodes 0x00 through 0x12. Bytes after that are strings and zero-filled data, even though the dispatcher only rejects condition opcodes >= 0x26. In the current local SQ2 scan, condition opcodes above 0x12 were not needed for successfully parsing ordinary condition lists.

Main interpreter loop

The main logic interpreter is at image offset 0x293c. It starts from the current instruction pointer stored in a logic cache record:

si = logic_record[0x06]

loop:
    opcode = *si++

    if opcode == 0x00:
        return si

    if opcode == 0xfe:
        delta = s16le(*si); si += 2
        si += delta
        continue

    if opcode == 0xff:
        evaluate_condition_then_maybe_execute_block()
        continue

    execute_action(opcode)
    if action returns si == 0:
        return 0
    continue

The 0xfe branch is visible at image offsets 0x2953..0x295a: it reads a little-endian word with lodsw and adds it to SI.

Action execution is delegated to 0x02c4. That dispatcher rejects opcodes 0x00 and 0xfc..0xff, reports an error for opcodes above 0xaf, then calls the action table at DS:0x061d.

Putting the observed dispatch ranges together:

Main-stream byte/rangeObserved role
0x00Logic-path terminator handled before the action dispatcher.
0x01..0xafNormal action opcodes. The action table contains one entry for each byte in this range.
0xb0..0xfbInvalid as action opcodes in this build. They reach the action dispatcher and take its “opcode above 0xaf” error path.
0xfcInvalid outside condition parsing. It is rejected by the action dispatcher as a structural/control byte.
0xfdInvalid outside condition parsing. It is rejected by the action dispatcher as a structural/control byte.
0xfeRelative jump handled by the main interpreter loop.
0xffConditional block marker handled by the main interpreter loop.

Cross-version note: the local Gold Rush / AGI v3 interpreter accepts action opcodes through 0xb5. Those slots are documented below as GR-specific extensions; the invalid-range statement above remains the SQ2 / AGI 2.936 behavior.

Shared opcode version note: the local Gold Rush / AGI 3.002.149 interpreter keeps the same parser contracts as SQ2 for shared action opcodes 0x00..0xaf, but source comparison found the following behavior-level deltas. These are version observations, not replacements for the SQ2 rows in the main catalog.

Opcode groupGold Rush / AGI v3 source-backed difference
0x6f, 0x73, 0x76, 0x77, 0x78, 0x89, 0x8aThe normal input-line and blocking prompt path is relocated, but the SQ2 display-mode-2/input-width branches are gone in GR. GR input-line setup computes the display offset as arg0 << 3 unconditionally.
0xa3, 0xa4, 0xa9GR maps the SQ2 input-width set/clear actions to the generic no-op handler. Its close-window action restores and clears active saved-window state, but does not clear a width flag.
0x79, 0xad, 0xb1, 0xb5GR expands the script key-map table to 0x31 slots; QEMU validates slot 48 by filling the first 48 slots with dummy mappings, mapping typed x in the final slot, and comparing the resulting nonblank picture capture with a direct draw while a no-key control remains blank. GR also changes the key-release gate from SQ2’s incremented [0x1530] byte to set/clear byte [0x0405], and adds a menu interaction gate word [0x0403]. QEMU menu_gate_suite validates that 0xb1(0) blocks a later menu request while 0xb1(1) lets the modal menu path run.
0x12GR calls a small helper before room switch: immediate target bytes 0x7e..0x80 become 0x49; other bytes pass through unchanged. Local GR scripts contain 0x12 operands 0x7e, 0x7f, and 0x80, so this is live compatibility behavior for this interpreter/game pair.
0x7c, 0x7d, 0x80, 0x84GR adds a temporary carried-item selector word, XORs the object/inventory chunk before and after save-file writes using repeating Avis Durgan bytes at data address DS:0x072c, records prompt-marker visibility before restart confirmation and redraws on accepted restart or on canceled restart only when the marker had been visible, and preserves object 0 motion mode 4 in 0x84. The save transform is modeled in tools/agi_save.py as gr_v3_object_inventory_save_xor() and QEMU-validated by blank-prefix save extraction build/gr-v3-behavior/save_xor_extract_qemu_001.json plus signed 0x8f("GR") extraction build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json; the restart redraw branch is modeled in tools/agi_restart.py.

The motion-mode 4 observation is intentionally narrower than an ordinary script-level opcode claim. Static comparison shows GR’s mode dispatcher accepts object byte +0x22 == 4 and routes it to the same target-direction helper used for mode 3. A copied-interpreter QEMU probe then patches only the generated fixture’s action-0x51 setup byte from mode 3 to mode 4; that instrumented case renders identically to the unmodified mode-3 fixture while a stationary control remains different. No ordinary bytecode setter for mode 4 has been observed yet.

Top-Level Cycle Timing

The top-level engine cycle observed at code.engine.main_cycle (0x0150) wraps logic execution with object and input/update passes. The current source model is:

  1. Run several input, sound, and display-maintenance helpers. One of these is code.engine.wait_for_cycle_counter (0x7f78), which reads byte DS:0x0013 (v10), waits until word [0x1784] is at least that value, then clears [0x1784]. Lower v10 values therefore reduce the top-level cycle wait; generated timed-carousel fixtures use v10 = 1 for fast but still capturable pacing.
  2. Mirror direction state between object 0 byte +0x21 and global byte [0x000f], depending on global word [0x0139]: when [0x0139] == 0, copy object 0 +0x21 to [0x000f]; otherwise copy [0x000f] back to object 0 +0x21.
  3. Call code.motion.pre_mode_and_boundary_update (0x0644). This pass scans active/update-eligible objects whose byte +0x01 == 1, dispatches motion mode byte +0x22 through code.motion.dispatch_mode_step (0x067a), and applies rectangle-boundary checks through code.motion.rectangle_boundary_check (0x06d9) when enabled.
  4. Invoke logic 0 through code.logic.call_logic (0x12ae). Nested logic calls use the same helper but preserve and restore the previous current-logic record.
  5. If byte [0x1757] is zero, call code.object.frame_timer_update (0x0563). That pass performs automatic direction-based group selection, frame-timer callbacks, movement update code.motion.update_objects (0x150a), and update-list draw/dirty-rectangle refresh. If [0x1757] is nonzero, the top-level loop skips 0x0563 for that cycle.

This ordering matters for generated probes and for compatibility. After logic 0 returns, code.engine.main_cycle also restores object 0 byte +0x21 from global byte [0x000f] before continuing the frame/update path. A logic script that writes object 0 +0x21 after the top-of-cycle mirror is therefore too late to seed [0x000f] for the following cycle unless another helper also updates the global direction byte. For example, automatic group selection observes object byte +0x01 before code.motion.update_objects later decrements that countdown byte. A one-shot script write of +0x01 = 2 therefore delays direction-based group selection by one later cycle rather than suppressing it permanently.

SQ2 logic 0 is the global per-cycle script. A source pass over the actual SQ2 logic resource shows that logic 0 handles global menu/input/status work first, then calls the current room script with action 0x17 (call_logic_var) using byte variable 0 at logic bytecode offset 0x053e. Room logics sampled from logic resources 1 through 10 begin with an if flag 5 entry block that loads views, pictures, sounds, and object state for that room, then fall through to normal per-cycle behavior. This means room-switch compatibility fixtures need to model both pieces: the engine’s room-switch helper and logic 0’s later call_logic_var(v0) room dispatch.

Conditional blocks

Opcode 0xff introduces a condition list. The condition parser uses these marker bytes:

ByteObserved role
0xfdInvert the next condition result.
0xfcOR-group marker.
0xffEnd of condition list.

The condition parser keeps two state bytes in BX. BL is the pending inversion flag for 0xfd; after each condition result is XORed with BL, the parser clears BL. BH tracks whether parsing is inside an OR group.

Observed 0xfc behavior:

  • If BH == 0, 0xfc starts an OR group by setting BH = 1.
  • If BH != 0, a second 0xfc means the OR group ended without a true term, so the whole condition list fails.
  • A false condition inside an OR group continues scanning for another OR term.
  • A true condition inside an OR group clears BH, skips the remaining OR terms until the next 0xfc, then resumes normal condition parsing.
  • A false condition outside an OR group fails the whole condition list.
  • A true condition outside an OR group simply advances to the next condition.

When all required conditions pass, execution continues with the following bytecode. When the condition list fails, the interpreter scans forward without executing actions until it finds a block-ending 0xff, then reads a little-endian relative offset and adds it to SI.

When a condition list has already failed, or when a true OR-term needs to skip the rest of its OR group, the condition-list scanner must know condition instruction lengths. For most condition opcodes it uses the fixed operand count in the condition dispatch table. There is a special case for condition opcode 0x0e (input_word_sequence): the scanner reads one count byte and skips count * 2 additional bytes. This special case appears in the condition skip paths at image offsets 0x29af..0x29b8 and 0x29d7..0x29e0.

Condition table

The current DS:0x08fd condition table entries are:

OpcodeLabelHandlerFixed operandsMetadata
0x00always_false0x09d800x00
0x01var_eq_imm0x082320x80
0x02var_eq_var0x083420xc0
0x03var_lt_imm0x084b20x80
0x04var_lt_var0x085c20xc0
0x05var_gt_imm0x087320x80
0x06var_gt_var0x088420xc0
0x07flag_set0x089b10x00
0x08flag_set_var0x08a010x80
0x09obj_table_room_ff0x08ad10x00
0x0aobj_table_room_eq_var0x093b20x40
0x0bobject_left_baseline_in_rect0x08c650x00
0x0cstatus_byte_12180x093110x00
0x0draw_key_event_available0x09be00x00
0x0einput_word_sequence0x095c00x00
0x0fstring_slots_equal_normalized0x09db20x00
0x10object_width_baseline_in_rect0x08e850x00
0x11object_center_baseline_in_rect0x08cc50x00
0x12object_right_baseline_in_rect0x08db50x00

The bytes after condition-table entry 0x12 decode as string/data bytes and then zero fill if forced through the same 4-byte entry parser. Although the condition dispatcher only rejects opcodes >= 0x26, no valid local condition list uses opcodes 0x13..0x25; for this build they are treated as invalid/reserved rather than real predicates.

Condition-list byte ranges:

Condition byte/rangeObserved role
0x00..0x12Valid predicates in this SQ2 build, listed above.
0x13..0x25Reserved/invalid for the portable model of this build. The dispatcher bound would allow them, but the underlying bytes are not a valid dispatch-table region.
0x26..0xfbRejected by the condition dispatcher if encountered as predicate opcodes.
0xfcOR-group marker interpreted by the condition-list scanner.
0xfdInvert-next-condition marker interpreted by the condition-list scanner.
0xfeRejected by the condition dispatcher if encountered as a predicate opcode.
0xffCondition-list terminator interpreted by the condition-list scanner.

The first seven handlers directly expose byte variable comparisons. The byte variable array begins at DS:0x0009.

OpcodeLabelObserved predicate
0x00always_falseAlways false. Handler 0x09d8 returns zero.
0x01var_eq_immbyte[0x0009 + arg0] == arg1
0x02var_eq_varbyte[0x0009 + arg0] == byte[0x0009 + arg1]
0x03var_lt_immbyte[0x0009 + arg0] < arg1
0x04var_lt_varbyte[0x0009 + arg0] < byte[0x0009 + arg1]
0x05var_gt_immbyte[0x0009 + arg0] > arg1
0x06var_gt_varbyte[0x0009 + arg0] > byte[0x0009 + arg1]
0x07flag_setTests flag bit arg0.
0x08flag_set_varTests flag bit byte[0x0009 + arg0].
0x09obj_table_room_ffLooks up a 3-byte table entry at [0x0971] + arg0 * 3 and tests whether byte +2 is 0xff.
0x0aobj_table_room_eq_varCompares byte +2 of that 3-byte table entry with byte[0x0009 + arg1].
0x0cstatus_byte_1218Returns byte DS:0x1218 + arg0.
0x0draw_key_event_availableChecks or obtains a raw key-like event byte through helper 0x459e, caching a non-zero byte at DS:0x001c.
0x0einput_word_sequenceVariable-length parsed-input word sequence test.
0x0fstring_slots_equal_normalizedCompares two fixed string slots after a small normalization pass.

The handler reads two byte operands and passes them to helper 0x0eac. Helper 0x0eac builds two temporary normalized strings through helper 0x0ef8, then compares the resulting zero-terminated byte strings exactly. The string-slot address is computed as 0x020d + slot * 0x28.

The 0x0ef8 normalization step:

  • walks the source slot until a zero byte;
  • skips bytes present in the zero-terminated table at DS:0x094b;
  • in local SQ2 data, DS:0x094b contains space, tab, ., ,, ;, :, ', !, and -;
  • converts ASCII uppercase A..Z to lowercase a..z through helper 0x4fea;
  • writes a zero terminator to the temporary buffer.

Unlike action 0x75 (parse_string_slot), this predicate does not parse dictionary words and does not use the parsed-word tables. It is a direct case-insensitive comparison after dropping the listed punctuation/spacing bytes.

Condition opcodes 0x0b (object_left_baseline_in_rect), 0x10 (object_width_baseline_in_rect), 0x11 (object_center_baseline_in_rect), and 0x12 (object_right_baseline_in_rect) all load an entry from a 43-byte structure array rooted at [0x096b] using the first operand as an index. They compare that object’s baseline position or horizontal extent against four subsequent byte operands:

arg0: object index
arg1: left bound
arg2: top/Y bound
arg3: right bound
arg4: bottom/Y bound

The shared helper at 0x091a loads:

object = [0x096b] + arg0 * 0x2b
dh = object[+0x03]
ch = object[+0x03]
dl = object[+0x05]

The common comparison at 0x08f0 returns true when:

dh >= arg1
dl >= arg2
ch <= arg3
dl <= arg4

The four handlers differ only in how they choose dh and ch before the comparison:

OpcodeLabelHorizontal test before shared rectangle comparison
0x0bobject_left_baseline_in_rectTests object left X and baseline Y inside the rectangle: dh = ch = x.
0x10object_width_baseline_in_rectTests the full object horizontal span at baseline Y: dh = x, ch = x + width - 1.
0x11object_center_baseline_in_rectTests object horizontal center and baseline Y: dh = ch = x + floor(width / 2).
0x12object_right_baseline_in_rectTests object right X and baseline Y: dh = ch = x + width - 1.

Flag helpers use a bitfield rooted at DS:0x0109. Helper 0x7511 computes:

byte_address = DS:0x0109 + flag_number / 8
mask = 0x80 >> (flag_number & 7)

Helper 0x74ee sets the bit, 0x74f4 clears it, 0x74fc toggles it, and 0x7502 tests it. Initialization helper 0x752a clears 0x20 bytes starting at DS:0x0109.

Condition opcode 0x0e (input_word_sequence) is the most common condition in the local SQ2 scripts. Its bytecode operands are variable length:

u8 count
u16le word_id[count]

Handler 0x095c compares those word IDs with a parsed input-word buffer rooted at DS:0x0c7b; word [0x0ca3] is used as the parsed-word count. The handler returns false without consuming the word list when the parsed-word count is zero, when flag 4 is already set, or when flag 2 is clear. Otherwise it walks the operand word IDs and the parsed input words together. Operand word 0x270f terminates the test successfully and skips any remaining operand words; operand word 0x0001 behaves as a wildcard for one parsed word. On a full match the handler sets flag 4 and returns true; on a mismatch it skips the remaining operand words and returns false. Because parser failures store a nonzero count/error-position and set flag 2, a terminator-only pattern can match an unknown-token parse state; QEMU batch parser_unknown_terminator_001 confirms this source-modeled edge.

Parsed input producer

Action 0x75 (parse_string_slot) is the observed producer for the parsed-word state consumed by condition 0x0e (input_word_sequence). It reads a string-slot index, accepts only slots 0..11, and passes fixed string slot 0x020d + slot * 0x28 to parser helper 0x18ac. Before parsing it clears flags 2 and 4.

Helper 0x18ac clears two 20-byte word tables:

0x0c7b: parsed dictionary word IDs, up to 10 words
0x0c8f: pointers to the normalized words in buffer 0x0ca7, up to 10 words

It normalizes the source string through helper 0x199d into buffer 0x0ca7. The normalization step:

  • treats bytes from DS:0x0c67 as separators. In SQ2 this string is ,.?!();:[]{}.
  • treats bytes from DS:0x0c75 as ignored punctuation. In SQ2 these bytes are 0x27, 0x60, 0x2d, and 0x22.
  • collapses runs of separators to one space.
  • drops ignored punctuation instead of making a new word.
  • trims a trailing space and writes a zero terminator.

Helper 0x1a6b then looks up each normalized word in WORDS.TOK, whose loaded base pointer is stored at [0x0ca5]. The file starts with 26 big-endian word offsets, one for each lowercase initial letter. A zero offset means no words for that initial. Dictionary entries are prefix-compressed:

u8 prefix_len_from_previous_decoded_word
encoded suffix bytes, last byte has bit 7 set
u16be word_id

For each suffix byte, (byte & 0x7f) ^ 0x7f gives the decoded lowercase character. The high bit only marks the final suffix byte. Local inspection of SQ2/WORDS.TOK finds 1,099 entries; for example look has word ID 0x0002, get has word ID 0x0005, and anyword has word ID 0x0001.

Parser result handling in 0x18ac:

  • Recognized words with nonzero IDs are appended to 0x0c7b, and their normalized-word pointers are appended to 0x0c8f.
  • Word ID zero is ignored; single-letter a and i followed by a space or terminator are handled this way, as are entries whose dictionary ID is zero. Ignored zero-ID words do not consume one of the ten parsed output slots.
  • An unrecognized token stores its pointer in the next 0x0c8f output slot, sets byte variable [0x0012] and word [0x0ca3] to parsed_nonzero_word_count + 1, and stops parsing. Thus an unknown word after ignored zero-ID words such as the still reports output slot 1, not the raw token ordinal.
  • If at least one token position is produced, flag 2 is set. Condition 0x0e (input_word_sequence) later sets flag 4 after a successful command-pattern match.

Raw event queue

Condition 0x0d (raw_key_event_available) is separate from parsed-word matching. Handler 0x09be first checks byte variable [0x001c]; if it is already nonzero, the condition returns true. Otherwise it calls helper 0x459e until that helper returns something other than 0xffff:

event = dequeue_event()                         # 0x44f9
if no event:
    return false
normalize_enter_escape(event)                   # 0x4634
if event.type == 1:
    return event.value
return 0xffff

When 0x459e returns a nonzero key-like value, condition 0x0d (raw_key_event_available) stores the low byte in [0x001c] and returns true. A zero return means no available event and the condition returns false. A 0xffff return means a non-key event was discarded and polling should continue.

QEMU fixture raw_key_condition_001 validates this predicate dynamically. The generated logic draws only when condition 0x0d succeeds, does not install any script key mapping with 0x79, and then sends a plain x key through the QEMU monitor. The original interpreter capture matched the validation draw, proving that the raw type-1 event path can satisfy 0x0d directly.

The queue itself is a 20-entry circular buffer of 4-byte records:

storage: 0x11ba .. 0x1209
[0x120a]: write pointer
[0x120c]: read pointer

record +0x00: event type word
record +0x02: event value word

Helper 0x44a9(type, value) enqueues a record and fails if advancing the write pointer would collide with the read pointer. Helper 0x44f9() dequeues one record, returning zero when the queue is empty.

The keyboard IRQ hook at image 0x6036 also feeds this queue. For selected scan codes in the range 0x47..0x51, it checks an enable table at 0x1519 + (scan - 0x47) and a pressed latch at 0x1524 + (scan - 0x47). On a key release, if the latch was set, it clears the latch and tests byte [0x1530]. When that byte is nonzero, the hook enqueues event (type=2, value=0) through code.input.enqueue_event (0x44a9). Action 0xad increments [0x1530], so it acts as a source-backed gate/count for this release-event path.

Keyboard helper 0x5a89 uses BIOS int 16h: it returns zero if no key is waiting, otherwise reads one key. If the ASCII byte is nonzero it clears AH and returns just that byte; if ASCII is zero, the BIOS scan-code word is returned intact.

Helper 0x467f drains available BIOS key events through 0x5a89 and enqueues them:

  • If helper 0x46b6 finds the returned key word in table DS:0x16b3, it enqueues type 2 with the mapped value.
  • Otherwise it enqueues type 1 with the raw key word.

The local DS:0x16b3 table maps BIOS arrow/keypad scan words to direction-like values:

Key wordMapped value
0x48001
0x49002
0x4d003
0x51004
0x50005
0x4f006
0x4b007
0x47008

The adjacent hardware/display remap table at DS:0x16d7 maps single-byte numeric keypad values to the same type-2 movement values when display adapter word [0x112e] == 2: 0x38, 0x39, 0x36, 0x33, 0x32, 0x31, 0x34, and 0x37 map to 1..8. The trailing 0x35 -> 0 entry is present in the table but does not enqueue a movement value.

Helper 0x4634 normalizes some multi-byte key words after dequeue: values 0x0101 and 0x0301 become 0x000d, while 0x0201 and 0x0401 become 0x001b.

Helper 0x4566(event_record) performs a script-configured remap for type-1 events. It scans four-byte slots rooted at 0x0145; when slot word +0 equals the event value, it changes the event type to 3 and replaces the value with slot word +2. Action 0x79 (map_key_event) appends entries to this table. An attempted QEMU probe mapping 0x5000 through 0x79 did not set the target status byte when driven with monitor sendkey down; this is recorded as a QEMU/input-instrumentation gap, not as evidence against the source table.

When display adapter word [0x112e] == 2, helper 0x46e8 can also remap type-1 event values through table DS:0x16d7, changing the event type to 2. The local table maps ASCII digits to direction-like values:

Key wordMapped value
0x00381
0x00392
0x00363
0x00334
0x00325
0x00316
0x00347
0x00378

Action table

The action table at DS:0x061d has entries for opcodes 0x00..0xaf. The main dispatcher only executes opcodes 0x01..0xaf; 0x00 terminates the current logic loop and 0xfc..0xff are structural bytes.

All action-table entries through 0xaf now have local labels. Some labels remain deliberately implementation-shaped where the handler’s state mutation is clear but the user-facing command name is not.

Coverage audit:

  • tools/disassemble_logic.py labels every action byte 0x00..0xaf.
  • 0x00 is a structural byte handled by the main interpreter loop, not by the normal action dispatcher.
  • 0xfc..0xff are structural bytes and are rejected by the action dispatcher.
  • The current condition catalog labels all valid-looking condition-table entries 0x00..0x12; bytes 0x13..0x25 are treated as reserved/invalid in this SQ2 build even though the dispatcher only rejects >= 0x26.

Examples of action entries:

OpcodeLabelHandlerFixed operandsMetadata
0x00end0x505100x00
0x01inc_var0x735510x80
0x02dec_var0x736810x80
0x03assignn0x737b20x80
0x0cset_flag0x748410x00
0x14load_logic0x113d10x00
0x16call_logic0x125a10x00
0x1eload_view0x39b110x00
0x21reset_object_state0x04d910x00
0x23activate_object0x09ea10x00
0x25set_object_pos0x7c1a30x00
0x29set_object_resource0x3a7720x00
0x3fset_global_012d0x7e7c10x00
0x51move_object_to0x6ce450x00
0x62load_sound0x510a10x00
0x64stop_sound_or_clear_sound_state0x522500x00
0x7asetup_transient_object0x2c7a70x00
0x82random_range_to_var0x500930x20
0x93set_object_pos_dirty0x7d7730x00
0xa7divn0x744c20x80

This table entry points at the same no-op helper used by action 0x7f, but the main interpreter loop handles opcode byte 0x00 before entering the action dispatcher. Runtime opcode 0x00 returns the instruction pointer immediately after the terminator. It does not write the logic record’s resume-pointer field. Later bytes in the same logic payload can still be reached by a jump from earlier code.

The action dispatcher itself does not use the operand-count byte; handlers consume operands directly from SI and return the next SI in AX. The table metadata is used by scanner/debug paths that need to skip or display bytecode.

Decoded action families

Variable action handlers directly operate on the byte array rooted at DS:0x0009.

OpcodeLabelHandlerObserved action
0x01inc_var0x7355Increment var[arg0] unless it is already 0xff.
0x02dec_var0x7368Decrement var[arg0] unless it is already 0x00.
0x03assignn0x737bvar[arg0] = arg1
0x04assignv0x7388var[arg0] = var[arg1]
0x05addn0x739bvar[arg0] += arg1
0x06addv0x73a8var[arg0] += var[arg1]
0x07subn0x73bbvar[arg0] -= arg1
0x08subv0x73c8var[arg0] -= var[arg1]
0x09indirect_assignv0x73dbvar[var[arg0]] = var[arg1]
0x0aassign_indirectv0x7405var[arg0] = var[var[arg1]]
0x0bindirect_assignn0x73f4var[var[arg0]] = arg1
0xa5muln0x741evar[arg0] *= arg1; low byte of the product is stored.
0xa6mulv0x7431var[arg0] *= var[arg1]; low byte of the product is stored.
0xa7divn0x744cvar[arg0] /= arg1; 8-bit quotient is stored.
0xa8divv0x7465var[arg0] /= var[arg1]; 8-bit quotient is stored.

Flag action handlers use the same bitfield helpers as condition opcodes 0x07 and 0x08.

OpcodeLabelHandlerObserved action
0x0cset_flag0x7484Set flag bit arg0.
0x0dclear_flag0x748bClear flag bit arg0.
0x0etoggle_flag0x7492Toggle flag bit arg0.
0x0fset_flag_var0x7499Set flag bit var[arg0].
0x10clear_flag_var0x74a8Clear flag bit var[arg0].
0x11toggle_flag_var0x74b7Toggle flag bit var[arg0].

Several object/view actions use entries in the 43-byte structure array rooted at [0x096b]. Field names remain provisional; these notes record observed storage and calls rather than assigning final game-level names.

OpcodeLabelHandlerObserved action
0x23activate_object0x09eaReads an object index and calls 0x0a06. The helper validates the object, requires word [object+0x10] != 0, copies [+0x10] to [+0x12], [+0x03] to [+0x16], and [+0x05] to [+0x18], sets bits in word [+0x25], and calls several list/graphics helpers.
0x24deactivate_object0x0a8fCalls helper 0x0aab for object arg0. If the object has bit 0x0001 set in [+0x25], the helper clears that bit and calls list/graphics helpers to remove or deactivate the object.
0x25set_object_pos0x7c1aSet object position-like fields from immediate bytes: [+0x16] = [+0x03] = arg1, [+0x18] = [+0x05] = arg2.
0x26set_object_pos_var0x7c57Set the same fields from variables var[arg1] and var[arg2].
0x27get_object_pos0x7ca4Store low bytes of object fields [+0x03] and [+0x05] into var[arg1] and var[arg2].
0x28add_object_pos_from_vars0x7ce7Reads object index arg0, signed deltas from var[arg1] and var[arg2], adds those deltas to object fields [+0x03] and [+0x05], clamps each field to zero when a negative delta would underflow, sets bit 0x0400 in [+0x25], then calls 0x593a.
0x29set_object_resource0x3a77Resolve object arg0, pass immediate arg1 to helper 0x3ae7.
0x2aset_object_resource_var0x3aabResolve object arg0, pass var[arg1] to helper 0x3ae7.
0x2bset_object_subresource0x3b47Resolve object arg0, pass immediate arg1 to helper 0x3bb7.
0x2cset_object_subresource_var0x3b7bResolve object arg0, pass var[arg1] to helper 0x3bb7.
0x2fset_object_derived_resource_20x3c55Resolve object arg0, pass immediate arg1 to helper 0x3ccb, then clear bit 0x1000 in object word field [+0x25].
0x30set_object_derived_resource_2_var0x3c8cSame as 0x2f, but the helper argument is read from var[arg1]. Helper 0x3ccb selects a derived subresource/loop-like entry, updates object byte [+0x0e], pointer [+0x10], width-like word [+0x1a], and height-like word [+0x1c], then clamps object fields [+0x03] and [+0x05] against visible bounds and sets bit 0x0400 when it adjusts them.
0x31get_object_resource_loop_count0x3d9fStores byte [*([object+0x0c])] - 1 into var[arg1]. This appears to report a count from the object’s loaded resource table.
0x32get_object_field_0e0x3dedStores object byte [+0x0e] into var[arg1].
0x33get_object_field_0a0x3e25Stores object byte [+0x0a] into var[arg1].
0x34get_object_field_070x3e5dStores object byte [+0x07] into var[arg1].
0x35get_object_field_0b0x3e95Stores object byte [+0x0b] into var[arg1]. This opcode was present in the action table but not encountered in the current SQ2 scan.

Helper 0x3ae7 finds a cached resource record via 0x3979, stores the resource payload pointer at object field [+0x08], stores the selected resource number at byte [+0x07], copies byte [payload+0x02] to object byte [+0x0b], then calls 0x3bb7. Helper 0x3bb7 validates object field [+0x08], checks its second argument against byte [+0x0b], then calls 0x3c1b and 0x3ccb to update derived object fields.

Resource and interpreter-control actions observed so far:

OpcodeLabelHandlerObserved action
0x12switch_room_like0x175cReads immediate arg0 and calls helper 0x1792. The helper stops active sound state, restores heap/update-list state through 0x1485, calls cleanup helpers 0x4482, 0x707c, and 0x706d, resets selected object fields, truncates/clears resource caches, sets [0x0139] = 1, stores 0x24 in word [0x012d], copies byte variable 0 from DS:0x0009 to DS:0x000a, writes arg0 to byte variable 0, clears bytes DS:0x000d and DS:0x000e, records object 0’s current view/resource byte in DS:0x0019, calls 0x117d to load logic arg0, optionally loads another logic from [0x1d12], may reposition object 0 from boundary byte [0x000b], sets flag 5, and calls redraw/reinitialization helpers. This is a broad room/state switch action; the final name is still provisional.
0x13switch_room_like_var0x1773Same as 0x12, but the target number is read from var[arg0].
0x14load_logic0x113dReads immediate arg0, calls 0x117d. Helper 0x117d loads logic resource arg0 through 0x119a, then records pair (0, arg0) through helper 0x70b1.
0x15load_logic_var0x1159Same as 0x14, but logic number is var[arg0].
0x16call_logic0x125aReads immediate arg0, calls 0x12ae, and returns zero to the action dispatcher if 0x12ae returns zero. Helper 0x12ae preserves the previous current logic pointer at [0x0981], finds or loads the target logic, calls main interpreter 0x293c, and frees the target record afterward only if it had to be loaded transiently for this call.
0x17call_logic_var0x1280Same as 0x16, but logic number is var[arg0].
0x18load_picture_var0x4a16Reads a picture-like resource number from var[arg0] and calls loader helper 0x4a3b. When the resource is not already cached, the helper records event pair (2, resource), uses directory accessor 0x43d9 and the generic volume reader 0x2e32, then stores the loaded payload pointer in a linked cache entry rooted at 0x120e.
0x19prepare_picture_var0x4aaaReads a picture-like resource number from var[arg0], requires an existing cached entry through 0x4acf, records event pair (4, resource), stores the resource payload pointer at global 0x1377, and calls helpers 0x6a54, 0x6445, and 0x6a8e.
0x1ashow_picture_like0x4b82Clears flag 15 through wrapper 0x74d0, calls helper 0x1f2b with argument 0, calls 0x5546, and sets global word [0x1216] = 1. This appears to be a picture/display finalization action.
0x1bdiscard_picture_var0x4baaReads a picture-like resource number from var[arg0] and calls helper 0x4bce, which records event pair (6, resource), clears the link preceding the matching cache record so the match and every later picture record become unreachable, rewinds the shared heap through 0x143c, and calls update/free-state helpers 0x6a8e and 0x14a0.
0x1coverlay_picture_var0x4b17Reads a picture-like resource number from var[arg0], requires an existing cached entry through helper 0x4b3b, records event pair (8, resource), stores that resource’s payload pointer at global [0x1377], calls helpers 0x6a54, 0x6440, 0x6a8e, and 0x6aab, and clears word [0x1216]. Unlike 0x19 (prepare_picture_var), this path enters the picture decoder at 0x6440 rather than 0x6445, so it skips the extra buffer-fill setup performed by 0x6445.
0x1dshow_priority_screen0x731bSets word [0x1755] = 1, calls full-screen refresh helper 0x5546, waits for an event through 0x4618, calls 0x5546 again, then clears [0x1755]. Helper 0x5546 swaps the high and low nibbles of every byte in the logical graphics buffer while [0x1755] & 1 is set before copying the full screen to the display. The only observed local input phrase reaching this action is show pri, where WORDS.TOK word id 0x0028 maps to “show” and word id 0x003f maps to “pri”.
0x1eload_view0x39b1Loads or refreshes a view-like resource through helper 0x39f7 using immediate arg0. When no cached view entry exists, the helper records event pair (1, resource) before allocating and reading the resource.
0x1fload_view_var0x39d0Same as 0x1e, but resource number is var[arg0].
0x20discard_view0x3ecdReads immediate view-like resource number arg0 and calls helper 0x3f0d. That helper requires a matching cached view record through 0x3979, records pair (7, resource) through 0x70b1, clears the preceding link through *([0x1000]) so the match and every later view record become unreachable, flushes update lists with 0x6a54, rewinds to the selected record with 0x143c, rebuilds update lists with 0x6a8e, and calls 0x14a0.
0x62load_sound0x510aLoads a sound-like resource through helper 0x5126 using immediate arg0. When no cached sound entry exists, the helper records event pair (3, resource), uses sound directory accessor 0x440d, and builds four internal pointers from the loaded payload.
0x63start_sound_with_flag0x51d3Stops any active sound-like state through 0x5234, reads immediate sound number arg0 and immediate flag number arg1, stores arg1 in word [0x126a], clears flag arg1, locates or loads sound arg0 through helper 0x50d8, and starts it through helper 0x7f96. If the sound cannot be loaded, it reports error code 9 with the sound number.
0x64stop_sound_or_clear_sound_state0x5225Calls helper 0x5234, which clears a pending sound-like state at [0x1258], sets flag [0x126a], and calls 0x080af when that state was active.

QEMU fixture resource_lifecycle_003 validates several resource lifecycle paths with synthetic resources:

  • 0x15 (load_logic_var) can load a variable-selected logic resource before 0x16 calls it.
  • 0x1c (overlay_picture_var) requires the target picture to have already been loaded with 0x18. The overlay changes logical picture state, but the composed picture was not visible in the QEMU capture until 0x1a (show_picture_like) ran afterward.
  • 0x1b (discard_picture_var) can discard a loaded picture and allow a later reload/overlay path.
  • 0x20 (discard_view) and 0x99 (discard_view_var) can discard a loaded view; reloading that view with 0x1e then permits normal drawing.
  • 0x1f (load_view_var) is validated by starting without the usual preloaded view 11, setting a variable to 11, executing 0x1f, and then successfully drawing that view.

QEMU fixture menu_sound_001 dispatch-smokes 0x62 (load_sound) followed by 0x64 (stop_sound_or_clear_sound_state) with sound resource 1. QEMU fixture sound_completion_001 adds a behavior assertion across 0x62, 0x63, and 0x64: it first loads sound 1 with 0x62, starts it with completion flag operand 77 through 0x63, then clears sound state with 0x64 and draws only after flag 77 is set. This validates the load/start/stop completion-flag contract for those opcodes, but it does not yet model audio output or asynchronous playback timing.

QEMU fixture priority_diag_sound_001 validates the same stop-flag behavior in the narrower priority/diagnostic batch: after 0x63 has associated completion flag 77 with sound 1, 0x64 sets that flag while clearing sound state. The same batch validates 0x1d as a blocking priority/control display that returns after Enter and 0x85 as an object-diagnostics display action that returns after Enter.

Source mapping clarifies the sound state transition. Action 0x63 first calls the same stop helper used by 0x64, then reads the sound resource number and completion flag operand. It stores the flag number in [0x126a], clears that flag, locates an already loaded sound resource through helper 0x50d8, reports an error if it cannot be found, and calls the hardware/driver start helper 0x7f96 with the located record. Action 0x64 is a thin wrapper around code.sound.stop_or_clear_state; that helper only sets the configured completion flag when active-state word [0x1258] is nonzero, then clears the active-state word and calls the hardware/driver stop helper 0x80af.

The sound loader and driver-start helpers also reveal the sound resource container. code.sound.load_resource reads the payload through the sound directory, stores the raw payload pointer at cache-record +0x04, then reads four little-endian words from the payload header and stores four derived payload-relative channel pointers at record offsets +0x06, +0x08, +0x0a, and +0x0c. code.sound.driver_start copies those four pointers into the runtime channel pointer table, initializes four countdown words to 1, initializes active/tone state words, and marks sound state active at [0x1258]. The driver tick reads duration u16, treats 0xffff as a channel terminator, otherwise reads a 16-bit tone/control word followed by one control byte whose low nibble is the observed attenuation/control value.

The timer interrupt hook calls code.sound.driver_tick while [0x1258] is nonzero. code.sound.driver_tick first tests flag 9 through the shared flag helper; if flag 9 is clear, it immediately runs the stop/core completion path. When playback is allowed, the tick loop advances channel 0 only for hardware selector [0x112e] values 0 and 8, and advances channels 0 through 3 for other observed selector values. Each channel’s countdown starts at 1, so its first event or terminator is consumed on the first tick. Event durations are stored as 16-bit countdown values. Duration zero would wrap before the next record read; no present SQ2 sound resource uses duration zero. Natural completion occurs when all active channels have consumed 0xffff terminators, which reaches the same stop/core path that clears active state and sets the configured completion flag. Tone-to-frequency conversion, attenuation envelopes, and hardware port effects remain provisional.

Resource Event Log

The engine keeps a compact replay log for resource and transient-display work that must be reconstructed after a restore or display-mode rebuild. The log is not a general trace. It records the operations needed to rebuild persistent room state from a reset cache, and some helper paths intentionally disable recording while doing temporary work.

Each entry is a two-byte pair:

u8 kind
u8 value

data.event.pair_capacity ([0x0141]) is the maximum number of pairs. code.event.reset_pair_buffer allocates capacity * 2 bytes if the base pointer is zero, stores the base at [0x1707], resets the write pointer [0x1709] to the base, and clears data.event.pair_count ([0x0143]).

code.event.record_pair appends only when both gates allow it:

  • flag 7 must be clear;
  • data.event.recording_enabled ([0x170d]) must be nonzero.

When appending, it checks the write pointer against base + capacity * 2, reports error code 0x0b on overflow, writes the low bytes of (kind, value), advances the write pointer by two, and increments the pair count. It also maintains data.event.pair_high_water ([0x170f]) from the current write distance; the heap/status diagnostic displays this value.

Known event kinds:

KindRecorded byRestore replay effect
0load_logic / load_logic_var through code.logic.load_cachedLoad the logic resource through code.logic.load_resource, then restore saved logic resume metadata with 0x13a5.
1load_view / load_view_var when a view cache entry is createdLoad or refresh the view resource through code.view.load_resource with the force/refresh argument set.
2load_picture_var when a picture cache entry is createdLoad the picture resource through code.picture.load_resource.
3load_sound when a sound cache entry is createdLoad the sound resource through code.sound.load_resource.
4prepare_picture_varPrepare/decode the already-loaded picture through code.picture.prepare.
5setup_transient_object / setup_transient_object_varRead the next three pairs into staged bytes 0x0eae..0x0eb3, then call code.object.setup_transient_display_object.
6discard_picture_varDiscard the picture and truncate later picture cache records through code.picture.discard.
7discard_view / discard_view_varDiscard the view and truncate later view cache records through 0x3f0d.
8overlay_picture_varOverlay/decode the already-loaded picture through code.picture.overlay_prepare.

Kind 5 is a four-pair packet rather than a single resource event. Helper 0x2d52 first records (5, 0), then records ([0x0eae], [0x0eaf]), ([0x0eb0], [0x0eb1]), and ([0x0eb2], [0x0eb3]). Restore replay consumes those three following pairs and writes them back to the same staged globals before calling 0x2d52.

Restore-time replay at code.restore.replay_resource_events stops sound, resets room caches, disables event recording, prepares the replay cursor, then walks pairs with code.event.next_replay_pair. Disabling recording is what prevents the replayed loads/discards from appending duplicates to the log. The event-kind dispatch table lives at image 0x6915; a linear disassembly can misread the first post-table instruction as data. When disassembled at the loop-exit target, image 0x6927 is call 0x706d, so replay re-enables event recording immediately after code.event.next_replay_pair returns zero and before it rebinds object views. After that it restores object flags saved around the cache reset, refreshes the display, and redraws text/input state. This source finding explains the earlier QEMU observation that recording was enabled again by the following script action.

Because kinds 6 and 7 enter the ordinary discard helpers, replay reproduces the same ordered family truncation. A later kind 1 or 2 pair can retain a resource again after that truncation; restore therefore rebuilds retention order from the active pair sequence rather than from an unordered final set.

The temporary view-resource display actions 0x81 and 0xa2 also call code.event.disable_recording before their internal load/display/discard sequence, then call code.event.enable_recording before returning. This keeps view preview/text display from becoming part of the room restore model.

Room/state switch helper 0x1792, reached by actions 0x12 (switch_room_like) and 0x13 (switch_room_like_var), first stops active sound state, resets heap/update-list state through code.heap.reset_dynamic_state, clears parser/input state, initializes the resource/event pair buffer, and enables resource/event pair recording. It then resets all object records from [0x096b] to [0x096d]. For each 43-byte record it clears active/update bits 0x0001 and 0x0040, sets bit 0x0010, clears pointer fields +0x08, +0x10, and +0x14, and stores 1 in bytes +0x00, +0x01, +0x1e, +0x1f, and +0x20. The last three are the step-size byte and the frame-timer reload/current bytes; they are seeded to one, not zero.

After the object reset, helper 0x10d0 resets the room resource caches. It preserves the first logic cache record by writing zero to that record’s next link, then clears the view cache root [0x0ffa], sound cache root [0x125a], and picture cache root [0x120e]. The room-switch helper then sets the object-boundary flag word [0x0139] = 1, clears word [0x013d], restores the horizon-like global [0x012d] to 0x24, copies current room byte variable 0 to previous-room byte variable 1, writes the destination room to byte variable 0, clears object-boundary bytes [0x000d] and [0x000e], copies object 0’s view/resource byte +0x07 to byte variable 16, refreshes object/resource state, loads the destination logic through code.logic.load_cached, reloads optional trace logic [0x1d12] when configured, handles the entry-boundary selector, sets flag 5, refreshes display/resource state, redraws the status line, redraws the input line, and returns zero.

The zero return is important. code.engine.main_cycle calls logic 0 through code.logic.call_logic(0); if that call returns zero, it clears temporary room-boundary variables and immediately calls logic 0 again instead of continuing the post-logic frame/update path. This explains why one-shot synthetic room-switch probes that draw after 0x12/0x13 are the wrong shape: the switch action intentionally aborts the current bytecode stream so the next logic-0 pass can perform entry initialization and room dispatch.

Entry-boundary selector byte variable 2 (DS:0x000b) is interpreted after the destination logic is loaded: 1 places object 0 Y at 0xa7, 2 places X at 0, 3 places Y at 0x25, and 4 places X at 0xa0 - object_width; the byte is then cleared.

In script terms, byte variable 0 (DS:0x0009) is the current room, byte variable 1 (DS:0x000a) is the previous room, and byte variable 2 (DS:0x000b) is the entry-boundary selector consumed by the switch helper. The helper loads the destination logic resource but does not by itself execute that room’s bytecode. In SQ2, logic 0 later dispatches the current room via call_logic_var(v0).

Several early QEMU probes for 0x12/0x13 did not produce stable reusable evidence. A direct var0 assertion after the switch failed, a target-logic draw probe failed, a logic-0 re-entry fixture with 0x92 (restore_logic_entry_ip) before the switch did not reach its validation draw, and a first synthetic call_logic_var(v0) dispatch fixture still produced the same blank-screen mismatch. Those failures were fixture-shape problems rather than disproofs of the source model.

A later QEMU fixture room_switch_reentry_001 matched the original engine for both 0x12 and 0x13. In that fixture, logic 0 sets a private init flag before executing the switch action. Because the switch returns zero, the current logic invocation aborts and code.engine.main_cycle immediately calls logic 0 again. On the second pass, logic 0 skips the switch and executes call_logic_var(v0). The destination logic then checks flag 5, loads and shows its own picture, loads its view, and draws a validation sprite. This validates the user-visible re-entry/current-room dispatch shape: a room switch prepares state and loads the destination logic, but room-local drawing/setup belongs to the destination room logic reached on the following logic-0 pass.

QEMU fixture room_previous_001 validates the byte-variable room update contract for both 0x12 and 0x13. The fixture writes a synthetic previous room into byte variable 0 before switching to room 1, and writes an invalid boundary selector into byte variable 2. The destination room logic draws only if byte variable 0 is the destination room, byte variable 1 is the synthetic previous room, and byte variable 2 has been cleared. The matched immediate and variable-selected cases confirm that the switch helper copies old v0 to v1, writes the destination room to v0, and clears v2 on both operand paths.

QEMU fixtures room_boundary_002 and room_boundary_var_001 validate the four entry-boundary selector cases through normal bytecode-visible object state for both immediate and variable-selected room switches. The fixture initializes object 0 with loaded view 11 frame 0 at (44,80) before switching rooms. The destination room logic then reads object 0 with action 0x27 and draws only if the expected position and cleared selector byte are observed. The matched cases confirm selector 1 writes Y 0xa7, selector 2 writes X 0, selector 3 writes Y 0x25, selector 4 writes X 140 (0xa0 - 20 for the selected cel width), and all four clear byte variable 2 on both operand paths. An earlier attempt that bound object 0’s view without first loading view 11 did not reach the switch; the right-edge selector is only meaningful when object width has already been derived.

QEMU fixture room_object_reset_001 validates one visible object-reset effect for both operand paths. Logic 0 loads view 11, sets up object 10, activates it as a persistent object, and then switches rooms. The destination room logic draws a single validation sprite. Both immediate and variable-selected cases match only when the pre-switch persistent object is absent from the destination room render, confirming that active object draw state does not survive the room switch.

The broader internal reset effects are still source-backed from the disassembly: object/resource reset beyond the bytecode-visible fields checked above, resource/event recording, optional trace logic reload, and the exact redraw/input refresh calls.

Additional object-state actions:

OpcodeLabelHandlerObserved action
0x21reset_object_state0x04d9Calls helper 0x04f5 for object arg0. If object word [+0x25] does not have bit 0x0040, the helper sets [+0x25] = 0x0070 and clears bytes [+0x22], [+0x23], and [+0x21].
0x22clear_all_object_bits0x053dIterates all 43-byte object entries from [0x096b] to [0x096d] and clears bits 0x0041 in word field [+0x25].
0x2dset_object_bit_20000x497bSets object bit 0x2000 in [+0x25]. In the per-cycle frame/group update helper, this bit suppresses automatic direction-based group selection.
0x2eclear_object_bit_20000x49a3Clears object bit 0x2000 in [+0x25], allowing automatic direction-based group selection when the object’s view/group count field is in a supported range.
0x3aclear_object_bit_00100x6ac8Calls helper 0x6b44, which clears object bit 0x0010 if it was set. The bit partitions active objects between update-list root 0x16ff and root 0x1703; the helper wraps a membership change with 0x6a54 and 0x6a8e to flush/rebuild the two lists.
0x3bset_object_bit_00100x6af0Calls helper 0x6b62, which sets object bit 0x0010 if it was clear. As with 0x3a, a real membership change flushes and rebuilds the two update-list roots through 0x6a54 and 0x6a8e.
0x3crefresh_object_lists0x6b18Computes the object record address from arg0, then calls helpers 0x6a54, 0x6a8e, and 0x6aab. The computed object address is not passed to those helpers; the observed effect is an all-list flush, rebuild/draw, and dirty-rectangle refresh pass, with no direct object field writes in the handler itself.
0x3dset_object_bit_00080x7e94Sets object bit 0x0008 in [+0x25].
0x3eclear_object_bit_00080x7eb9Clears object bit 0x0008 in [+0x25].
0x3fset_global_012d0x7e7cStores immediate arg0 as a word at DS:0x012d.
0x40set_object_bit_01000x7e0dSets bit 0x0100 in object word field [+0x25]. QEMU validates that this makes a priority-14 object on a full control-class-2 picture remain visible but unable to move from (20,80) to (50,80).
0x41set_object_bit_08000x7e32Sets bit 0x0800 in object word field [+0x25]. QEMU validates that this makes a priority-14 object on a full control-class-3 picture remain visible but unable to move from (20,80) to (50,80).
0x42clear_object_bits_09000x7e57Clears object bits 0x0100 and 0x0800 by ANDing [+0x25] with 0xf6ff. QEMU validates that clearing after 0x40 or 0x41 restores movement to (50,80) on the same synthetic control-class pictures.
0x43set_object_bit_02000x479fSets bit 0x0200 in object word field [+0x25].
0x44clear_object_bit_02000x47c7Clears bit 0x0200 in object word field [+0x25].
0x45object_distance_to_var0x47efReads two object indices and a destination variable index. If either object lacks bit 0x0001, stores 0xff in the destination variable. Otherwise computes abs(y0-y1) + abs((x0 + width0/2) - (x1 + width1/2)), caps the result at 0xfe, and stores it in the destination variable.
0x46clear_object_bit_00200x6c97Clears object bit 0x0020 in [+0x25].
0x47set_object_bit_00200x6cbcSets object bit 0x0020 in [+0x25].
0x48set_object_field_23_mode00x6b82Sets object byte [+0x23] = 0 and sets bit 0x0020 in [+0x25].
0x49set_object_field_23_mode10x6baeSets object byte [+0x23] = 1, sets bits 0x1030 in [+0x25], stores immediate arg1 in byte [+0x27], and clears flag arg1.
0x4aset_object_field_23_mode30x6bebSets object byte [+0x23] = 3 and sets bit 0x0020 in [+0x25].
0x4bset_object_field_23_mode20x6c17Sets object byte [+0x23] = 2, sets bits 0x1030 in [+0x25], stores immediate arg1 in byte [+0x27], and clears flag arg1.
0x4cset_object_field_1f_var0x6c54Stores var[arg1] in object byte [+0x1f] and copies the same byte to object byte [+0x20]. These bytes are the reload and current countdown for code.object.frame_timer_update.
0x4dclear_object_fields_21_220x6ec8Clears object bytes [+0x21] and [+0x22]; if the object is the first object entry, also clears byte DS:0x000f and word [0x0139].
0x4eclear_object_field_22_and_global0x6f05Clears object byte [+0x22]; if the object is the first object entry, clears byte DS:0x000f and sets word [0x0139] = 1. A QEMU movement probe validates the byte [+0x22] effect by starting random motion with 0x54, immediately executing 0x4e, and observing that the object remains at its starting position.
0x4fset_object_field_1e_var0x6f3eStores var[arg1] in object byte [+0x1e].
0x50set_object_field_01_var0x6f7cStores var[arg1] in object byte [+0x01] and clears object byte [+0x00].

QEMU batch object_root_partition_004 validates the visible behavior of 0x3a, 0x3b, and 0x3c: clearing bit 0x0010 moves an active object into the root 0x1703 partition drawn before root 0x16ff; setting the bit moves it back into the later-drawn 0x16ff partition; and 0x3c refreshes the lists without an object-local field write.

Static analysis of code.object.frame_timer_update (0x0563) ties bit 0x2000 to automatic group selection. The helper initializes a local target group to sentinel value 4. If bit 0x2000 is clear and object byte +0x0b is 2 or 3, it indexes data.object.group_for_direction_two_or_three_groups (AGIDATA.OVL:0x08dd) by direction byte +0x21; if +0x0b >= 4, it instead indexes data.object.group_for_direction_four_plus_groups (AGIDATA.OVL:0x08e7). When object byte +0x01 == 1, the target group is not sentinel 4, and the target differs from byte +0x0a, it calls code.object.select_group (0x3bb7). QEMU batches object_bit_2000_002 and object_bit_2000_004 validate this behavior:

Gold Rush / AGI v3 keeps the same table values but changes the high group-count branch. Its code.object.frame_timer_update at image 0x055c sends exactly four groups directly through the four-plus table, while views with more than four groups use that table only when flag 0x14 is set. QEMU report build/gr-v3-behavior/frame_selection_gate_qemu_001.json validates exact-four view 177 selecting group 1 for direction 6 whether flag 0x14 is clear or set, and more-than-four view 39 selecting group 1 only when flag 0x14 is set.

  • In the four-plus-groups table, view 4 with direction 6 changes from group 0 to group 1 after 0x2e; after 0x2d, the same object remains on group 0.
  • In the two/three-groups table, view 5 with direction 6 changes from group 0 to group 1.
  • Direction 5 in the two/three-groups table yields sentinel 4, so no group change occurs.
  • A one-shot +0x01 = 2 delays selection until the countdown reaches 1 in a later cycle. A probe that writes +0x01 = 2 every logic cycle keeps the object on its original group, confirming the exact +0x01 == 1 gate.

QEMU probes validate the observable flag-clearing side effect of 0x49 and 0x4b: each clears its flag operand before following bytecode tests that the flag is no longer set. Later frame-timer movement probes validate visible frame-mode behavior for 0x48, 0x4a, and 0x4b.

Static analysis now ties 0x46..0x4c to the frame-cycling path. Per-cycle helper code.object.frame_timer_update (0x0563) scans active objects with (flags & 0x0051) == 0x0051; when bit 0x0020 is set, byte +0x20 is nonzero, and decrementing +0x20 reaches zero, it calls code.object.advance_frame_by_mode (0x48b3) and reloads +0x20 from +0x1f. The advance helper treats +0x23 as a frame mode: mode 0 loops forward, mode 1 advances forward and completes at the last frame, mode 2 steps backward until it reaches frame 0 and completes, and mode 3 loops backward. Completion in modes 1 and 2 sets flag +0x27, clears bit 0x0020, clears direction byte +0x21, and resets +0x23 to 0. Actions 0x49 and 0x4b also set bit 0x1000; the first advance callback after setup clears that bit and returns without changing frame.

QEMU movement batch frame_timer_001 validates the visible mode-1 path: after 0x4c seeds the interval and 0x49 starts mode 1, view 11/group 0 advances from frame 0 to frame 1. The same batch validates that 0x46 prevents the advance by clearing bit 0x0020, and 0x47 restores the advance when executed after 0x46.

QEMU movement batch frame_timer_modes_002 validates the remaining visible frame-mode actions: 0x48 mode 0 wraps view 11/group 0 from frame 1 to frame 0, 0x4b mode 2 moves from frame 1 to frame 0 and stops, and 0x4a mode 3 wraps backward from frame 0 to frame 1. The looping-mode fixtures stop the timer with 0x46 after a bytecode guard observes the expected frame through 0x32.

Movement probe move_collision_clear_skip_bit_blocks_again validates 0x44 by first setting collision-skip bit 0x0200 with 0x43, then clearing it with 0x44, and observing that the default object-object collision stop returns.

QEMU horizon probes validate 0x3f, 0x3d, and 0x3e together. With 0x3f setting [0x012d] = 100, ordinary placement at baseline 80 clamps to baseline 101. Setting bit 0x0008 with 0x3d keeps the object at baseline 80, and clearing the bit with 0x3e restores the clamp.

Movement-mode actions have compact setup contracts but richer per-cycle semantics, so the entry points are summarized in a table and the state machines are described below it.

OpcodeLabelHandlerSetup contract
0x51move_object_to0x6ce4Starts targeted object movement. Operand 0 is the object index, operand 1 is target X, operand 2 is target Y, operand 3 is an optional step-size override, and operand 4 is the completion flag.
0x52move_object_to_var0x6d61Same contract as 0x51, but target X, target Y, and optional step-size override are read through variables while the object index and completion flag remain immediate operands.
0x53approach_first_object_until_near0x6e02Starts autonomous movement for object operand 0 toward the first object entry. Operand 1 is a proximity threshold or step floor, and operand 2 is a completion flag.
0x54start_random_motion0x6e68Starts autonomous random movement for object operand 0. If the object is the first object entry, sets [0x0139] = 0; then stores [+0x22] = 1 and sets object bit 0x0010.

Actions 0x51 and 0x52 share the same targeted-movement state machine.

The handler sets object byte [+0x22] = 3, stores target X/Y in [+0x27] and [+0x28], copies old step size [+0x1e] to [+0x29], stores the completion flag in [+0x2a], clears that flag, sets object bit 0x0010, and calls helper 0x1672. If operand 3 is nonzero, it temporarily replaces [+0x1e] with that step size; completion helper 0x16b9 restores [+0x1e] from [+0x29], sets the completion flag through 0x74c6, clears [+0x22], and clears ego direction byte [0x000f] when the object is object 0.

Helper 0x1672 computes the initial direction byte [+0x21] from current position [+0x03]/[+0x05] to target [+0x27]/[+0x28] using step size [+0x1e]. The direction lookup table at DS:0x0a85 is:

target above:  8 1 2
target level:  7 0 3
target below:  6 5 4
               left near right

The center value is zero, so an object already within one step of both target coordinates completes immediately.

QEMU probes with generated logic resources confirm two valid ways this target mode can complete. A script can reissue 0x51 each cycle while the completion flag is clear; that matched QEMU for reachable horizontal and vertical targets, and for unreachable right/bottom targets that complete at the movement clamp. A one-shot 0x51 setup can also complete without script reissue when object byte +0x01 is set to 1, because the pre-movement dispatcher path 0x067a -> 0x1672 recomputes the direction and detects arrival.

For action 0x53, the setup handler sets object byte [+0x22] = 2; stores max(operand1, object[+0x1e]) in [+0x27]; stores the completion flag number in [+0x28]; clears that flag; stores sentinel 0xff in [+0x29]; and sets object bit 0x0010. The per-cycle helper at 0x0b36 computes the object’s horizontal center and the first object’s horizontal center, calls direction helper 0x16ed(object_center_x, object_y, first_object_center_x, first_object_y, object[+0x27]), and writes the returned direction to [+0x21] when its local delay permits. If the returned direction is zero, the object is already within the threshold: the helper clears [+0x21] and [+0x22] and sets the completion flag through 0x74c6. If object bit 0x4000 says the object did not move in the last position comparison, the helper temporarily chooses a random nonzero direction and computes a retry delay in [+0x29] before returning to the direct approach direction.

QEMU validates the direct autonomous mode-2 path with object 1 approaching object 0: with step 5, countdown byte +0x01 = 1, and threshold 35, object 1 stops at (50,80) when object 0 is at (80,80). An exploratory threshold 25 probe landed at (60,75), consistent with the source path that enters stuck recovery after a blocked or stationary comparison.

For action 0x54, the per-cycle helper at 0x3f5a decrements [+0x27] as a countdown; when it reaches zero, or when object bit 0x4000 reports no movement, it chooses a random direction 0..8 via helper 0x3fa3, stores it in [+0x21], mirrors it to byte [0x000f] for the first object, and reseeds [+0x27] to a random delay in the range 6..50.

A QEMU property probe validates this path without assuming a deterministic final coordinate: after setting step 5, countdown byte +0x01 = 1, and starting 0x54, the object rendered exactly at a valid final position. In the recorded run the final position was (140,112).

OpcodeLabelHandlerObserved action
0x55stop_motion_mode0x6ea1Stops the autonomous motion mode for object operand 0 by clearing object byte [+0x22]. Unlike 0x4d (clear_object_fields_21_22) it does not clear direction byte [+0x21], and unlike 0x4e (clear_object_field_22_and_global) it does not update the first-object globals.
0x56set_object_field_21_var0x6fbeStores var[arg1] in object byte [+0x21].
0x57get_object_field_210x6ffcStores object byte [+0x21] into var[arg1].
0x58set_object_bit_00020x7b9cSets bit 0x0002 in object word field [+0x25]. QEMU validates that this bypasses the rectangle-boundary crossing stop configured by 0x5a; the object reaches (50,80) instead of stopping at (30,80).
0x59clear_object_bit_00020x7bc1Clears bit 0x0002 in object word field [+0x25]. QEMU validates that clearing after 0x58 restores the rectangle-boundary crossing stop at (30,80).
0x83clear_global_01390x702fSets global word [0x0139] = 0. Source-backed main-cycle analysis shows this selects the next pre-logic mirror direction: object 0 byte +0x21 is copied to global byte [0x000f] when the selector is zero. The same cycle later restores object 0 +0x21 from [0x000f] after logic returns, so script-level probes that set object 0 direction after the pre-logic mirror are clobbered before they can seed the next cycle.
0x84set_global_0139_and_clear_object0_field_220x7041Sets global word [0x0139] = 1 and clears byte [+0x22] on the first object entry. A QEMU movement probe validates the byte [+0x22] effect by starting random motion on object 0 with 0x54, immediately executing 0x84, and observing that the object remains at its starting position.
0x85display_object_diagnostics_var0x72b5Reads an object index from var[arg0], gathers object fields [+0x03], [+0x05], [+0x1a], [+0x1c], [+0x24], and [+0x1e], formats them with string template DS:0x1713 through helper 0x2374, and displays the result through 0x1ce8.

In local SQ2 data, the template at DS:0x1713 is:

Object %d:
x: %d  xsize: %d
y: %d  ysize: %d
pri: %d
stepsize: %d

The single observed local use is in logic 99. The script accepts parsed words object or sp, prompts with message 7 (object #:), stores the number in variable 64, and then executes this action. This makes 0x85 a developer or diagnostic object-inspection command, and it independently confirms that object fields [+0x03], [+0x05], [+0x1a], [+0x1c], [+0x24], and [+0x1e] are displayed as X, Y, width, height, priority/control, and step size.

OpcodeLabelHandlerObserved action
0x93set_object_pos_dirty0x7d77Sets object fields [+0x03] = arg1 and [+0x05] = arg2, sets bit 0x0400 in [+0x25], then calls helper 0x593a.
0x94set_object_pos_dirty_var0x7dbaSame broad shape as 0x93, but fields [+0x03] and [+0x05] are loaded from var[arg1] and var[arg2]. It also sets object bit 0x0400 and calls helper 0x593a.

Actions that update object byte [+0x24]:

OpcodeLabelHandlerObserved action
0x36set_object_field_240x7a80Sets bit 0x0004 in object word field [+0x25] and writes immediate arg1 to byte [+0x24].
0x37set_object_field_24_var0x7b08Same as 0x36, but writes var[arg1] to byte [+0x24].
0x38clear_object_bit_00040x7ab0Clears bit 0x0004 in object word field [+0x25]. A QEMU probe confirms that this stops using the fixed byte [+0x24]; a view placed at baseline 80 then derives priority 7 and draws over a control-6 background.
0x39get_object_field_240x7ad5Stores object byte [+0x24] into var[arg1].

Message-display actions use the current logic resource’s message table through helper 0x21f0.

OpcodeLabelHandlerObserved action
0x65display_message0x1c06Resolve message arg0 with 0x21f0, then pass the string pointer to display helper 0x1ce8.
0x66display_message_var0x1c29Same as 0x65, but message number is var[arg0].
0x67display_formatted_message0x2250Calls setup helper 0x2b28, passes immediate operands arg0 and arg1 to helper 0x2b0d, resolves message arg2, copies or formats it into a 1000-byte stack buffer with helper 0x1f54 and maximum 0x28, sends the resulting string to formatter/display helper 0x2390, then calls cleanup helper 0x2b4f. This appears to be a positioned or configured formatted-message display action.
0x68display_formatted_message_var0x22a7Same as 0x67, but all three operands are read through variables.
0x97display_message_configured0x1c54Reads immediate message number arg0, then reads row, column, and width bytes into globals [0x0d0b], [0x0d0d], and [0x0d09] before resolving and displaying the message. A zero width is replaced with 0x1e before display. The globals are reset to 0xffff afterward.
0x98display_message_configured_var0x1c71Same as 0x97, but message number is var[arg0].

QEMU fixture text_input_002 validates that 0x65, 0x66, and 0x97 display a message/window, accept Enter through the display helper, then return to following bytecode. The same batch validates one typed-input path for 0x76: entering 42 stores byte value 42 in the destination variable. Later fixture text_ui_003 validates the formatted/positioned display variants 0x67, 0x68, and 0x98 with the same return-to-bytecode behavior. These message paths can leave text-plane pixels visible after dismissal; the QEMU fixtures issue a full picture refresh (0x1a) before comparing graphics output when the validation draw would otherwise be contaminated by that display state.

Menu/list-like UI actions use a circular list rooted at word globals [0x1d2c], [0x1d2e], and [0x1d30]. The final user-level names remain provisional; the observed structures look like a top-level list of headings with per-heading circular sublists of selectable items.

OpcodeLabelHandlerObserved action
0x9cadd_menu_heading_like0x911dResolves message arg0, allocates an 18-byte node through 0x13d6, links it into the top-level circular list rooted at [0x1d2c], stores the message pointer at [node+0x04], stores the current horizontal/text position from [0x1d24] at [node+0x08], marks [node+0x0a] = 1, clears its item-list pointer at [node+0x0c], advances [0x1d24] by the message width plus one using helper 0x4d99, clears [0x1d30], and resets [0x1d26] = 1.
0x9dadd_menu_item_like0x91cfResolves message arg0, reads immediate item id arg1, allocates a 14-byte node, links it into the current heading’s circular item list at [current_heading+0x0c], stores the message pointer at [node+0x04], stores a 1-based row/order at [node+0x06], stores a column-like value at [node+0x08], marks [node+0x0a] = 1, stores item id arg1 at [node+0x0c], and increments [current_heading+0x10].
0x9efinalize_menu_like0x92baFinalizes the current menu/list setup: if the current heading has no item list, clears [heading+0x0a]; calls helper 0x1476; sets current heading [0x1d2e] to the list root [0x1d2c]; sets current item [0x1d30] from [heading+0x0c]; and sets [0x1d2a] = 1, which makes later 0x9c/0x9d additions no-op.
0x9fenable_menu_item_like0x92eeCalls helper 0x935f(arg0, 1). The helper walks all active heading/item lists, finds item nodes whose id at [node+0x0c] equals arg0, and writes [node+0x0a] = 1.
0xa0disable_menu_item_like0x9340Calls helper 0x935f(arg0, 0), clearing [node+0x0a] for matching item ids.
0xa1mark_menu_if_flag_0e0x93b1Tests flag 0x0e through helper 0x74e4; if set, stores word [0x1d22] = 1. Its role is likely tied to the same menu/list UI state, but no direct list traversal occurs in this handler.

QEMU fixture menu_sound_001 dispatch-smokes 0x9c..0xa0 by creating a heading, adding one item, finalizing the structure, disabling and re-enabling that item id, and then drawing a known view. A separate case sets flag 0x0e, runs 0xa1, and draws.

QEMU fixture menu_interaction_001 validates the one-item interactive Enter path. The fixture builds a menu heading and enabled item id 7, finalizes the structure, sets flag 0x0e, and runs 0xa1. The following input cycle enters code.menu.interact (0x93d1); pressing Enter on the enabled item calls 0x44a9(3, 7). The generated logic then draws only when condition 0x0c 7 observes the resulting status byte. The comparison refreshes the picture before the validation draw because the menu leaves a top text strip visible.

QEMU fixture menu_edges_002 validates three additional menu edge cases. Escape exits the interactive menu without setting item status byte 7. Enter on a disabled item also leaves status byte 7 clear before Escape exits. Disabling then re-enabling the same item allows Enter to enqueue status byte 7 again. These promote 0x9c..0xa0 from setup-only dispatch evidence to behavior evidence for the tested setup, disable, and re-enable paths. A down-arrow navigation fixture was attempted twice, but the QEMU key sequence did not reach the expected second-item status path, so arrow-key navigation remains source-backed from the code.menu.interact dispatch table.

Source mapping of code.menu.interact shows the navigation model. The menu loop waits through code.input.wait_for_event_or_tick, then normalizes the event record through the Enter/Escape helper and the display-adapter remap helper. Event type 1 is interpreted as raw Enter/Escape: Enter enqueues a type-3 event with the current item’s id only if the item enable word [item+0x0a] is nonzero; Escape leaves without enqueueing a selection. Event type 2 is interpreted as a movement code. Movement values 1..8 dispatch through the word table at 0x9526, then the loop stores the current heading/item in [0x1d2e] and [0x1d30] before waiting again. Item navigation does not skip disabled items; the enable word is tested when Enter is pressed. Left/right heading navigation skips headings whose enable word is zero, while Home/End-like heading jumps select the root or root-previous heading directly.

Movement valueRaw key table sourceTargetSource-backed effect
10x48000x9492Move to previous item in the current heading ([item+0x02]).
20x49000x94a6Move to the first item in the current heading ([heading+0x0c]).
30x4d000x94b2Move to the next enabled heading ([heading+0x00] loop), restoring that heading’s remembered item from [heading+0x0e].
40x51000x94cbMove to the last item in the current heading ([first_item+0x02]).
50x50000x94daMove to the next item in the current heading ([item+0x00]).
60x4f000x94e5Move to the root list’s previous heading ([root+0x02]) and restore its remembered item.
70x4b000x94f6Move to the previous enabled heading ([heading+0x02] loop), restoring that heading’s remembered item.
80x47000x9509Move to the root heading ([0x1d2c]) and restore its remembered item.

Text-window and input-line actions:

OpcodeLabelHandlerObserved action
0x69clear_text_rect0x7714Reads immediates arg0, arg1, and arg2; transforms arg2 through helper 0x78ad; then calls code.text.clear_rows (0x2b78) with top row arg0, bottom row arg1, left column 0, right column 0x27, and the transformed attribute. QEMU validates that rows 5..6 clear logical Y 40..55 to visual color 0 without repainting the picture.
0x9aclear_text_rect_bounds0x7753Reads five immediates. The first four are passed as top row, left column, bottom row, and right column to code.text.clear_bounds (0x2bc4); the fifth is transformed through helper 0x78ad and passed as the text attribute. Helper 0x2bc4 saves the current cursor, calls BIOS int 10h scroll/clear-window service AH=0x06 with those bounds, then restores the cursor. In the EGA target, QEMU validates text columns as four logical pixels wide and text rows as eight logical pixels tall.
0x6aenable_text_attr_mode_17570x76caCalls prompt/input cleanup helper 0x382e, sets byte [0x1757] = 1, derives text attributes through 0x77d5 using globals [0x05cd] and [0x05cf], calls helper 0x9803, then clears or fills a text rectangle through 0x2b78. QEMU validates that in the normal EGA path this leaves the visible logical surface cleared to black, and a following transient-object validation draw is not visible while this alternate text-attribute mode remains active.
0x6bdisable_text_attr_mode_17570x7702Calls prompt/input cleanup helper 0x382e, then helper 0x78cb, which clears byte [0x1757], recomputes text attributes from [0x05cd] and [0x05cf], calls helper 0x9806, redraws the status-line-like area through 0x34bd, and refreshes the input-line-like area through 0x38d7. QEMU validates that after 0x6a, running 0x6b and then refreshing the picture allows the normal transient-object validation draw to appear again.
0x6cset_input_prompt_char0x38b4Resolves current-logic message arg0 through 0x21f0 and stores the first byte of that message in [0x05d7]. Helpers 0x37f7, 0x382e, and 0x38d7 test [0x05d7] while drawing or erasing the prompt/input marker. QEMU validates the empty-message case by first setting a nonempty marker, then setting an empty message; a following input-line redraw stays black with no prompt marker glyph.
0x6dset_text_window_pair0x77afReads immediates arg0 and arg1, then calls helper 0x77d5(arg0, arg1). That helper stores derived values in globals [0x05d1], [0x05cd], and [0x05cf] using helpers 0x7803, 0x78a1, and 0x78ad. In the observed EGA path, setting pair (0, 1) while normal text mode is active stores [0x05cd]=0 and [0x05cf]=0xff; a following 0x6a recomputes [0x05d1] with [0x1757]=1, producing a packed low-byte text attribute 0xf0. QEMU validates the resulting alternate text-surface clear as full-screen visual color 15.
0x6eshake_screen_like0x7a00Reads an immediate count into CL and performs a display-shake-like loop. Display mode [0x1130] == 3, 2, or 4 delegates to overlay/helper paths 0x99b8, 0x9be3, or 0x9916. Otherwise it chooses base byte [0x1779] = 0x70 when hardware selector [0x112e] == 0, or 0x38 otherwise, then repeatedly writes CRT controller registers 0x02 and 0x07 through ports 0x3d4/0x3d5. Each step reads bytes from the small table at 0x177a, adds [0x1365] to the register-2 value and [0x1779] to the register-7 value, waits for timer word [0x0129] to change, advances until the register-7 value returns to the base, and repeats for the count. QEMU dispatch-smokes the action returning to following bytecode; the transient hardware timing effect is source-backed.
0x6fset_input_line_config0x78f0Reads immediates arg0, arg1, and arg2; stores arg0 in [0x05dd], arg0 + 0x15 in [0x05df], arg1 in [0x05d5], and arg2 in [0x05db]. It also computes [0x1379] from arg0: normally arg0 << 3, but in display mode [0x1130] == 2 it stores arg0 * 6 for values 0 or 1, and clamps larger values to 6. Nearby redraw helpers use these globals for the input-line/status text areas, so the user-level name remains provisional.
0x70show_status_line_like0x3547Sets word [0x05d9] = 1 and calls helper 0x34bd, which redraws a status-line-like text area using helpers 0x2b28, 0x7989, 0x2ba6, 0x2b0d, 0x2390, 0x79c3, and 0x2b4f. QEMU validates that after 0x6f(0, 0, 5), showing the status line writes visible color-15 pixels in logical row 40..47, the row configured by operand 2.
0x71hide_status_line_like0x355cSets word [0x05d9] = 0, then calls code.text.clear_row (0x2ba6) with row [0x05db] and attribute 0, clearing the associated text row. QEMU validates that after 0x6f(0, 0, 5), hiding the status line clears logical Y 40..47 to visual color 0 without repainting the picture.
0x72set_string_slot_from_message0x0d37Computes destination 0x020d + arg0 * 0x28, resolves current-logic message arg1, and copies up to 0x28 bytes into that fixed-size string slot through helper 0x4de8.
0x73prompt_string_to_slot0x0c44Reads fixed string slot arg0, message number arg1, row-like byte arg2, column-like byte arg3, and max-length byte arg4. It clears the destination string slot, optionally positions text with 0x2b0d(arg2, arg3) when arg2 < 0x19, displays the resolved current-logic message, accepts edited input through code.input.edit_string (0x0da9), then restores the input-line/status display as needed. The accepted length is min(arg4 + 1, 0x28).
0x74set_string_slot_from_table0x0d70Computes destination 0x020d + arg0 * 0x28, reads a word pointer from DS:0x0c8f + arg1 * 2, and copies up to 0x28 bytes from that pointer into the string slot through helper 0x4de8. In the static SQ2 AGIDATA.OVL, the sampled table entries at 0x0c8f are zero-filled and this opcode was not encountered in the current local logic scan. QEMU fixture input_key_string_behaviour_001 validates the copy semantics by patching only the generated fixture’s AGIDATA.OVL so table entry 0 points to a synthetic look string.
0x75parse_string_slot0x1958Clears flags 2 and 4, reads a string-slot index arg0, and if arg0 < 12 parses fixed string slot 0x020d + arg0 * 0x28 through helper 0x18ac. The parser normalizes the string, looks words up in WORDS.TOK, and fills parsed-word tables used by condition 0x0e (input_word_sequence).
0x76prompt_number_to_var0x71edDisplays current-logic message arg0 as a prompt, accepts/edits up to four characters through helper 0x0da9, parses the resulting buffer as a decimal number through helper 0x4e8d, and stores the low byte in var[arg1]. It has two display paths: one using text helpers 0x2b0d, 0x1f54, 0x2390, 0x37f7, and 0x38d7, and another using helpers 0x9c52 and 0x9d93 when display mode [0x1130] == 2 and [0x0d0f] == 0.
0x77disable_input_line_like0x386fSets word [0x05d3] = 0; unless display mode [0x1130] == 2, it calls helper 0x382e and clears a text area through code.text.clear_row (0x2ba6) with row [0x05d5] and attribute 0. QEMU validates that after 0x6f(0, 5, 22), disabling the input line clears logical Y 40..47 to visual color 0 without repainting the picture.
0x78enable_input_line_like0x3898Sets word [0x05d3] = 1; unless display mode [0x1130] == 2, calls helper 0x38d7 to redraw the input-line-like display. QEMU validates that after 0x6f(0, 5, 22), enabling the input line clears/redraws logical Y 40..47 to visual color 0 when the prompt marker and input buffers are empty.
0x89refresh_input_line0x3753If the input line is enabled through word [0x05d3], refreshes the input-line display. In display mode [0x1130] == 2 with word [0x0d0f] == 0, it displays the string at 0x1e2e (“ENTER COMMAND”) through the alternate display helpers, clears or rewrites the current input character byte [0x001c], and passes that byte to helper 0x3652. In other modes it calls helper 0x37a5, which appends bytes from the buffer/string at 0x0fce into the visible input buffer 0x0fa4 until the input length word [0x0ff8] reaches that string’s length. QEMU validates the normal EGA path by typing look plus Enter, then observing that 0x89 repaints visible input glyph pixels from the entered source buffer. A failed pre-Enter probe showed that unaccepted live edit characters are not sufficient for this replay path.
0x8aerase_input_line0x3726Erases the visible input-line buffer by repeatedly passing byte 0x08 to helper 0x3652 while word [0x0ff8] is nonzero. In display mode [0x1130] == 2, it skips the erase loop when word [0x0d0f] == 0. QEMU validates the normal EGA path by first confirming typed characters are visible on the configured input row, then running 0x8a each cycle and matching logical Y 40..47 back to black.
0xa3set_global_0d0f0x3939Sets word [0x0d0f] = 1. Helper 0x3652 uses this global while computing input-line width: when set, it starts from a fixed 0x24 character cap instead of deriving the cap from fixed string slot 0. QEMU validates this by using a long blank string slot 0 and observing accepted live-input glyphs on the wrapped input row only after 0xa3.
0xa4clear_global_0d0f0x394bClears word [0x0d0f]. QEMU validates the visible counterpart to 0xa3: after 0xa3 followed by 0xa4, the same long blank string slot 0 leaves the wrapped input row without live-input glyph pixels.
0xa9close_text_window_state0x1f2bIf word [0x0d1d] is nonzero, calls helper 0x560c([0x0d23], [0x0d25]), which restores the display rectangle saved by the modal message-window opener at 0x1d96. That opener closes any prior active window, computes packed rectangle words [0x0d23] and [0x0d25], calls helper 0x5590 to save/fill/draw the window region, and only then sets [0x0d1d] = 1. After the conditional restore, 0xa9 clears words [0x0d0f] and [0x0d1d]. QEMU validates the unconditional [0x0d0f] clear by setting 0xa3, running 0xa9 with no active saved window, and observing the same narrowed input-width behavior as 0xa4; the active restore lifecycle is source-backed from the producer/consumer code.

code.input.edit_string (0x0da9) is the shared line editor used by both 0x73 and 0x76. Static disassembly shows that it clamps the requested maximum length to 0x28, copies the destination buffer into a local edit buffer, draws the existing text, waits for a nonzero/non-0xffff event through code.input.wait_event (0x45d7), and dispatches key values through data.input.edit_key_table (0x0e64). Observed table entries map 0x08 to single-character backspace, 0x03 and 0x18 to clear-current-input, 0x0d to accept by zero-terminating the local buffer and copying it back to the destination slot, and 0x1b to cancel without copying. Other input bytes append when space remains and are echoed through the character display helper.

QEMU fixture prompt_string_003 validates the 0x73 path. One case proves the prompt returns after typed text plus Enter; a second initializes string slot 1 from message text look, runs 0x73 into slot 0, then conditionally draws only if the two string slots compare equal. The matched capture proves that typed text was copied into the destination slot. Like the formatted-message probes, the fixture refreshes the picture with 0x1a before the validation draw because the text overlay can otherwise remain visible in the captured display.

QEMU fixture text_ui_003 originally dispatch-smoked 0x77, 0x78, 0x89, and 0x8a as an input-line toggle/refresh/erase group, and 0xa9 as text-window cleanup. Later focused batches promote the input-line opcodes and the unconditional [0x0d0f] clear side of 0xa9 to the behavior-level observations above. Follow-up fixture text_rect_clear_behaviour_003 validates the display-surface effect of 0x69 and 0x9a without relying on a picture refresh: formatted message text is acknowledged, the clear action runs, and the captured screen matches only when the expected display surface includes the black text-cell rectangle written by the BIOS clear helper.

QEMU fixture text_hide_clear_behaviour_001 extends that display-surface coverage to the single-row text clear used by the status and input line handlers. The two cases first display formatted text on row 5, configure the row globals through 0x6f, run either 0x71 or 0x77, and then validate that logical Y 40..47 is black before the final object draw. This promotes 0x71 (hide_status_line_like) and 0x77 (disable_input_line_like) beyond dispatch-smoke coverage for the normal EGA display path.

QEMU fixture text_enable_attr_behaviour_002 promotes two more handlers from that cluster. Case input_line_enable_clears_configured_row sets the prompt marker to an empty message, displays formatted text on row 5, configures the input row with 0x6f(0, 5, 22), then runs 0x78; the capture matches when the configured row is black before the final object draw. Case text_attribute_enable_clears_visible_surface runs 0x6a and then attempts the usual transient-object validation draw; the original capture remains a fully black logical surface, so the case compares only the visible surface and does not compose the validation object. This is evidence for the alternate text-attribute mode surface clear, not for ordinary graphics drawing.

QEMU fixture text_prompt_attr_behaviour_001 promotes two neighboring handlers. Case text_attribute_disable_restores_picture_draw runs 0x6a, then 0x6b, then refreshes the picture and draws the validation object; the match confirms that leaving alternate text-attribute mode restores the normal graphics path. Case input_prompt_empty_message_suppresses_marker first sets a nonempty prompt marker, displays/acknowledges formatted text on row 5, sets the prompt marker from an empty message, and runs 0x78; the capture matches only when the configured input row is black with no prompt-marker glyph.

QEMU fixture text_status_002 originally dispatch-smoked low-risk text/status/input handlers in this cluster: 0x6d for text-attribute configuration; 0x6e for a one-count screen-shake return; 0x70 for status-line show; and 0x79 for key-event mapping table insertion. Later focused batches promote 0x6d and 0x70 to the behavior-level observations above. The source-backed details still matter: 0x6f stores its first operand in [0x05dd], stores operand + 0x15 in [0x05df], and derives display offset global [0x1379] from that first operand. An intermediate QEMU run with first operand 1 shifted the later validation draw relative to the local renderer, so the final smoke fixture uses operand 0 and leaves non-default display-offset semantics for a dedicated behavior probe.

QEMU fixture input_key_string_behaviour_001 adds that dedicated behavior coverage. Case input_line_config_operand1_offsets_display_by_8 runs 0x6f(1, 0, 22), refreshes the picture, and draws a view at script baseline 80; the original interpreter capture matches the local renderer only when the expected baseline is 88, confirming the observed arg0 << 3 offset in normal display mode. Case mapped_key_sets_status_byte installs 0x79('x', 0, 7), sends key x, and draws only when condition 0x0c observes status byte [0x1218 + 7]. This confirms the path through code.input.map_key_event (0x4c3d) and helper 0x4566, where a matching type-1 event becomes type 3 and sets the mapped status byte.

QEMU fixture diagnostics_system_001 validates that 0x87, 0x88, and 0x8d display their diagnostic/pause/version messages, accept Enter, and return to following bytecode. The same batch originally smoke-exercised several global/system handlers. Separate focused or source-backed work now covers the formerly smoke-only actions: 0xab/0xac, 0xa3/0xa4, and the object-0 motion-byte effect of 0x84 have focused QEMU evidence; 0x83, 0x8e, 0xaa, and 0xad are covered from source because their main effects are global/timing/save-selector state not cleanly exposed by ordinary single-room script probes.

Resource/table actions outside the main object table:

OpcodeLabelHandlerObserved action
0x5aset_rect_bounds_01310x7b4eSets word [0x013d] = 1 and stores four immediate operands as words at [0x0131], [0x0133], [0x0135], and [0x0137]. Helper 0x7be6 later tests whether an (x,y) pair lies strictly inside those bounds. QEMU movement probes validate that these bounds stop an object before it crosses the configured rectangle.
0x5bclear_rect_bounds_01310x7b8aClears word [0x013d]. QEMU movement probe rect_bounds_clear_001 validates that clearing after 0x5a lets the same object cross the former rectangle boundary and reach its target.
0x5cset_entry_0971_marker_ff0x7538Resolves a 3-byte entry from the table rooted at [0x0971] using immediate index arg0, validates it against end pointer [0x0973], and stores byte [entry+0x02] = 0xff. Invalid indices report error code 0x17 through helper 0x3fe8.
0x5dset_entry_0971_marker_ff_var0x7554Same as 0x5c, but the entry index is read from var[arg0].
0x5eclear_entry_0971_marker0x7570Resolves a 3-byte entry from the table rooted at [0x0971] using immediate index arg0 and stores byte [entry+0x02] = 0.
0x5fset_entry_0971_marker_from_var0x758cResolves a table entry using immediate index arg0, then stores var[arg1] in byte [entry+0x02].
0x60set_entry_0971_marker_from_var_var0x75b7Resolves a table entry using index var[arg0], then stores var[arg1] in byte [entry+0x02].
0x61get_entry_0971_marker_to_var0x75e2Resolves a table entry using index var[arg0], then stores byte [entry+0x02] into var[arg1].
0x7cshow_inventory_selection0x31d8Builds a temporary 8-byte-per-row list from the 3-byte table rooted at [0x0971], including only entries whose marker byte [entry+0x02] is 0xff. For each included entry it stores the original table index, an item-name pointer computed as [0x0971] + word[entry+0x00], and a two-column row/column position. It displays the header string at 0x0f26 (“You are carrying:”), a fallback string at 0x0f1e (“nothing”) when no rows exist, and either a selection prompt at 0x0f38 or a noninteractive return prompt at 0x0f5d depending on flag 13. In interactive mode Enter writes the selected table index to absolute byte DS:0x0022; Escape writes 0xff there. Because script byte variables begin at DS:0x0009, this is exposed to logic bytecode as variable 0x19. QEMU batch inventory_selection_001 validates Enter storing selected carried-entry index 0, Escape storing 0xff, and the noninteractive acknowledgement path returning to following bytecode.
0x79map_key_event0x4c3dCombines arg0 and arg1 into a little-endian key/event word, scans up to 39 four-byte slots rooted at 0x0145 for an empty first word, and stores the combined word at slot offset +0 and arg2 at slot offset +2. Helper 0x4566 later uses this table to convert matching type-1 event records into type-3 records carrying the mapped value.
0x81display_view_resource_text_like0x5ebfDisplays or previews a view-like resource selected by immediate arg0. Helper 0x5edb disables resource-event recording, ensures the resource is loaded, temporarily sets [0x0f18] = 1 around 0x39f7, builds a temporary object-like record through 0x3ae7, may render/cache it through helpers 0x9097, 0x9db0, 0x9db6, and 0x5762 if enough memory is available, displays a string pointer derived from the loaded resource through 0x1ce8, optionally discards a view it loaded only for this display, then re-enables recording.
0xa2display_view_resource_text_like_var0x5e9bSame as 0x81, but the resource number is read from var[arg0]. The action table metadata byte is 0x01, but the handler itself clearly performs the variable lookup. Its helper also disables resource-event recording around the temporary load/display/discard sequence.
0x99discard_view_var0x3ee9Same helper path as 0x20 (discard_view), but the view-like resource number is read from var[arg0].

QEMU fixture view_resource_display_001 validates that 0x81 and 0xa2 display/preview view resource 11, accept Enter, return to following bytecode, and allow a subsequent picture refresh plus validation draw.

Interpreter/session control actions:

OpcodeLabelHandlerObserved action
0x86confirm_and_restart_like0x027fStops active sound state through helper 0x5234, reads immediate arg0, and if arg0 == 1 calls helper 0x02ae immediately. Otherwise it displays string pointer 0x05e3 through 0x1ce8 and calls 0x02ae only if the display helper returns 1. Helper 0x02ae calls 0x8275 and then 0x00ae(0). This is a confirmation-gated restart/exit-like action; the final user-level name is still provisional.
0x87show_heap_status0x14bdFormats a diagnostic heap/status message into a 100-byte stack buffer with helper 0x2374, then displays it with 0x1ce8. The format string at 0x0a19 is heapsize: %u, now: %u max: %u, rm.0, etc.: %u, and max script: %d. The values are derived from globals [0x0a55], [0x0a57], [0x0a59], [0x0a5b], [0x0a5f], and [0x170f].
0x88pause_game_message0x0257Sets word [0x0615] = 1, calls helper 0x4482, stops sound through 0x5234, displays the fixed message at 0x0c0d (“Game paused. Press Enter to continue.”), then clears [0x0615].
0x8bcalibrate_joystick0x613cInitializes joystick-related globals [0x15c5] and [0x15c7] to 0xffff, calls helper 0x63be, and if joystick axes/state globals [0x15c1] and [0x15c3] are nonzero, displays the message at 0x1549, which starts “Please center your joystick.” It waits for Enter to continue or Escape to cancel. On acceptance it closes any active text window, computes min/max centered bounds around [0x15c1] and [0x15c3] into [0x15c9], [0x15cd], [0x15cb], and [0x15cf], then repeatedly calls helper 0x6425 while calibration records at 0x1531 or 0x153d remain active. It finishes by calling helper 0x4482.
0x80confirm_restart_game0x2472Stops active sound state, clears the prompt/input line, and either proceeds immediately if flag 16 is set or displays the confirmation text at 0x0adb (“Press ENTER to restart the game… Press ESC to continue this game.”). On confirmation it calls input/display cleanup helper 0x3726, preserves flag 9, resets heap/update-list state through 0x1485, calls helpers 0x0fa5 and 0x30d6, sets flag 6, restores flag 9 if it had been set, clears timer/event words [0x0129] and [0x012b], reloads logic [0x1d12] if configured, and calls menu/list refresh helper 0x930e. It then redraws the input prompt through 0x37f7. When restart is accepted it returns zero to the dispatcher.
0x7dsave_game_state0x2753Save-game-state path. It marks modal state [0x0615] = 1, temporarily changes byte [0x0d15] to 0x40, asks helper code.save.select_slot_or_path(0x73) for a selected save slot/path, optionally displays the confirmation text at 0x0db6, creates file 0x1c8c through DOS wrapper 0x5cad, writes a 31-byte description/header from 0x1c6c, then writes five length-prefixed memory blocks through helper 0x28c6: 0x05e1 bytes from 0x0002, [0x096f] bytes from [0x096b], [0x0975] bytes from [0x0971], [0x0141] * 2 bytes from [0x1707], and the 0x1364-sized block from 0x0985. Local SQ2 save files confirm block lengths 1505, 903, 328, 200, and a variable fifth block. Create failure displays the directory-full/write-protected text at 0x0df0 and returns to the following bytecode. Header or block write failure closes and deletes the partial file, displays the disk-full text at 0x0e46, restores text state, restores [0x0d15], clears [0x0615], and returns to the following bytecode.
0x7erestore_game_state0x2512Restore-game-state path. It marks modal state [0x0615] = 1, saves and temporarily changes byte [0x0d15], asks helper code.save.select_slot_or_path(0x72) for a selected restore slot/path, optionally displays the confirmation text at 0x0d34, opens file 0x1c8c through DOS wrapper 0x5cce, seeks past a 31-byte description/header, then reads the same five length-prefixed block families into 0x0002, [0x096b], [0x0971], [0x1707], and 0x0985 through helper 0x26b0. Open failure displays the can’t-open-file text at 0x0d73, restores modal/text state, and returns to the following bytecode. Block read failure closes the file, displays the error text at 0x0d87, and calls 0x02ae. On success it restores hardware/display byte variables from [0x112e] and [0x1130], sets flag 11 according to hardware mode, calls code.restore.replay_resource_events, refreshes display/resource state through 0x4c23 and 0x30d6, clears the caller return pointer so execution restarts through the restored state, calls menu/list refresh helper 0x930e, restores text state, restores [0x0d15], and clears [0x0615].
0x8eset_global_0141_and_refresh0x716aStores immediate arg0 as data.event.pair_capacity, then wraps code.event.reset_pair_buffer with update-list flush/rebuild calls 0x6a54 and 0x6a8e. Source-backed reset semantics: if capacity is positive and no pair buffer exists, code.event.reset_pair_buffer allocates capacity * 2 bytes, stores the pointer in data.event.pair_buffer_base, initializes allocator state, resets data.event.pair_buffer_write to the base pointer, and clears data.event.pair_count.
0x8ctoggle_display_mode_bit0x794cIf word [0x112e] == 0, byte variable 0 is nonzero, and display mode word [0x1130] is neither 2 nor 3, calls helper 0x1364, toggles bit 0 of word [0x1130], and refreshes display state through helpers 0x2b28, 0x5528, 0x2b4f, and 0x681c. A QEMU memory probe with SIERRA -p -c and patched guard words confirmed the branch executes and toggles [0x1130] from 0 to 1. The same probe showed replay excludes a picture drawn while flag 7 was set or after 0xab/0xac rollback. Source inspection now indicates the visible row-interleaved background is a CGA-mode remapping of the replayed recorded picture, not the excluded picture surviving. Because the handler requires [0x112e] == 0, this behavior is outside the full 16-color EGA target path.
0x8fverify_game_signature0x0e7eReads immediate message number arg0, resolves that current-logic message through 0x21f0, copies up to seven bytes into absolute buffer 0x0002 with 0x4de8, then calls helper 0x5b49. Helper 0x5b49 compares bytes at 0x0002 against an embedded SQ2\0 string at code offset 0x5b6c, calling helper 0x02ae on the first mismatch. The one observed local use is in logic 140 immediately before action 0x6f (set_input_line_config), consistent with a game-signature/configuration guard.
0x90append_message_to_log_file0x828fReads immediate message number arg0. If global file handle [0x1823] is 0xffff, helper 0x833f opens or creates the file named at 0x1825 (logfile) and seeks it to the end. The handler then appends a formatted room/input-line record using template 0x1809 (Room %d\nInput line: %s\n) with byte variable 0 and string/input buffer 0x0fce, resolves message arg0 through 0x21f0, formats it into the same stack buffer through 0x1f54, appends it, and closes the file handle with 0x5d52. If opening fails, it returns after consuming the operand.
0x91save_logic_resume_ip0x1335Stores the current bytecode pointer SI into word [current_logic+0x06], where current_logic is the record pointed to by [0x0981].
0x92restore_logic_entry_ip0x134aRestores word [current_logic+0x06] from [current_logic+0x04].
0x95enable_action_trace_window0x8c91If word [0x1d10] is nonzero, returns SI + 1, consuming one byte beyond the opcode. Otherwise it calls helper 0x8cae, which starts an action-trace display only when flag 10 is set: it sets [0x1d10] = 1, computes a text-window rectangle from input-line base row [0x05dd], trace offset [0x1d08], and trace height [0x1d0a], stores the derived bounds in [0x1d14], [0x1d16], [0x1d18], [0x1d1a], [0x1d1c], and [0x1d1e], then draws a boxed area through helper 0x5590. QEMU validates the flag-set enabled path by observing the red-bordered, white-filled trace window and black trace text.
0x96configure_action_trace_window0x8d3dReads three immediates into [0x1d12], [0x1d08], and [0x1d0a], clamping [0x1d0a] upward to at least 2. The first value names an optional logic resource used by the action-trace formatter around 0x8e0b; the second and third values control the trace window’s row offset and height. Restart and room-switch paths also reload logic [0x1d12] when it is nonzero, so this configuration participates in both trace display and session reset. QEMU validates 0x96(0,1,2) feeding the enabled trace-window draw path.

QEMU fixture system_dialog_001 validates several control paths in this cluster. 0x8f returns when the resolved message begins with the expected SQ2 signature. 0x80 and 0x86(0) display confirmation prompts and return to following bytecode when Escape cancels. 0x8b follows the no-joystick path under the current QEMU environment and returns. 0x8c returns without toggling when byte variable 0 is zero. Two later display-mode replay cases patch the hardware/mode guard words and launch with SIERRA -p -c; screenshot comparison validates the observable CGA row-interleaved remapping of the recorded picture, while the paired QEMU memory probe validates the internal replay log rather than relying on the screenshot alone. 0x96 followed by 0x95 dispatch-smokes the trace-window configuration path with flag 10 clear. Later fixture trace_window_enable_002 sets flag 10, configures the trace window with 0x96(0, 1, 2), and runs 0x95; the original-engine capture matches bounded checks for a red border, white fill, and black trace text. The trace window then becomes active, so the dispatcher may pause before the next action and format the current opcode/operands into that window.

QEMU fixture file_log_001 validates that 0x7d and 0x7e open their save/restore selector paths and return after Escape cancellation. It also dispatch-smokes 0x90 by appending a formatted record to the DOS logfile and returning to following bytecode.

The shared save/restore selector helper code.save.select_slot_or_path (0x85e5) is responsible for the modal UI around choosing a slot or path. It captures whether the prompt marker was visible, erases the marker, pushes text attribute state, stops active sound, sets a text attribute pair, and calls the path prompt helper at 0x8705. If runtime buffer [0x0e72] is empty, that helper displays the save or restore path question, edits path buffer 0x1962 through modal helper 0x8794, and validates the path with 0x5bdd; helper 0x86a3 displays the insert-disk style message when the selected drive/path is not available. Slot helper 0x8814 scans up to 12 numbered save files through summary reader 0x8b9f, displays their 31-byte descriptions, highlights the current row with glyph 0x1a, and handles Enter, Escape, and movement events 1 and 5 for accept, cancel, up, and down. In save mode, accepting an empty description slot prompts for a new 31-byte description into 0x1c6c before the caller creates the file. On exit, 0x85e5 formats the selected filename into 0x1c8c, restores text state, and redraws the prompt marker if it had been visible. A zero return means cancel/no selection; a nonzero return lets the caller perform actual file I/O.

Follow-up fixture log_file_contents_001 ran the log append case alone, then converted the post-run qcow2 disk image to raw and extracted LF00000\LOGFILE with mtools. The file bytes were:



Room 0
Input line:
LOG

This matches the source-derived 0x90 format: leading newlines, current room, empty input-line buffer, and the resolved current-logic message.

Miscellaneous actions:

OpcodeLabelHandlerObserved action
0x7asetup_transient_object0x2c7aCopies seven immediate operands into globals 0x0eae..0x0eb3, with operand 6 shifted into the high nibble of 0x0eb3, then calls helper 0x2d52. The staged values select view resource, group, frame, X, Y, and priority/control nibbles. Helper 0x2d52 records event pair (5, 0) followed by the three staged byte pairs, initializes a transient object-like record at 0x0eb4 through 0x3ae7, 0x3bb7, and 0x3ccb, places it with 0x593a, draws/marks it through 0x57cf, rebuilds update lists, and calls 0x5762.
0x7bsetup_transient_object_var0x2ccaSame as 0x7a, but all seven operands are read through variables before the seventh value is shifted into the high nibble of 0x0eb3. The replay event sequence is the same four-pair packet emitted by helper 0x2d52.
0x82random_range_to_var0x5009Reads low bound arg0, high bound arg1, and destination variable index arg2. Calls helper 0x71c0, takes the returned value modulo (high - low + 1), adds low, and stores the low byte in var[arg2]. Helper 0x71c0 seeds from BIOS timer interrupt 1a when needed and advances a 16-bit state at 0x1711.
0x8dshow_interpreter_version0x733cDisplays the static interpreter identification string at 0x0aab through helper 0x1ce8. In the sampled SQ2 overlay this string reads Adventure Game Interpreter followed by Version 2.936.

Remaining table entries:

OpcodeLabelHandlerObserved action
0x7fnoop0x5051Performs no state change and returns the current bytecode pointer unchanged. This opcode was present in the action table but not encountered in the local SQ2 logic scan.
0x9bnoop_20x4c15Consumes two operand bytes and performs no state change. The handler returns SI + 2.
0xaacopy_save_description_to_string_slot0x2726Reads immediate string-slot index arg0, computes destination string slot 0x020d + arg0 * 0x28, and copies up to 0x1f bytes from runtime save-description buffer 0x0e72 into that slot through helper 0x4de8. Save/restore handlers test byte [0x0e72] after slot/path selection, so this action exposes the last selected/entered save-description buffer to logic string storage. A previous QEMU attempt patched static AGIDATA.OVL bytes at 0x0e72, but that did not populate the interpreter’s runtime data segment; direct dynamic validation would need to drive the save/restore selector path that fills the buffer.
0xabsave_event_buffer_count0x718bCopies data.event.pair_count to data.event.saved_pair_count. This marks a rollback point in the resource-event pair log.
0xacrestore_event_buffer_count0x719dRestores data.event.pair_count from data.event.saved_pair_count, then recomputes data.event.pair_buffer_write = data.event.pair_buffer_base + data.event.pair_count * 2.
0xadincrement_global_15300x602fIncrements byte [0x1530] and returns the current bytecode pointer. Source-backed keyboard-IRQ analysis shows [0x1530] is a nonzero gate for selected scan-code release paths: when an enabled tracked key in range 0x47..0x51 has its pressed latch set, release clears the latch and, if [0x1530] != 0, enqueues event (type=2, value=0) through code.input.enqueue_event (0x44a9). Local model tools/agi_input.py pins the latch semantics and the 8-bit increment wraparound.
0xaerebuild_priority_table_from_y0x4d10Reads immediate row/value arg0, clears word [0x124a], and rebuilds the 168-byte priority/control table at 0x127a. Entries below arg0 are set to 4; entries from arg0 upward are assigned a rising value starting at 5, using a scale derived from (0xa8 - arg0) * 0xa8 / 10, and capped at 0x0f. Helper 0x4cbb later maps priority/control values back through this table when [0x124a] == 0.
0xafnoop_1_table_count0x5051Uses the same no-op handler as action 0x7f, returning the bytecode pointer it was given. The action table gives this opcode one fixed operand byte, so table-driven static scans skip one byte, but the handler itself does not read or advance past that operand. This opcode was not encountered in the local SQ2 logic scan.

Gold Rush v3 extra action slots

The local Gold Rush / AGI 3.002.149 action dispatcher accepts entries through 0xb5 using the v3 table at AGIDATA.OVL:0x0440. KQ4D / AGI 3.002.102 has the same v3 action-table shape at AGIDATA.OVL:0x0620. The decoded Gold Rush scripts observed so far do not use these slots, so the following descriptions are source-backed from handler disassembly and cross-references rather than QEMU behavior probes.

OpcodeLocal labelGR handlerObserved source effect
0xb0reserved_noop_v3_00x5286Generic no-op/return handler with no operands.
0xb1set_menu_interaction_gate0x970bReads one immediate byte, zero-extends it, and stores it in word [0x0403]. GR code.menu.interact (0x9724) tests this word first and returns immediately while it is zero; nonzero values allow the existing menu interaction path to run after 0xa1 has set the menu-request flag. QEMU report build/gr-v3-behavior/menu_gate_suite.json validates zero as matching a blocked control and nonzero as producing a distinct modal-menu capture.
0xb2reserved_noop_v3_20x5286Generic no-op/return handler with no operands.
0xb3reserved_noop_v3_4args0x5286The table declares four fixed operands, but the handler itself performs no state change.
0xb4reserved_noop_v3_2varargs0x5286The table declares two variable operands with metadata 0xc0, but the handler itself performs no state change.
0xb5clear_key_release_event_gate0x63b0Stores zero in byte [0x0405]. GR’s shared action 0xad stores one in the same byte, and the GR keyboard IRQ hook (0x63b8) tests [0x0405] before enqueueing a type-2 zero event on selected key-release paths. Local model tools/agi_input.py covers the GR set/clear gate against the shared tracked-key latch state machine.

QEMU fixture display_mode_replay_uses_rolled_back_event_count validates the observable pair-log effect of 0xab and 0xac. The script saves the current pair count, records a later picture operation, restores the saved count, then enters display-mode replay. The memory-backed replay notes show the rolled-back picture is absent from the active pair buffer, and the automated capture matches the expected replayed-picture state.

Local SQ2 scan

The local helper tools/disassemble_logic.py extracts logic resources, applies the dispatch tables, and prints decoded bytecode. It detects dispatch-table bases from the observed operand metadata signature, so v3 inputs whose tables move within AGIDATA.OVL can still be decoded after their resource container is recognized. It also has a --stats mode for linear opcode counts. This is still a static bytecode listing, not a live execution trace.

One LOGDIR entry decodes to VOL.0:0x1ffff but does not have a valid volume record header; the stats mode reports this as logic 141 and skips it.

Using the action table operand counts and the condition table operand counts, the current local scan of present SQ2 logic resources found these structural byte counts:

ByteCountMeaning
0x00144End of the current execution path. Later bytecode in the same logic can still be reached by jumps.
0xfe365Relative jump.
0xff2224Conditional block marker.

Frequently encountered action opcodes at statement boundaries included:

OpcodeLabelCountFixed operands
0x03assignn20192
0x65display_message13011
0x2bset_object_subresource5322
0x29set_object_resource5202
0x23activate_object5161
0x2fset_object_derived_resource_24662
0x0cset_flag4641
0x25set_object_pos4483
0x36set_object_field_243452
0x1eload_view3231

Frequently encountered condition opcodes included:

OpcodeLabelCountFixed operands
0x0einput_word_sequence2472variable
0x01var_eq_imm19492
0x07flag_set10981
0x05var_gt_imm5042
0x0bobject_left_baseline_in_rect4505
0x03var_lt_imm1982
0x09obj_table_room_ff1531

The current scanner is intentionally conservative. It correctly identifies the main structural bytes and many statement boundaries. Remaining interpreter work is concentrated less on unnamed action opcodes and more on refining implementation-level contracts for object/view fields, display-specific buffer variants, and the more hardware-facing input and graphics helpers.

Logic Opcode Evidence Matrix

This chapter is generated from local clean-room artifacts by tools/logic_opcode_evidence.py. It is a coverage index, not the full semantic specification; detailed behavior remains in logic_bytecode.md and the command/evidence trail remains in clean_room_executable_notes.md.

Evidence levels:

  • QEMU-validated: a generated fixture has been run through the original SQ2 interpreter in QEMU and compared against the local expected output.
  • QEMU dispatch-smoke: a generated fixture proves the opcode executes, consumes operands, and returns to following bytecode under the original interpreter, but it does not yet expose every downstream state mutation.
  • source-backed: behavior is derived from local disassembly and local SQ2 bytecode/resource scans, but no targeted QEMU fixture is recorded for the opcode yet.
  • source-backed structural: handled directly by the interpreter loop or condition scanner rather than by the normal action/condition handler dispatch path.
  • reserved/invalid: outside the valid opcode catalog for this SQ2 build.

Structural and Invalid Bytes

ContextByte or rangeLabelEvidence levelEvidence
Main stream0x00endsource-backed structuralTerminates the current logic path before action dispatch.
Main stream0x01..0xafaction opcodesmixedSee the action matrix below.
Main stream0xb0..0xfbinvalid action bytesreserved/invalidAction dispatcher rejects opcodes above 0xaf.
Main stream0xfcinvalid outside conditionsreserved/invalidAction dispatcher rejects structural/control byte.
Main stream0xfdinvalid outside conditionsreserved/invalidAction dispatcher rejects structural/control byte.
Main stream0xfejumpQEMU-validatedlogic_interpreter_probe: jump_skips_first_draw
Main stream0xffifQEMU-validatedlogic_interpreter_probe: all conditional probes
Condition list0xfcOR_MARKQEMU-validatedlogic_interpreter_probe: or_group_true_runs_then_draw
Condition list0xfdNOT_NEXTQEMU-validatedlogic_interpreter_probe: not_condition_runs_then_draw
Condition list0xfeinvalid predicate bytereserved/invalidRejected if seen as a predicate opcode.
Condition list0xffcondition terminatorQEMU-validatedEnds condition list and precedes the false-path delta.
Condition list0x13..0x25reserved predicate rangereserved/invalidBytes after condition-table entry 0x12 are not a valid table region in this build.
Condition list0x26..0xfbinvalid predicate rangereserved/invalidCondition dispatcher rejects predicate opcodes >= 0x26.

Action Opcodes

OpcodeLabelOperand shapeBehavior summaryEvidence levelProbe/evidence
0x00end-endsource-backed structuralHandled by code.logic.interpret_main before action dispatch; table entry is not executed.
0x01inc_varvar0inc varQEMU-validatedlogic_interpreter_probe: var_inc_reaches_expected_value, var_inc_saturates_at_ff
0x02dec_varvar0dec varQEMU-validatedlogic_interpreter_probe: var_dec_reaches_expected_value, var_dec_saturates_at_zero
0x03assignnvar0, imm1assignnQEMU-validatedlogic_interpreter_probe: variable setup in all state probes
0x04assignvvar0, var1assignvQEMU-validatedlogic_interpreter_probe: assignv_copies_source_variable
0x05addnvar0, imm1addnQEMU-validatedlogic_interpreter_probe: addn_uses_low_byte_arithmetic
0x06addvvar0, var1addvQEMU-validatedlogic_interpreter_probe: addv_uses_source_variable
0x07subnvar0, imm1subnQEMU-validatedlogic_interpreter_probe: subn_uses_low_byte_arithmetic
0x08subvvar0, var1subvQEMU-validatedlogic_interpreter_probe: subv_uses_source_variable
0x09indirect_assignvvar0, var1indirect assignvQEMU-validatedlogic_interpreter_probe: indirect_assignv_writes_indexed_destination
0x0aassign_indirectvvar0, var1assign indirectvQEMU-validatedlogic_interpreter_probe: assign_indirectv_reads_indexed_source
0x0bindirect_assignnvar0, imm1indirect assignnQEMU-validatedlogic_interpreter_probe: indirect_assignn_writes_immediate_to_indexed_destination
0x0cset_flagimm0set flagQEMU-validatedlogic_interpreter_probe: flag_set_clear_toggle_actions, or_group_true_runs_then_draw
0x0dclear_flagimm0clear flagQEMU-validatedlogic_interpreter_probe: flag_set_clear_toggle_actions
0x0etoggle_flagimm0toggle flagQEMU-validatedlogic_interpreter_probe: flag_set_clear_toggle_actions
0x0fset_flag_varvar0set flag varQEMU-validatedlogic_interpreter_probe: flag_var_actions_and_condition
0x10clear_flag_varvar0clear flag varQEMU-validatedlogic_interpreter_probe: flag_var_actions_and_condition
0x11toggle_flag_varvar0toggle flag varQEMU-validatedlogic_interpreter_probe: flag_var_actions_and_condition
0x12switch_room_likeimm0switch room likeQEMU-validatedlogic_interpreter_probe: switch_room_reentry_dispatches_current_room, switch_room_sets_current_previous_and_clears_boundary, switch_room_boundary_1..4, and switch_room_removes_preexisting_persistent_object
0x13switch_room_like_varvar0switch room like varQEMU-validatedlogic_interpreter_probe: switch_room_v_reentry_dispatches_current_room, switch_room_v_sets_current_previous_and_clears_boundary, switch_room_v_boundary_1..4, and switch_room_v_removes_preexisting_persistent_object
0x14load_logicimm0load logicQEMU-validatedlogic_interpreter_probe: load_logic_then_call_logic_draws
0x15load_logic_varvar0load logic varQEMU-validatedlogic_interpreter_probe: load_logic_var_then_call_logic_draws
0x16call_logicimm0call logicQEMU-validatedlogic_interpreter_probe: call_logic_draws_from_called_logic, load_logic_then_call_logic_draws
0x17call_logic_varvar0call logic varQEMU-validatedlogic_interpreter_probe: call_logic_var_draws_selected_logic, switch_room_reentry_dispatches_current_room
0x18load_picture_varvar0load picture varQEMU-validatedpicture/view QEMU fixtures load generated picture resources
0x19prepare_picture_varvar0prepare picture varQEMU-validatedpicture/view QEMU fixtures prepare generated picture resources
0x1ashow_picture_like-show picture likeQEMU-validatedpicture/view QEMU fixtures show generated picture resources; logic_interpreter_probe: overlay_picture_var_composes_extra_picture
0x1bdiscard_picture_varvar0discard picture varQEMU-validatedlogic_interpreter_probe: discard_picture_var_allows_reload_and_overlay
0x1coverlay_picture_varvar0overlay picture varQEMU-validatedlogic_interpreter_probe: overlay_picture_var_composes_extra_picture
0x1dshow_priority_screen-show priority screenQEMU-validatedlogic_interpreter_probe: priority_screen_enter_returns
0x1eload_viewimm0load viewQEMU-validatedview/object QEMU fixtures load view resources
0x1fload_view_varvar0load view varQEMU-validatedlogic_interpreter_probe: load_view_var_allows_following_draw
0x20discard_viewimm0discard viewQEMU-validatedlogic_interpreter_probe: discard_view_allows_reload_and_draw
0x21reset_object_stateimm0reset object stateQEMU-validatedobject overlay and movement probes reset object state
0x22clear_all_object_bits-clear all object bitsQEMU-validatedlogic_interpreter_probe: clear_all_object_bits_keeps_current_draw_entry
0x23activate_objectimm0activate objectQEMU-validatedobject overlay and movement probes activate persistent objects
0x24deactivate_objectimm0deactivate objectQEMU-validatedlogic_interpreter_probe: deactivate_object_removes_persistent_draw
0x25set_object_posimm0, imm1, imm2set object posQEMU-validatedlogic_interpreter_probe: object_position_getter_observes_setter
0x26set_object_pos_varimm0, var1, var2set object pos varQEMU-validatedlogic_interpreter_probe: set_object_pos_var_getter_observes_values
0x27get_object_posimm0, var1, var2get object posQEMU-validatedlogic_interpreter_probe: object_position_getter_observes_setter
0x28add_object_pos_from_varsimm0, var1, var2add object pos from varsQEMU-validatedlogic_interpreter_probe: object_add_pos_from_vars_getter_observes_sum
0x29set_object_resourceimm0, imm1set object resourceQEMU-validatedobject overlay and movement probes bind object resources
0x2aset_object_resource_varimm0, var1set object resource varQEMU-validatedlogic_interpreter_probe: var_resource_group_frame_setup_draws_persistent_object
0x2bset_object_subresourceimm0, imm1set object subresourceQEMU-validatedobject overlay and movement probes select object groups
0x2cset_object_subresource_varimm0, var1set object subresource varQEMU-validatedlogic_interpreter_probe: var_resource_group_frame_setup_draws_persistent_object
0x2dset_object_bit_2000imm0set object bit 2000QEMU-validatedlogic_interpreter_probe: object_bit_2000_002
0x2eclear_object_bit_2000imm0clear object bit 2000QEMU-validatedlogic_interpreter_probe: object_bit_2000_002
0x2fset_object_derived_resource_2imm0, imm1set object derived resource 2QEMU-validatedobject overlay and movement probes select object frames
0x30set_object_derived_resource_2_varimm0, var1set object derived resource 2 varQEMU-validatedlogic_interpreter_probe: var_resource_group_frame_setup_draws_persistent_object
0x31get_object_resource_loop_countimm0, var1get object resource loop countQEMU-validatedlogic_interpreter_probe: object_view_metadata_getters
0x32get_object_field_0eimm0, var1get object field 0eQEMU-validatedlogic_interpreter_probe: object_view_metadata_getters
0x33get_object_field_0aimm0, var1get object field 0aQEMU-validatedlogic_interpreter_probe: object_view_metadata_getters
0x34get_object_field_07imm0, var1get object field 07QEMU-validatedlogic_interpreter_probe: object_view_metadata_getters
0x35get_object_field_0bimm0, var1get object field 0bQEMU-validatedlogic_interpreter_probe: object_view_metadata_getters
0x36set_object_field_24imm0, imm1set object field 24QEMU-validatedlogic_interpreter_probe: object_field_24_getter_observes_setter
0x37set_object_field_24_varimm0, var1set object field 24 varQEMU-validatedlogic_interpreter_probe: object_field_24_var_getter_observes_value
0x38clear_object_bit_0004imm0clear object bit 0004QEMU-validatedlogic_interpreter_probe: clear_fixed_priority_bit_uses_derived_priority
0x39get_object_field_24imm0, var1get object field 24QEMU-validatedlogic_interpreter_probe: object_field_24_getter_observes_setter
0x3aclear_object_bit_0010imm0clear object bit 0010QEMU-validatedlogic_interpreter_probe: object_root_partition_004
0x3bset_object_bit_0010imm0set object bit 0010QEMU-validatedlogic_interpreter_probe: object_root_partition_004
0x3crefresh_object_listsimm0refresh object listsQEMU-validatedlogic_interpreter_probe: object_root_partition_004
0x3dset_object_bit_0008imm0set object bit 0008QEMU-validatedlogic_interpreter_probe: horizon_exempt_bit_keeps_object_above_horizon
0x3eclear_object_bit_0008imm0clear object bit 0008QEMU-validatedlogic_interpreter_probe: horizon_clear_exempt_bit_restores_clamp
0x3fset_global_012dimm0set global 012dQEMU-validatedlogic_interpreter_probe: horizon_clamps_object_when_bit_clear
0x40set_object_bit_0100imm0set object bit 0100QEMU-validatedobject_movement_probe: control_bits_0900_002
0x41set_object_bit_0800imm0set object bit 0800QEMU-validatedobject_movement_probe: control_bits_0900_002
0x42clear_object_bits_0900imm0clear object bits 0900QEMU-validatedobject_movement_probe: control_bits_0900_002
0x43set_object_bit_0200imm0set object bit 0200QEMU-validatedobject_movement_probe: movement_collision
0x44clear_object_bit_0200imm0clear object bit 0200QEMU-validatedobject_movement_probe: clear_skip_bit_001
0x45object_distance_to_varimm0, imm1, var2object distance to varQEMU-validatedlogic_interpreter_probe: object_distance_inactive_pair_sets_ff
0x46clear_object_bit_0020imm0clear object bit 0020QEMU-validatedobject_movement_probe: frame_timer_001
0x47set_object_bit_0020imm0set object bit 0020QEMU-validatedobject_movement_probe: frame_timer_001
0x48set_object_field_23_mode0imm0set object field 23 mode0QEMU-validatedobject_movement_probe: frame_timer_modes_002
0x49set_object_field_23_mode1imm0, imm1set object field 23 mode1QEMU-validatedlogic_interpreter_probe: object_field_23_mode1_clears_flag
0x4aset_object_field_23_mode3imm0set object field 23 mode3QEMU-validatedobject_movement_probe: frame_timer_modes_002
0x4bset_object_field_23_mode2imm0, imm1set object field 23 mode2QEMU-validatedlogic_interpreter_probe: object_field_23_mode2_clears_flag; object_movement_probe: frame_timer_modes_002
0x4cset_object_field_1f_varimm0, var1set object field 1f varQEMU-validatedobject_movement_probe: frame_timer_001
0x4dclear_object_fields_21_22imm0clear object fields 21 22QEMU-validatedlogic_interpreter_probe: clear_object_fields_21_22_clears_direction
0x4eclear_object_field_22_and_globalimm0clear object field 22 and globalQEMU-validatedobject_movement_probe: clear_field_22_001
0x4fset_object_field_1e_varimm0, var1set object field 1e varQEMU-validatedobject_movement_probe: autonomous_modes_003 and motion_modes_004
0x50set_object_field_01_varimm0, var1set object field 01 varQEMU-validatedobject_movement_probe: autonomous_modes_003 and motion_modes_004
0x51move_object_toimm0, imm1, imm2, imm3, imm4move object toQEMU-validatedobject_movement_probe: motion_modes_004
0x52move_object_to_varimm0, var1, var2, var3, imm4move object to varQEMU-validatedlogic_interpreter_probe: move_object_to_var_sets_flag_at_existing_target
0x53approach_first_object_until_nearimm0, imm1, imm2approach first object until nearQEMU-validatedobject_movement_probe: autonomous_modes_003
0x54start_random_motionimm0start random motionQEMU-validatedobject_movement_probe: motion_modes_004
0x55stop_motion_modeimm0stop motion modeQEMU-validatedobject_movement_probe setup paths
0x56set_object_field_21_varimm0, var1set object field 21 varQEMU-validatedlogic_interpreter_probe: object_field_21_getter_observes_setter
0x57get_object_field_21imm0, var1get object field 21QEMU-validatedlogic_interpreter_probe: object_field_21_getter_observes_setter
0x58set_object_bit_0002imm0set object bit 0002QEMU-validatedobject_movement_probe: rect_bit_0002_001
0x59clear_object_bit_0002imm0clear object bit 0002QEMU-validatedobject_movement_probe: rect_bit_0002_001
0x5aset_rect_bounds_0131imm0, imm1, imm2, imm3set rect bounds 0131QEMU-validatedobject_movement_probe: rect_bit_0002_001 and rect_bounds_clear_001
0x5bclear_rect_bounds_0131-clear rect bounds 0131QEMU-validatedobject_movement_probe: rect_bounds_clear_001
0x5cset_entry_0971_marker_ffimm0set entry 0971 marker ffQEMU-validatedlogic_interpreter_probe: inventory_marker_ff_condition_true
0x5dset_entry_0971_marker_ff_varvar0set entry 0971 marker ff varQEMU-validatedlogic_interpreter_probe: inventory_marker_ff_var_and_getter
0x5eclear_entry_0971_markerimm0clear entry 0971 markerQEMU-validatedlogic_interpreter_probe: inventory_marker_clear_and_getter
0x5fset_entry_0971_marker_from_varimm0, imm1set entry 0971 marker from varQEMU-validatedlogic_interpreter_probe: inventory_marker_from_var
0x60set_entry_0971_marker_from_var_varimm0, var1set entry 0971 marker from var varQEMU-validatedlogic_interpreter_probe: inventory_marker_from_var_var
0x61get_entry_0971_marker_to_varvar0, var1get entry 0971 marker to varQEMU-validatedlogic_interpreter_probe: inventory marker getter probes
0x62load_soundimm0load soundQEMU-validatedlogic_interpreter_probe: sound_stop_sets_completion_flag
0x63start_sound_with_flagimm0, imm1start sound with flagQEMU-validatedlogic_interpreter_probe: sound_stop_sets_completion_flag
0x64stop_sound_or_clear_sound_state-stop sound or clear sound stateQEMU-validatedlogic_interpreter_probe: sound_stop_sets_completion_flag
0x65display_messageimm0display messageQEMU-validatedlogic_interpreter_probe: display_message_then_ack_continues_to_draw
0x66display_message_varvar0display message varQEMU-validatedlogic_interpreter_probe: display_message_var_then_ack_continues_to_draw
0x67display_formatted_messageimm0, imm1, imm2display formatted messageQEMU-validatedlogic_interpreter_probe: display_formatted_message_then_ack_continues_to_draw
0x68display_formatted_message_varvar0, var1, var2display formatted message varQEMU-validatedlogic_interpreter_probe: display_formatted_message_var_then_ack_continues_to_draw
0x69clear_text_rectimm0, imm1, imm2clear text rectQEMU-validatedlogic_interpreter_probe: text_rect_clear_rows_removes_formatted_text
0x6aenable_text_attr_mode_1757-enable text attr mode 1757QEMU-validatedlogic_interpreter_probe: text_attribute_enable_clears_visible_surface
0x6bdisable_text_attr_mode_1757-disable text attr mode 1757QEMU-validatedlogic_interpreter_probe: text_attribute_disable_restores_picture_draw
0x6cset_input_prompt_charimm0set input prompt charQEMU-validatedlogic_interpreter_probe: input_prompt_empty_message_suppresses_marker
0x6dset_text_window_pairimm0, imm1set text window pairQEMU-validatedlogic_interpreter_probe: text_attribute_pair_changes_attr_mode_clear_color
0x6eshake_screen_likeimm0shake screen likesource-backedDisassembly: action reads a count byte and performs display-mode-specific screen shake; in the normal path it writes CRT controller registers 0x02/0x07 from a small offset table with timer-tick waits. QEMU dispatch smoke confirms return to following bytecode.
0x6fset_input_line_configimm0, imm1, imm2set input line configQEMU-validatedlogic_interpreter_probe: input_line_config_operand1_offsets_display_by_8
0x70show_status_line_like-show status line likeQEMU-validatedlogic_interpreter_probe: status_line_show_draws_configured_row
0x71hide_status_line_like-hide status line likeQEMU-validatedlogic_interpreter_probe: status_line_hide_clears_configured_row
0x72set_string_slot_from_messageimm0, imm1set string slot from messageQEMU-validatedlogic_interpreter_probe: set_string_from_message_equal_normalized and parse_string_slot_sets_input_word_sequence
0x73prompt_string_to_slotimm0, imm1, imm2, imm3, imm4prompt string to slotQEMU-validatedlogic_interpreter_probe: prompt_string_to_slot_stores_typed_word
0x74set_string_slot_from_tableimm0, imm1set string slot from tableQEMU-validatedlogic_interpreter_probe: set_string_from_table_copies_patched_pointer
0x75parse_string_slotimm0parse string slotQEMU-validatedlogic_interpreter_probe: parse_string_slot_sets_input_word_sequence
0x76prompt_number_to_varimm0, var1prompt number to varQEMU-validatedlogic_interpreter_probe: prompt_number_to_var_accepts_digits
0x77disable_input_line_like-disable input line likeQEMU-validatedlogic_interpreter_probe: input_line_disable_clears_configured_row
0x78enable_input_line_like-enable input line likeQEMU-validatedlogic_interpreter_probe: input_line_enable_clears_configured_row
0x79map_key_eventimm0, imm1, imm2map key eventQEMU-validatedlogic_interpreter_probe: mapped_key_sets_status_byte
0x7asetup_transient_objectimm0, imm1, imm2, imm3, imm4, imm5, imm6setup transient objectQEMU-validatedlogic_interpreter_probe and object_overlay_probe transient drawing
0x7bsetup_transient_object_varvar0, var1, var2, var3, var4, var5, var6setup transient object varQEMU-validatedlogic_interpreter_probe: setup_transient_object_var_draws_selected_cel
0x7cshow_inventory_selection-show inventory selectionQEMU-validatedlogic_interpreter_probe: inventory_selection_enter_sets_var19, inventory_selection_escape_sets_var19_ff, and inventory_selection_noninteractive_ack_returns
0x7dsave_game_state-save game stateQEMU-validatedlogic_interpreter_probe: save_game_escape_continues_to_draw
0x7erestore_game_state-restore game stateQEMU-validatedlogic_interpreter_probe: restore_game_escape_continues_to_draw
0x7fnoop-noopQEMU-validatedlogic_interpreter_probe: noop_7f_continues_to_draw
0x80confirm_restart_game-confirm restart gameQEMU-validatedlogic_interpreter_probe: restart_confirm_escape_continues_to_draw
0x81display_view_resource_text_likeimm0display view resource text likeQEMU-validatedlogic_interpreter_probe: view_resource_display_immediate_returns
0x82random_range_to_varimm0, imm1, var2random range to varQEMU-validatedlogic_interpreter_probe: random_equal_bounds_stores_bound
0x83clear_global_0139-clear global 0139source-backedDisassembly: action clears [0x0139]; code.engine.main_cycle uses that selector before logic to choose object0->global direction mirroring, then restores object0 direction from global after logic.
0x84set_global_0139_and_clear_object0_field_22-set global 0139 and clear object0 field 22QEMU-validatedobject_movement_probe: action_84_after_random_motion_stops_motion
0x85display_object_diagnostics_varvar0display object diagnostics varQEMU-validatedlogic_interpreter_probe: object_diagnostics_var_enter_returns
0x86confirm_and_restart_likeimm0confirm and restart likeQEMU-validatedlogic_interpreter_probe: confirm_restart_like_escape_continues_to_draw
0x87show_heap_status-show heap statusQEMU-validatedlogic_interpreter_probe: heap_status_then_ack_continues_to_draw
0x88pause_game_message-pause game messageQEMU-validatedlogic_interpreter_probe: pause_message_then_ack_continues_to_draw
0x89refresh_input_line-refresh input lineQEMU-validatedlogic_interpreter_probe: input_line_refresh_repaints_entered_buffer
0x8aerase_input_line-erase input lineQEMU-validatedlogic_interpreter_probe: input_line_erase_clears_typed_buffer
0x8bcalibrate_joystick-calibrate joystickQEMU-validatedlogic_interpreter_probe: joystick_calibration_no_joystick_returns
0x8ctoggle_display_mode_bit-toggle display mode bitQEMU-validatedlogic_interpreter_probe: display_mode_toggle_guarded_noop_continues
0x8dshow_interpreter_version-show interpreter versionQEMU-validatedlogic_interpreter_probe: interpreter_version_then_ack_continues_to_draw
0x8eset_global_0141_and_refreshimm0set global 0141 and refreshsource-backedDisassembly: action writes data.event.pair_capacity and calls code.event.reset_pair_buffer inside update-list flush/rebuild helpers; reset allocates capacity*2 bytes on first use and clears pair count/write cursor.
0x8fverify_game_signatureimm0verify game signatureQEMU-validatedlogic_interpreter_probe: signature_check_matching_message_returns
0x90append_message_to_log_fileimm0append message to log fileQEMU-validatedlogic_interpreter_probe: log_file_append_dispatch_smoke plus extracted LOGFILE content
0x91save_logic_resume_ip-save logic resume ipQEMU-validatedlogic_interpreter_probe: save_restore_resume_actions_continue_to_draw
0x92restore_logic_entry_ip-restore logic entry ipQEMU-validatedlogic_interpreter_probe: save_restore_resume_actions_continue_to_draw
0x93set_object_pos_dirtyimm0, imm1, imm2set object pos dirtyQEMU-validatedlogic_interpreter_probe: set_object_pos_dirty_getter_observes_values
0x94set_object_pos_dirty_varimm0, var1, var2set object pos dirty varQEMU-validatedlogic_interpreter_probe: set_object_pos_dirty_var_getter_observes_values
0x95enable_action_trace_window-enable action trace windowQEMU-validatedlogic_interpreter_probe: trace_window_enable_draws_box_when_flag10_set
0x96configure_action_trace_windowimm0, imm1, imm2configure action trace windowQEMU-validatedlogic_interpreter_probe: trace_window_enable_draws_box_when_flag10_set
0x97display_message_configuredimm0, imm1, imm2, imm3display message configuredQEMU-validatedlogic_interpreter_probe: display_message_configured_then_ack_continues_to_draw
0x98display_message_configured_varvar0, imm1, imm2, imm3display message configured varQEMU-validatedlogic_interpreter_probe: display_message_configured_var_then_ack_continues_to_draw
0x99discard_view_varvar0discard view varQEMU-validatedlogic_interpreter_probe: discard_view_var_allows_reload_and_draw
0x9aclear_text_rect_boundsimm0, imm1, imm2, imm3, imm4clear text rect boundsQEMU-validatedlogic_interpreter_probe: text_rect_clear_bounds_removes_formatted_text
0x9bnoop_2imm0, imm1noop 2QEMU-validatedlogic_interpreter_probe: noop_9b_consumes_two_operands_then_draws
0x9cadd_menu_heading_likeimm0add menu heading likeQEMU-validatedlogic_interpreter_probe: menu_interactive/menu_edges setup cases
0x9dadd_menu_item_likeimm0, imm1add menu item likeQEMU-validatedlogic_interpreter_probe: menu_interactive/menu_edges setup cases
0x9efinalize_menu_like-finalize menu likeQEMU-validatedlogic_interpreter_probe: menu_interactive/menu_edges setup cases
0x9fenable_menu_item_likeimm0enable menu item likeQEMU-validatedlogic_interpreter_probe: menu_enable_after_disable_allows_enter_status_byte
0xa0disable_menu_item_likeimm0disable menu item likeQEMU-validatedlogic_interpreter_probe: menu_disabled_item_enter_does_not_set_status_byte and menu_enable_after_disable_allows_enter_status_byte
0xa1mark_menu_if_flag_0e-mark menu if flag 0eQEMU-validatedlogic_interpreter_probe: menu_interactive_enter_sets_status_byte and menu_edges_002
0xa2display_view_resource_text_like_varimm0display view resource text like varQEMU-validatedlogic_interpreter_probe: view_resource_display_var_returns
0xa3set_global_0d0f-set global 0d0fQEMU-validatedlogic_interpreter_probe: input_width_flag_a3_allows_long_live_input
0xa4clear_global_0d0f-clear global 0d0fQEMU-validatedlogic_interpreter_probe: input_width_flag_a4_restores_long_slot_limit
0xa5mulnvar0, imm1mulnQEMU-validatedlogic_interpreter_probe: muln_keeps_low_product_byte
0xa6mulvvar0, var1mulvQEMU-validatedlogic_interpreter_probe: mulv_keeps_low_product_byte
0xa7divnvar0, imm1divnQEMU-validatedlogic_interpreter_probe: divn_stores_quotient_byte
0xa8divvvar0, var1divvQEMU-validatedlogic_interpreter_probe: divv_stores_quotient_byte
0xa9close_text_window_state-close text window stateQEMU-validatedlogic_interpreter_probe: close_text_window_state_clears_input_width_flag
0xaacopy_save_description_to_string_slotimm0copy save description to string slotsource-backedDisassembly: action copies up to 0x1f bytes from runtime save-description buffer [0x0e72] into string slot 0x020d + arg0*0x28 via the shared bounded-copy helper.
0xabsave_event_buffer_count-save event buffer countQEMU-validatedlogic_interpreter_probe: display_mode_replay_uses_rolled_back_event_count
0xacrestore_event_buffer_count-restore event buffer countQEMU-validatedlogic_interpreter_probe: display_mode_replay_uses_rolled_back_event_count
0xadincrement_global_1530-increment global 1530source-backedDisassembly plus agi_input source model: action increments byte [0x1530]; the keyboard IRQ hook uses a tracked scan-code latch and tests the nonzero gate before enqueueing a type-2 zero event on selected key releases.
0xaerebuild_priority_table_from_yimm0rebuild priority table from yQEMU-validatedobject_overlay_probe: priority-table rebuild effects
0xafnoop_1_table_countimm0noop 1 table countQEMU-validatedlogic_interpreter_probe: noop_af_runtime_consumes_no_operand

Condition Opcodes

OpcodeLabelOperand shapeBehavior summaryEvidence levelProbe/evidence
0x00always_false-always falseQEMU-validatedlogic_interpreter_probe: always_false_condition_skips_then_draw
0x01var_eq_immvar0, imm1var eq immQEMU-validatedlogic_interpreter_probe: if_false_skips_then_draw and state probes
0x02var_eq_varvar0, var1var eq varQEMU-validatedlogic_interpreter_probe: var_comparison_conditions_all_true
0x03var_lt_immvar0, imm1var lt immQEMU-validatedlogic_interpreter_probe: var_comparison_conditions_all_true
0x04var_lt_varvar0, var1var lt varQEMU-validatedlogic_interpreter_probe: var_comparison_conditions_all_true
0x05var_gt_immvar0, imm1var gt immQEMU-validatedlogic_interpreter_probe: var_comparison_conditions_all_true
0x06var_gt_varvar0, var1var gt varQEMU-validatedlogic_interpreter_probe: var_comparison_conditions_all_true
0x07flag_setimm0flag setQEMU-validatedlogic_interpreter_probe: flag_set_clear_toggle_actions and OR-group probe
0x08flag_set_varvar0flag set varQEMU-validatedlogic_interpreter_probe: flag_var_actions_and_condition
0x09obj_table_room_ffimm0obj table room ffQEMU-validatedlogic_interpreter_probe: inventory_marker_ff_condition_true
0x0aobj_table_room_eq_varimm0, var1obj table room eq varQEMU-validatedlogic_interpreter_probe: inventory_marker_eq_var_condition_true
0x0bobject_left_baseline_in_rectimm0, imm1, imm2, imm3, imm4object left baseline in rectQEMU-validatedlogic_interpreter_probe: object_left_rect_condition_true
0x0cstatus_byte_1218imm0status byte 1218QEMU-validatedlogic_interpreter_probe: mapped_key_sets_status_byte
0x0draw_key_event_available-raw key event availableQEMU-validatedlogic_interpreter_probe: raw_key_event_available_draws_after_typed_key
0x0einput_word_sequencevarlen word sequenceinput word sequenceQEMU-validatedlogic_interpreter_probe: parse_string_slot_sets_input_word_sequence, parser_edges_001, and parser_unknown_terminator_001
0x0fstring_slots_equal_normalizedimm0, imm1string slots equal normalizedQEMU-validatedlogic_interpreter_probe: set_string_from_message_equal_normalized
0x10object_width_baseline_in_rectimm0, imm1, imm2, imm3, imm4object width baseline in rectQEMU-validatedlogic_interpreter_probe: object_width_rect_condition_true
0x11object_center_baseline_in_rectimm0, imm1, imm2, imm3, imm4object center baseline in rectQEMU-validatedlogic_interpreter_probe: object_center_rect_condition_true
0x12object_right_baseline_in_rectimm0, imm1, imm2, imm3, imm4object right baseline in rectQEMU-validatedlogic_interpreter_probe: object_right_rect_condition_true