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:
- Local deterministic tests run directly against the resource files selected by
--game-dir PATHorAGI_GAME_DIR=PATH. These are fast regression tests for directory parsing, resource decoding, and provisional renderers. - 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. - 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.
- 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. - 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:
-
PICDIRhas 74 valid picture payloads. Entry 147 decodes toVOL.0offset0x2ffffand 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
0xf4and0xf5are not used by any valid picture payload even though the interpreter implements them. -
Synthetic unit tests directly cover both rare corner-path commands:
0xf4starts with a vertical segment, then alternates horizontal/vertical;0xf5starts 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
0xf6line from(0,0)to(3,1)and a relative0xf7line with packed delta byte0x31both 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 01at 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
4x2downsampling 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 Y80, and priority/control15, the original-engine capture matched the localcompose_frame_on_picture()output with 0 mismatches out of 26,880 logical pixels. -
Source-modeled object-overlay priority tests cover the
IBM_OBJS.OVL:0x9e35pixel 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-
0x80frame 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
0x587dbeyond 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-
0x80orientation, left-edge clipping, top-edge placement, right-edge placement, bottom-edge placement, and a low-priority object case. The timed polling carouselview_carousel_base_001matched all eight from one original-engine process with 0 mismatches. -
The optional
--include-stressview batch adds eleven larger or transparent-color-focused cels, including transparent colors0,1,2,5,6,7,8,10,13,14, and15, plus a bit-0x80transparent-10 frame. QEMU snapshot batchview_stress_001matched the older 17-case set with 0 mismatches and 0 errors; timed polling carouselview_carousel_stress_001matched 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
leftby the negative top and raisesbaseline_yso 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 8The 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 bit0x0200is set on object 0. It also includes one autonomous mode-2 case: object 1 starts0x53(approach_first_object_until_near) toward object 0 with step5and near threshold35; 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 one0x51setup reaches(50,80)without script reissue, and a property-style random-motion case where0x54renders 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_againThe 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_motionThe 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 executing0x84, 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_motionThe 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 v30xb5(clear_key_release_event_gate).tests/test_input_model.pymodels the shared tracked-key IRQ latch: scan codes0x47..0x51use 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 SQ20xadbyte-increment wraparound and GR v30xad/0xb5set/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 writesdata.event.pair_capacityand resets the pair buffer inside update-list flush/rebuild calls. Existing replay QEMU cases validate downstream pair-log behavior for0xab/0xac;0x8eitself is covered by the compact disassembled reset path. -
Targeted frame-timer probes validate visible effects of actions
0x46,0x47, and0x4c: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_advanceThe batch matched QEMU with 3 matches, 0 mismatches, and 0 errors. It validates that
0x4cseeds the frame timer,0x46disables the timer-driven frame advance by clearing bit0x0020, and0x47restores that advance. -
A follow-up frame-mode batch validates actions
0x48,0x4a, and0x4bagainst the staticcode.object.advance_frame_by_modemodel: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_frame1The 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_targetThese batches matched QEMU with 0 mismatches. They validate
0x58/0x59through rectangle-boundary crossing behavior,0x40/0x41through control-class movement rejection,0x42restoring movement after either rejection bit is set, and0x5bclearing rectangle bounds after0x5aso 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 bit0x0002is set. -
tests/test_picture_fuzz.pycovers 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, plusseed_fill_edges_001forbase_021_visual_fill_full_height_barrier,base_022_visual_fill_multi_seed_boxes, andbase_023_control_fill_ignores_visual_barrier, andpattern_interleaved_001forbase_024_pattern_bypass_mask,base_025_interleaved_line_fill_pattern, andbase_026_pattern_random_bypass_sequence, pluspattern_channel_masks_001forbase_027_pattern_visual_control_channels,base_028_pattern_visual_disabled_control_only, andbase_029_pattern_control_disabled_visual_only, plusraw_operand_001forbase_033_raw_visual_operand,base_034_raw_control_operand, andbase_035_raw_pattern_mode_operand. -
The
seed_fill_edges_001snapshot 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_001snapshot batch matched the original engine with 3 matches, 0 mismatches, and 0 errors. These cases validate pattern mode bit0x10bypassing the row/column mask test, a rectangle/fill/line/pattern sequence in one valid picture stream, and a two-plot pattern sequence with both0x10and pseudo-random bit0x20set. -
The
pattern_channel_masks_001snapshot 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_001snapshot 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_001snapshot batch matched the original engine with 3 matches, 0 mismatches, and 0 errors. These cases validate the complementary scanner rule that0xf0,0xf2, and0xf9consume command-looking bytes as raw one-byte operands instead of treating them as new commands. -
The
relative_underflow_001snapshot 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
160to the linear pixel writer. The original engine stores that byte as X0on 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_001matched picture 1 and picture 45 with 2 matches, 0 mismatches, and 0 errors. -
Broad real-picture snapshot batch
picture_broad_001matched 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_001matched 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_001matched 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_001matched 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_suitematched the same eight-picture broad preset as part ofqemu_broad_002.json, also with 8 matches, 0 mismatches, and 0 errors. -
Chunked timed polling carousel batch
picture_carousel_all_timed_poll_chunk16_001matched 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:
- 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.
- Verify the complete archive SHA-256 and extract its largest raw image to a temporary source path.
- Copy the complete source FAT tree to a temporary host directory.
- Create a sparse 1,073,741,824-byte raw output disk.
- Preserve the source MBR boot code but replace its partition table with one
active type-
0x0eFAT16-LBA partition beginning at LBA 2048. - Format the partition as FAT16 with 32 KiB clusters while using the source FreeDOS partition boot sector as the boot-code template.
- Copy the complete FreeDOS tree into the enlarged volume and atomically replace the requested output image.
- 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
0x31from 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/0x000ffixed 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.