Clean-room executable notes
This file records local observations made while inspecting the Space Quest 2 files in this repository. No external AGI documentation, source code, or prior AGI-specific knowledge is used here.
2026-07-01: initial executable inventory
Commands run from /Users/peter/ai/agi/reverse:
cat AGENTS.mdrg --filesls -lagit status --shortls -l SQ2file SQ2/SIERRA.COM SQ2/AGI SQ2/AGIDATA.OVL SQ2/EGA_GRAF.OVL SQ2/IBM_OBJS.OVL SQ2/VOL.0 SQ2/LOGDIRxxd -l 256 -g 1 SQ2/SIERRA.COMxxd -l 256 -g 1 SQ2/AGIcommand -v objdump ndisasm r2 rizin ghidraRun retdec-decompiler llvm-objdump strings dosbox qemu-system-i386strings -a -t x SQ2/SIERRA.COMstrings -a -t x SQ2/AGIxxd -s 0x2a0 -l 512 -g 1 SQ2/SIERRA.COMpython3 -c "import capstone; print(capstone.__version__)"python3 -c "import distorm3; print(distorm3.__version__)"python3 -c "import iced_x86; print(iced_x86.__version__)"python3 --versionwhich gobjdump nasm ndisasm radare2 rizin
Local tooling result:
/usr/bin/objdumpand/usr/bin/stringsare present.- The installed
objdumprejects-b, so it cannot directly disassemble a raw DOS COM binary in the usual GNU objdump style. ndisasm,r2,rizin,ghidraRun,retdec-decompiler,qemu-system-i386, and DOSBox were not found on PATH.- Python 3.14.6 is present, but
capstone,distorm3, andiced_x86are not installed.
File inventory observations:
SQ2/SIERRA.COMis 3,121 bytes andfileidentifies it as a DOS executable COM file.SQ2/AGIis 39,424 bytes andfileidentifies it only asdata.- Several overlay files exist.
fileidentifies at leastEGA_GRAF.OVLandIBM_OBJS.OVLas DOS executable COM-like data. SQ2/SIERRA.COMbegins with bytese9 9d 02, an 8086 near jump in a COM program. With the COM load origin at offset0x100, this jumps from memory0x0100to memory0x03a0, corresponding to file offset0x02a0.- The bytes immediately after that jump contain text:
LOADER v3.0 (c) Copyright Sierra On-Line, Inc. 1987, followed bykeyOfs8. SQ2/AGIbegins with high-entropy-looking bytes and has no obvious header in the first 256 bytes.
Loader string observations from strings -a -t x SQ2/SIERRA.COM:
- File-related strings include
agiat file offset about0x01c1andagidata.ovlat file offset0x01c5. - Error/retry strings include:
Can't find the file 'agi'.Can't find the file 'agidata.ovl'.Press Enter to try again.Press ESC to quit.
- Display-adapter messages mention color/graphics and Hercules-compatible adapters.
- Disk-prompt strings mention an original disk and a play disk.
- Later strings include
Bad program image.and a nearby byte sequence that includesMZ, suggesting a check or message related to an executable image.
Initial loader code observations from bytes at file offset 0x02a0:
- The loader disables interrupts, sets
SStoCS, setsSPto a low internal offset, and re-enables interrupts. - It copies the DOS PSP command tail from memory offset
0x80to an internal buffer near memory offset0x037a. - It calls code that attempts to open
agi, and if that fails it displays the local retry/quit prompt strings and reads keyboard input. - It similarly has a path for opening
agidata.ovl. - A visible subroutine beginning at file offset
0x0310performs DOS interrupt21hcalls with AH values matching open/read/close style operations based on the local byte sequence:b4 3d cd 21b4 3f cd 21b4 3e cd 21
Current working hypotheses to verify:
SIERRA.COMis a loader rather than the main interpreter.AGIis likely packed, encrypted, relocated, or otherwise transformed bySIERRA.COMbefore execution.- The
keyOfs8text and adjacent bytes may relate to the transformation ofAGI. AGIDATA.OVLmay contain additional runtime data loaded by the loader.
2026-07-01: loader transform reproduced
Additional tools installed by the user:
nasm/ndisasmrizin
Additional commands run from /Users/peter/ai/agi/reverse:
command -v nasm ndisasm rizin r2 radare2ndisasm -vrizin -vpython3 tools/decrypt_agi.pyndisasm -b 16 -o 0x3a0 -e 0x2a0 SQ2/SIERRA.COMndisasm -b 16 -o 0x6777 -e 0x6977 build/cleanroom/AGI.decrypted.exerizin -q -e scr.color=false -A -c iH -c ie -c iS -c iz -c q build/cleanroom/AGI.decrypted.exestrings -a -t x build/cleanroom/AGI.decrypted.exerizin -q -e scr.color=false -A -c afl -c q build/cleanroom/AGI.decrypted.exerizin -q -e scr.color=false -A -c izz -c q build/cleanroom/AGI.decrypted.exe- Commands above were also re-run with shell redirection to preserve derived
disassembly and Rizin output under
build/cleanroom/.
Derived local artifacts:
tools/decrypt_agi.pybuild/cleanroom/AGI.decrypted.exebuild/cleanroom/SIERRA.COM.entry.ndisasmbuild/cleanroom/AGI.decrypted.entry.ndisasmbuild/cleanroom/AGI.decrypted.rizin-info.txtbuild/cleanroom/AGI.decrypted.rizin-functions.txtbuild/cleanroom/AGI.decrypted.rizin-strings.txt
Important ndisasm confirmations from SIERRA.COM:
- Loader entry at memory
0x03a0, file offset0x02a0. 0x03c7loads the string at memory0x02c1(agi) intoDX;0x03cbcalls0x0415, the open/read/close loader routine.0x03e0loads the string at memory0x02c5(agidata.ovl) intoDX;0x03e4calls0x045f, which opens and immediately closes the file. This means the loader checks forAGIDATA.OVLbut does not load it at this stage.0x03f9calls0x08fc, which prepares arguments for the transform routine.0x08fccomputes an end segment from[0x0371] + [0x0373], passes the loader key table addressCS:0x0141, and calls0x09f4.0x09f4performs the observed transform:- Source segment begins at
[0x0371]. - End segment is
[0x0371] + [0x0373]. - Key table is
ES:DI, called asCS:0x0141. - For each byte, it loads the key byte, XORs the source byte in place,
rotates the key byte right through carry (
rcr al,1), and stores the updated key byte back. - It processes 128 bytes per source segment pass, advances the source segment by 8 paragraphs, and preserves carry across passes.
- Source segment begins at
0x0c46validates that the transformed image begins with MZ, applies relocation entries, copies the executable image down from its MZ header location, prepares PSP state, setsSS:SP, copies the command tail, and far jumps to the MZ entry point.
Reproduced transform result:
tools/decrypt_agi.pyapplies the observed transform toSQ2/AGI.- The result starts with
4d 5a, an MZ signature. file build/cleanroom/AGI.decrypted.exereportsMS-DOS executable.- Header values printed by the script and confirmed by Rizin:
last_page_bytes = 0x0000pages = 0x004drelocations = 0x0021header_paragraphs = 0x0020minalloc = 0x0271maxalloc = 0xffffinitial_ss = 0x0be9initial_sp = 0x0080initial_ip = 0x6777initial_cs = 0x0000relocation_table = 0x001c
Strings recovered from the decrypted MZ image:
- At file offset
0x0200, the image contains:Adventure Game Interpreter, copyright text, and author text. - Near file offsets
0x9986through0x99eb, the image contains overlay/file names:CGA_GRAF.OVLJR_GRAF.OVLEGA_GRAF.OVLHGC_GRAF.OVLVG_GRAF.OVLIBM_OBJS.OVLHGC_OBJS.OVLAGIDATA.OVLAGI.EXE
2026-07-01: decrypted executable startup and overlays
Additional commands run from /Users/peter/ai/agi/reverse:
python3 -c ...one-off local parsers overbuild/cleanroom/AGI.decrypted.exeto print MZ relocation entries and the overlay descriptor table.xxd -s 0x9900 -l 256 -g 1 build/cleanroom/AGI.decrypted.exerizin -q -e scr.color=false -A -c 'pD ...' ... build/cleanroom/AGI.decrypted.exeover focused address ranges around0x00c4,0x5cce,0x5cef,0x5d12,0x5d52,0x5d6b,0x6777,0x67c3, and0x821c.xxdand small Python byte dumps overSQ2/AGIDATA.OVLto compare loaded data offsets with strings observed in the executable.
Documented result:
- Added
docs/src/agi_executable.md. - The decrypted executable starts at image offset
0x6777, loads overlay 8 (AGIDATA.OVL) through a generic overlay loader at image offset0x67c3, then switchesDSto segment0x0a01and jumps to image offset0x0078. - The overlay descriptor table begins at file offset
0x9900, image offset0x9700. Descriptors are 16 bytes, with the first descriptor at image offset0x9704. - The loader routine uses descriptor offset
+0x04as destination segment,+0x08as filename offset within the table segment, and+0x0eas maximum read paragraph count. - A later routine at image offset
0x821cchooses one graphics overlay and one object overlay based on mode variables at0x1130and0x112e, then calls the same overlay loader for both.
2026-07-01: resource directory and volume observations
Additional commands run from /Users/peter/ai/agi/reverse:
- One-off local Python searches for immediate values matching AGIDATA string
offsets such as
0x0955,0x0ee8,0x0eef,0x116a,0x1172,0x117a, and0x1182inbuild/cleanroom/AGI.decrypted.exe. - Focused Rizin disassembly around image offsets
0x3030,0x30f0,0x3113,0x42d0,0x4305,0x4371,0x43a5,0x43d9,0x440d, and0x4441. strings -a -t x SQ2/AGIDATA.OVLxxd -s 0x0ee0 -l 160 -g 1 SQ2/AGIDATA.OVL- A local Python parser over
SQ2/LOGDIR,SQ2/PICDIR,SQ2/VIEWDIR,SQ2/SNDDIR, andSQ2/VOL.*to decode sampled 3-byte entries and inspect target bytes. - Focused Rizin disassembly around image offsets
0x2e32,0x2e56,0x2f70,0x39f7,0x4a3b, and0x5126. - Rizin cross-reference checks for calls to
0x2e32,0x2e56,0x4371,0x43a5,0x43d9, and0x440d. - Local Python validation over all non-absent directory entries to check volume header magic, volume byte, payload length, and end offsets.
Documented result:
- Added
docs/src/resource_files.md. - The executable loads
logdir,picdir,viewdir, andsnddiras complete files via a whole-file loader at image offset0x3113. - Loaded directory pointers are stored at
0x11b2,0x11b6,0x11b4, and0x11b8. - Directory entries are 3 bytes. The high nibble of byte 0 selects
VOL.n, and the low nibble plus the next two bytes form a 20-bit offset. - Sampled directory entries point to
VOL.*offsets beginning with bytes12 34; the third byte in those sampled resource headers matches the decoded volume number. - The generic volume reader at image offset
0x2e56reads a 5-byte volume record header, validates12 34and the volume byte, interprets the next two bytes as a little-endian payload length, then reads exactly that many payload bytes into either a supplied destination pointer or newly allocated memory. - The retry wrapper at image offset
0x2e32calls0x2e56repeatedly until it succeeds or error state[0x0f02]becomes5. - Four higher-level loaders call this generic reader through the four directory
accessors:
- Logic loader
0x119a:0x4371then0x2e32. - View loader
0x39f7:0x43a5then0x2e32. - Picture loader
0x4a3b:0x43d9then0x2e32. - Sound loader
0x5126:0x440dthen0x2e32.
- Logic loader
2026-07-01: logic payload structure and message decoding
Additional commands run from /Users/peter/ai/agi/reverse:
- Python one-off extraction of selected
LOGDIRpayloads fromVOL.*, using the locally derived directory entry and volume record formats. xxd -s 0x8f1 -l 80 -g 1 SQ2/AGIDATA.OVLstrings -a -t x SQ2/AGIDATA.OVL- Focused Rizin disassembly around image offsets
0x07ab,0x119a,0x11e8, and0x21f0. - Corrected a segment assumption: routine
0x07abusesDS:0x08f1, which at runtime isAGIDATA.OVL, not the executable image bytes at image offset0x08f1.
Documented result:
- Added
docs/src/logic_resources.md. - Logic payload byte 0 and byte 1 form a little-endian bytecode length.
- The logic bytecode begins at payload offset
0x0002. - The byte after the bytecode is the message count.
- The message table begins one byte after the count and contains
message_count + 1little-endian offsets relative to the table base. - Message table entry 0 is used as the end pointer for the encrypted message
area. Entries 1 through
message_countpoint to individual messages. - The message text region starts after the offset table and is XOR-decoded in
place by image offset
0x07ab. - The XOR key is the zero-terminated string at
SQ2/AGIDATA.OVLoffset0x08f1:Avis Durgan.
2026-07-01: logic bytecode dispatcher
Additional commands run from /Users/peter/ai/agi/reverse:
- Focused Rizin and
ndisasmdisassembly around image offsets0x02c4,0x07e3,0x0823,0x091a,0x293c, and related handler ranges. - Local Python dumps of raw executable bytes around image offsets
0x07d0,0x08f0, and ofSQ2/AGIDATA.OVLaround data offsets0x061dand0x08fd. - Local Python parser over present
LOGDIRresources to use the derived action and condition operand-count tables for a conservative bytecode scan.
Documented result:
- Added
docs/src/logic_bytecode.md. - The main logic interpreter at image offset
0x293cexecutes fromlogic_record[0x06]. - Main bytecode structural opcodes:
0x00(end): terminate current logic execution.0xfe: little-endian relative jump.0xff: conditional block.
- The action dispatcher at image offset
0x02c4uses the table atDS:0x061d, which is inAGIDATA.OVLat runtime. - The condition dispatcher at image offset
0x07e3uses the table atDS:0x08fd, also inAGIDATA.OVL. - Both dispatch tables use 4-byte entries: handler image offset, fixed operand count, and one metadata byte.
- Condition predicates
0x01..0x06are direct byte-variable comparisons over the array rooted atDS:0x0009. - The condition parser uses marker bytes
0xfd,0xfc, and0xff; the exact OR-group grammar around0xfcremains to be refined.
2026-07-01: condition parser and initial action families
Additional commands run from /Users/peter/ai/agi/reverse:
- Focused Rizin disassembly around image offsets
0x296c,0x7355,0x744c,0x74ee,0x09ea,0x7c1a,0x3a77, and0x3b47. - Local Python dump of selected action table entries from
SQ2/AGIDATA.OVLoffset0x061d, covering opcodes0x01..0x11,0x23,0x25..0x27,0x29..0x2c, and0xa5..0xa8.
Documented result:
- Updated
docs/src/logic_bytecode.md. - Refined the
0xfccondition marker behavior. The parser usesBHto track an active OR group. A true condition inside an OR group skips the remaining OR terms until the closing0xfc; a second0xfcwhile still in an OR group fails the condition list. - Identified the flag bitfield at
DS:0x0109. Helper0x7511maps a flag number tobyte = 0x0109 + flag / 8andmask = 0x80 >> (flag & 7). - Identified flag helpers:
0x74ee: set flag bit.0x74f4: clear flag bit.0x74fc: toggle flag bit.0x7502: test flag bit.0x752a: clear0x20bytes of flags starting at0x0109.
- Condition opcode
0x07(flag_set) tests an immediate flag number; condition opcode0x08(flag_set_var) tests a flag number read from the byte-variable array rooted atDS:0x0009. - Decoded variable actions:
0x01(inc_var): saturated increment ofvar[arg0].0x02(dec_var): saturated decrement ofvar[arg0].0x03(assignn): assign immediate to variable.0x04(assignv): assign variable to variable.0x05(addn)/0x06(addv): add immediate or variable.0x07(subn)/0x08(subv): subtract immediate or variable.0x09(indirect_assignv),0x0a(assign_indirectv),0x0b(indirect_assignn): indirect variable assignment forms.0xa5(muln)/0xa6(mulv): multiply by immediate or variable, storing the low byte.0xa7(divn)/0xa8(divv): divide by immediate or variable, storing the 8-bit quotient.
- Decoded flag actions:
0x0c(set_flag)/0x0d(clear_flag)/0x0e(toggle_flag): set, clear, or toggle an immediate flag number.0x0f(set_flag_var)/0x10(clear_flag_var)/0x11(toggle_flag_var): set, clear, or toggle a flag number read from a variable.
- Added conservative object/view action notes:
0x23calls helper0x0a06, which validates a 43-byte object entry, copies several position/resource fields, sets bits in word field[object+0x25], and calls list/graphics helpers.0x25and0x26set position-like fields[+0x03],[+0x05],[+0x16], and[+0x18]from immediates or variables.0x27stores the low bytes of[+0x03]and[+0x05]into variables.0x29,0x2a,0x2b, and0x2cresolve an object and dispatch to helpers0x3ae7or0x3bb7with either immediate or variable operands.
2026-07-01: additional common action handlers
Additional commands run from /Users/peter/ai/agi/reverse:
- Focused Rizin disassembly around image offsets
0x113d,0x125a,0x39b1,0x04d9,0x7e7c,0x6ce4,0x2c7a,0x5009,0x510a,0x5225,0x7d77,0x70b1, and0x71c0. - Local Python dump of selected action table entries from
SQ2/AGIDATA.OVLoffset0x061d, covering opcodes0x14..0x17,0x1e..0x1f,0x21..0x22,0x3f..0x40,0x51..0x52,0x62..0x64,0x7a..0x7b,0x82, and0x93.
Documented result:
- Updated
docs/src/logic_bytecode.mdwith another batch of action semantics. - Action
0x14(load_logic) loads a logic resource by immediate number via0x117d; action0x15(load_logic_var) does the same with a variable-sourced number. - Action
0x16(call_logic) invokes helper0x12ae, which locates or loads a logic resource and calls the main interpreter at0x293con that logic, preserving the previous current logic pointer at[0x0981]; action0x17(call_logic_var) uses a variable-sourced logic number. - Actions
0x1e(load_view) and0x1f(load_view_var) call the view-like resource loader0x39f7with immediate or variable-sourced resource numbers. - Action
0x62(load_sound) calls the sound-like resource loader0x5126, which uses the sound directory accessor0x440d, generic volume reader0x2e32, and builds four internal pointers from the payload. Action0x64(stop_sound_or_clear_sound_state) clears an active sound-like state through helper0x5234. - Actions
0x21(reset_object_state),0x22(clear_all_object_bits),0x3f(set_global_012d),0x40(set_object_bit_0100),0x51(move_object_to),0x52(move_object_to_var), and0x93(set_object_pos_dirty) update object/global state fields and object word flags. Field names remain provisional. - Actions
0x7a(setup_transient_object) and0x7b(setup_transient_object_var) fill globals0x0eae..0x0eb3, combine one operand into the high nibble of0x0eb3, then call helper0x2d52, which uses the object/resource helpers0x3ae7,0x3bb7, and0x3ccb. - Action
0x82(random_range_to_var) stores a generated value in a variable within an inclusive range. Helper0x71c0seeds a 16-bit state at0x1711from BIOS interrupt1aif needed, advances that state, and returns an 8-bit mixed value. - Inferred the dispatch-table metadata bit rule from decoded handlers and table dumps. Bit 7 corresponds to operand 0, bit 6 to operand 1, and so on. Set bits mark variable-slot/reference operands for table-aware decoding, while each handler still decides whether the slot is read or written.
2026-07-01: local logic disassembler and input/message handlers
Additional commands run from /Users/peter/ai/agi/reverse:
- Added and ran
tools/disassemble_logic.py, a local parser/disassembler for SQ2 logic resources using only the derivedLOGDIR,VOL.*, logic payload, and dispatch table formats. python3 tools/disassemble_logic.py 0python3 tools/disassemble_logic.py --stats- Focused Rizin disassembly around image offsets
0x095c,0x1c06,0x1ce8,0x0a8f,0x3c55,0x479f,0x7a80, and0x7b9c.
Documented result:
- Updated
docs/src/logic_bytecode.md. - Corrected the interpretation of the
0x0evariable-length skip rule. It applies to condition-list scanning paths, not to ordinary action opcode0x0e(toggle_flag); action0x0e(toggle_flag) remains the one-byte immediate flag toggle handler at0x7492. - Refined linear bytecode listing behavior: action
0x00(end) ends the current execution path, but later bytes in the same logic code area can still be branch targets, so the local static disassembler keeps scanning after0x00. - Decoded condition opcode
0x0e(input_word_sequence) as a variable-length parsed-input word sequence test. Its operand stream is a byte count followed by that many little-endian word IDs. Handler0x095ccompares those word IDs with a parsed input-word buffer rooted atDS:0x0c7b, using word[0x0ca3]as the parsed-word count. Operand word0x270fterminates the test successfully, and operand word0x0001behaves as a wildcard for one parsed word. On full match the handler sets flag 4. - Decoded action
0x65(display_message) as immediate message display and action0x66(display_message_var) as variable-sourced message display. Both resolve the current logic message through helper0x21f0and pass the string pointer to display helper0x1ce8. - Decoded actions
0x97(display_message_configured) and0x98(display_message_configured_var) as configured message display variants. They set temporary globals[0x0d0b],[0x0d0d], and[0x0d09]from three operand bytes before display, then reset those globals to0xffff. - Added more conservative object-action notes:
0x24(deactivate_object): deactivates/removes an active object by clearing bit0x0001in[object+0x25]and calling list/graphics helpers.0x2f(set_object_derived_resource_2): calls helper0x3ccbwith an immediate operand and clears object bit0x1000.0x36(set_object_field_24),0x37(set_object_field_24_var),0x38(clear_object_bit_0004), and0x39(get_object_field_24): set, set-from-variable, clear, or read object byte[+0x24]with bit0x0004in[+0x25].0x43(set_object_bit_0200) and0x44(clear_object_bit_0200): set or clear object bit0x0200.0x58(set_object_bit_0002) and0x59(clear_object_bit_0002): set or clear object bit0x0002.
- The local stats pass reports
LOGDIRentry 141 as an invalid-looking target: it decodes toVOL.0offset0x1ffff, where no valid12 34volume header is present.
2026-07-01: additional object and picture action handlers
Additional local tools and artifacts used:
- The MS-DOS 6.22 hard disk image at
build/dos622/dos622.imgwas created locally with QEMU and mtools. - SQ2 was copied into that image under
C:\SQ2. - QEMU was used to boot DOS, run
SIERRA.COM, and capture screenshots showing the title sequence and intro scene. This confirmed that the local DOS/QEMU setup can execute the game, but the handler work below is still based on static disassembly.
Additional commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py --statspython3 -B tools/disassemble_logic.py 0 1 2- Focused Rizin disassembly around image offsets
0x1700,0x4a00,0x6a80, and0x6c80. - Focused
ndisasmdisassembly around image offsets0x47e0,0x497b,0x4b80,0x6b80,0x6c54,0x6e02,0x6f3e,0x7000,0x74b0, and0x7e00. - Additional focused
ndisasmdisassembly around image offsets0x2250,0x7dba,0x911d,0x91cf, and0x93b1. These runs used file skips equal to image offset plus0x200; an earlier shifted dump around the0x7dbaarea was rejected after the exact handler offset was rechecked. - Local Python dump of selected action table entries from
SQ2/AGIDATA.OVLoffset0x061d, covering opcodes0x18..0x1b,0x2d..0x2e, and0x3a..0x57.
Documented result:
- Updated
tools/disassemble_logic.pywith conservative names for newly decoded common action opcodes. - Updated
docs/src/logic_bytecode.md. - Added picture-like action notes:
0x18(load_picture_var): variable-sourced picture-like resource load through helper0x4a3b.0x19(prepare_picture_var): variable-sourced picture-like resource preparation through helper0x4acf.0x1a(show_picture_like): picture/display finalization-like action that clears flag 15, calls helpers0x1f2band0x5546, and sets[0x1216] = 1.0x1b(discard_picture_var): variable-sourced picture-like resource unlink/release helper.
- Added object bit actions:
0x2d(set_object_bit_2000)/0x2e(clear_object_bit_2000): set/clear object bit0x2000.0x3a(clear_object_bit_0010)/0x3b(set_object_bit_0010): clear/set object bit0x0010through helpers that wrap the update in redraw/cache calls.0x3d(set_object_bit_0008)/0x3e(clear_object_bit_0008): set/clear object bit0x0008.0x40(set_object_bit_0100): set object bit0x0100.0x41(set_object_bit_0800): set object bit0x0800.0x42(clear_object_bits_0900): clear object bits0x0100and0x0800.0x46(clear_object_bit_0020)/0x47(set_object_bit_0020): clear/set object bit0x0020.
- Added object field/action notes:
0x45(object_distance_to_var): computes a capped distance-like value between two active objects and stores it in a variable, or stores0xffif either object is inactive.0x48..0x4b: set object byte[+0x23]to modes 0, 1, 3, or 2, with the mode 1 and mode 2 forms also setting bits0x1030, storing an immediate in[+0x27], and clearing the corresponding flag.0x4c(set_object_field_1f_var),0x4f(set_object_field_1e_var),0x50(set_object_field_01_var),0x56(set_object_field_21_var), and0x57(get_object_field_21): move values between variables and object bytes[+0x1f],[+0x1e],[+0x01], and[+0x21].0x4d(clear_object_fields_21_22),0x4e(clear_object_field_22_and_global),0x53(approach_first_object_until_near),0x54(start_random_motion),0x55(stop_motion_mode),0x83(clear_global_0139), and0x84(set_global_0139_and_clear_object0_field_22): update object byte[+0x22]and related globals, especially[0x0139].
- Added interpreter/resource-control notes:
0x12(switch_room_like)/0x13(switch_room_like_var): broad room/state switch helpers that stop active sound, reset object entries, update byte variable 0, load the target logic, set flag 5, clear the status table at0x1218, and call redraw/reinit helpers.0x63(start_sound_with_flag): starts a sound-like resource and associates a flag with completion or active-state handling by storing it in[0x126a].
- Added formatted-message and menu/list-like UI notes:
0x67(display_formatted_message)/0x68(display_formatted_message_var): configured formatted-message display helpers. They call setup helper0x2b28, pass two placement/configuration values to0x2b0d, resolve a current-logic message through0x21f0, format/copy it into a large stack buffer via0x1f54, send it to0x2390, then call cleanup helper0x2b4f. The0x68form reads all three operands through variables.0x9c(add_menu_heading_like): allocates and links an 18-byte top-level node that stores a message pointer, active marker, and position-like value.0x9d(add_menu_item_like): allocates and links a 14-byte item node under the current top-level node, storing message pointer, item id, active marker, and row/column-like values.0x9e(finalize_menu_like): finalizes the menu/list-like structure and sets[0x1d2a] = 1, after which later additions are ignored by the handlers.0x9f(enable_menu_item_like)/0xa0(disable_menu_item_like): walk the menu/list-like structure and set or clear the active marker on item nodes whose stored id matches the operand.0xa1(mark_menu_if_flag_0e): tests flag0x0e; if set, writes[0x1d22] = 1.
- Corrected action
0x94(set_object_pos_dirty_var): exact disassembly at image offset0x7dbashows that it is the variable-coordinate counterpart to0x93, storingvar[arg1]andvar[arg2]into object fields[+0x03]and[+0x05], setting bit0x0400, and calling helper0x593a.
2026-07-01: text-window and auxiliary table action handlers
Additional commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py --statspython3 -B tools/disassemble_logic.py 0 1 2 3 4 5 6 7 8 9 10- Local Python dump of selected action table entries from
SQ2/AGIDATA.OVLoffset0x061d, covering opcodes0x5a..0x61,0x69..0x71,0x76..0x79,0x81, and0xa2..0xa4. - Focused
ndisasmdisassembly around image offsets0x2b78,0x34bd,0x3547,0x382e,0x386f,0x4c3d,0x5e9b,0x5ebf,0x7538,0x7663,0x7714,0x7803,0x7a00, and0x7b4e.
Documented result:
- Updated
tools/disassemble_logic.pywith conservative names for another batch of action opcodes, mostly text-window, prompt/status, and auxiliary table handlers. - Added an operand metadata override for action
0xa2(display_view_resource_text_like_var): the table byte is0x01, but exact handler disassembly at0x5e9bshows it reads the resource number fromvar[arg0]. - Added text-window/action notes:
0x69(clear_text_rect): clears/fills a text rectangle through BIOSint 10hserviceAH=0x06via helper0x2b78.0x6a(enable_text_attr_mode_1757)/0x6b(disable_text_attr_mode_1757): enable/disable an alternate text-attribute mode tracked by byte[0x1757], then refresh related text areas.0x6d(set_text_window_pair): updates globals[0x05d1],[0x05cd], and[0x05cf]through helper0x77d5.0x6e(shake_screen_like): display-shake-like action that either calls display-mode-specific helpers or writes CRT controller ports0x3d4/0x3d5directly.0x70(show_status_line_like)/0x71(hide_status_line_like): show/hide a status-line-like area controlled by word[0x05d9].0x77(disable_input_line_like)/0x78(enable_input_line_like): disable/enable an input-line-like area controlled by word[0x05d3].0xa3(set_global_0d0f)/0xa4(clear_global_0d0f): set/clear word[0x0d0f], which helper0x3652consults while updating input-line display state.
- Added auxiliary resource/table notes:
0x5a(set_rect_bounds_0131)/0x5b(clear_rect_bounds_0131): set/clear a rectangle/bounds filter stored in globals[0x0131],[0x0133],[0x0135],[0x0137], and[0x013d].0x5c..0x61: manipulate byte[+0x02]in 3-byte entries from the table rooted at[0x0971], validating against end pointer[0x0973].0x79(map_key_event): stores(arg0 | arg1 << 8, arg2)into the first free four-byte slot among 39 slots rooted at0x0145.0x81(display_view_resource_text_like)/0xa2(display_view_resource_text_like_var): immediate and variable forms of a view-like resource display/preview helper that loads the resource, builds a temporary object-like record, displays a resource-derived string through0x1ce8, and cleans up temporary allocations.
2026-07-01: resource accessors and prompt/session handlers
Additional commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py --stats- Local Python dump of selected action table entries from
SQ2/AGIDATA.OVLoffset0x061d, covering opcodes0x2f..0x35,0x72..0x76,0x85..0x86,0x8e, and0x91..0x92. - Focused
ndisasmdisassembly around image offsets0x027f,0x0d37,0x1335,0x3c8c,0x3e25,0x4de8,0x4e8d,0x5234,0x716a, and0x71ed. python3 -B tools/disassemble_logic.py --limit 200 | rg ...was attempted for call-site sampling, but the helper aborted at the known invalidLOGDIRentry 141. The partial output before that abort was used only as supporting call-site evidence; the verification pass continues to use--stats, which records and skips the bad entry.
Documented result:
- Added resource-derived object accessor notes:
0x30(set_object_derived_resource_2_var): variable-argument counterpart to0x2f, calling helper0x3ccb. The helper selects a derived subresource/loop-like entry, updates object byte[+0x0e], pointer[+0x10], words[+0x1a]and[+0x1c], clamps object coordinates, and sets bit0x0400if it adjusts them.0x31(get_object_resource_loop_count): stores*([object+0x0c]) - 1into a variable, apparently a count from the object’s loaded resource table.0x32(get_object_field_0e),0x33(get_object_field_0a),0x34(get_object_field_07), and0x35(get_object_field_0b): copy object bytes[+0x0e],[+0x0a],[+0x07], and[+0x0b]into variables.0x35was present in the table but not encountered in the current SQ2 scan.
- Added string/prompt notes:
0x72(set_string_slot_from_message): copies a current-logic message into fixed string slot0x020d + arg0 * 0x28through helper0x4de8.0x76(prompt_number_to_var): displays a current-logic message prompt, accepts up to four characters through helper0x0da9, parses the result as decimal via0x4e8d, and stores the low byte in a variable.0x85(display_object_diagnostics_var): formats several object fields into a display string using template pointer0x1713, then displays it through0x1ce8.
- Added interpreter/session-control notes:
0x86(confirm_and_restart_like): stops sound state and conditionally calls helper0x02ae, which calls0x8275and0x00ae(0). The handler displays string0x05e3as a confirmation path when the operand is not 1.0x8e(set_global_0141_and_refresh): stores a word at[0x0141]and calls refresh helper0x707cwrapped in0x6a54/0x6a8e.0x91(save_logic_resume_ip): saves the current bytecode pointer into[current_logic+0x06].0x92(restore_logic_entry_ip): restores[current_logic+0x06]from[current_logic+0x04].
2026-07-01: graphics and object pipeline synthesis
Additional commands run from /Users/peter/ai/agi/reverse:
sed -n '1,220p' AGENTS.mdrg -n "picture|draw|object|graphics|0x4a3b|0x4acf|0x6445|0x6a54|0x6a8e|0x593a|0x59fa|0x3ae7|0x3ccb|0x09ea|0x0a06" docs/src toolsls docs/srcpython3 -B tools/disassemble_logic.py --stats- Focused
ndisasmdisassembly around image offsets0x0307,0x09ea,0x3979,0x3bb7,0x4a16,0x5200,0x5546,0x593a,0x6440,0x6a20,0x6a54, and0x9097. These runs used file skips equal to image offset plus0x200, matching the decrypted MZ header size. - Additional reads of existing docs with
sedandrgto avoid duplicating opcode tables already captured inlogic_bytecode.md.
Documented result:
- Added
docs/src/graphics_object_pipeline.md. - Added the new chapter to
docs/src/SUMMARY.md. - Consolidated the picture load/decode path:
- Action
0x18(load_picture_var) calls loader0x4a3b, which uses cache lookup0x49e8, directory accessor0x43d9, and generic reader0x2e32. - Action
0x19(prepare_picture_var) calls helper0x4acf, stores the selected payload pointer at[0x1377], wraps the operation in0x6a54/0x6a8e, and decodes the picture through0x6445. - Action
0x1a(show_picture_like) clears flag 15, calls display helpers, and sets[0x1216] = 1.
- Action
- Documented the picture command scanner at
0x6475, including the0xf0..0xfadispatch range, the0xffterminator, and the observed drawing globals[0x1369],[0x136b],[0x136c],[0x136d],[0x136e], and[0x136f]. - Expanded the view/object binding model:
- Helper
0x3ae7binds a cached view-like payload to object fields+0x07and+0x08, copies payload byte+0x02into object byte+0x0b, and delegates to0x3bb7. - Helpers
0x3c1b,0x3ccb, and0x3d6aselect nested subresources, update object pointer/size fields, and clamp object coordinates.
- Helper
- Added a field map for the 43-byte object records rooted at
[0x096b]. - Documented object activation/deactivation helpers
0x0a06and0x0aab, including their list flushing/rebuild calls and the observed active bit0x0001. - Documented placement helper
0x593aand bounds helper0x5a14, including the screen limits0xa0and0xa7and the horizon-like global[0x012d]. - Clarified the update-list wrappers:
0x6a26builds list root0x16ffthrough shared builder0x0358.0x6a3dbuilds list root0x1703through shared builder0x0358.0x6a54flushes both roots through0x0307.0x6a8erebuilds and processes both roots through0x045e.0x6aabcompares current and saved object fields through0x0488.
2026-07-01: view payload and object overlay rendering
Additional commands run from /Users/peter/ai/agi/reverse:
rg -n "0x3d6a|0x9097|0x9177|0x9db0|0x9db6|0x5762|view payload|render/update|update node|0x042f|0x0358" docs/src tools- Focused
ndisasmdisassembly around main executable image offsets0x0307,0x0358,0x3d6a,0x587d, and0x9097. A few intermediate disassembly commands used mismatched-o/-evalues and were rejected; the observations documented here use the corrected convention-o image_offsetand-e image_offset + 0x200. wc -c build/cleanroom/AGI.decrypted.exe SQ2/EGA_GRAF.OVL SQ2/IBM_OBJS.OVL SQ2/AGIDATA.OVLfile SQ2/EGA_GRAF.OVL SQ2/IBM_OBJS.OVL SQ2/AGIDATA.OVLxxd -l 128 -g 1 SQ2/EGA_GRAF.OVLxxd -l 128 -g 1 SQ2/IBM_OBJS.OVLndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVLndisasm -b 16 -o 0x9800 SQ2/EGA_GRAF.OVLpython3 -B tools/inspect_view.py 0 1 2 10 --groups 4 --frames 5python3 -B tools/inspect_view.py --limit 12 --groups 2 --frames 3python3 -B tools/inspect_view.py 11 --groups 4 --frames 4python3 -B -m py_compile tools/disassemble_logic.py tools/inspect_view.py
Documented result:
- Added
tools/inspect_view.py, a deterministic local helper for printing the observed view-like payload structure using only the locally derived directory and volume readers. - Confirmed that calls to
0x9db0,0x9db3, and0x9db6targetIBM_OBJS.OVL, which is loaded at segment0x09dband therefore appears at near offsets starting at0x9db0. - Documented the three IBM object-overlay entry jumps:
0x9db0 -> 0x9db9: save a screen rectangle into a node backing buffer.0x9db3 -> 0x9df8: restore a screen rectangle from a node backing buffer.0x9db6 -> 0x9e35: draw an object’s selected frame into the graphics buffer.
- Documented render/update node layout from allocator
0x9097: next pointer, previous pointer, object pointer, rectangle coordinates/dimensions, and a backing-buffer pointer. - Expanded the view-like payload format:
- Payload byte
+0x02is the top-level group count. - The group offset table begins at payload
+0x05, with 16-bit offsets relative to the payload base. - Each group starts with a frame count, followed by 16-bit frame offsets relative to the group base.
- Each frame begins with width, height, and a control byte, followed by row-terminated encoded data.
- Payload byte
- Local
tools/inspect_view.pysamples matched the helper-derived layout. For example, view 11 has two groups; group 0 starts at offset0x09, has two frames, and its first frame starts at offset0x0ewith size20x5and control byte0x01. - Documented the current frame-data model from object overlay draw routine
0x9e35: zero bytes terminate rows; nonzero bytes use the high nibble as a color-like value and the low nibble as a run length; a run whose high nibble matches the frame control byte’s low nibble advances without writing.
2026-07-01: object update-list selection and movement pass
Additional commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' AGENTS.mdsed -n '1,260p' docs/src/graphics_object_pipeline.mdtail -n 180 docs/src/clean_room_executable_notes.mdrg -n "0x150a|0x4719|0x56b8|0x69e4|0x6a05|0x6b44|0x6b62|0x0400|0x0051" docs/src tools- Focused
ndisasmdisassembly around main executable image offsets0x13f0,0x4619,0x55b8,0x583a, and0x69c0. Some intermediate commands used mismatched file skips and were rejected; the observations below use the corrected convention-o image_offsetand-e image_offset + 0x200forbuild/cleanroom/AGI.decrypted.exe.
Documented result:
- Refined the update-list predicates:
- Callback
0x69e4, used by builder wrapper0x6a26for root0x16ff, accepts objects when(object[+0x25] & 0x0051) == 0x0051. - Callback
0x6a05, used by builder wrapper0x6a3dfor root0x1703, accepts objects when(object[+0x25] & 0x0051) == 0x0041. - Therefore flag bit
0x0010partitions otherwise active/eligible objects between the two update-list roots. Helpers0x6b44and0x6b62clear and set that bit while wrapping the change with0x6a54/0x6a8e.
- Callback
- Documented movement pass
0x150a:- It clears event globals
[0x000e],[0x000d], and[0x000b]. - It scans object records from
[0x096b]to[0x096d]in0x2b-byte strides, processing only objects whose flag word satisfies(object[+0x25] & 0x0051) == 0x0051. - Object byte
+0x01is a countdown/tick divider reloaded from byte+0x00. - Unless bit
0x0400is set, direction byte+0x21, step byte+0x1e, and signed-delta tables at0x0a61and0x0a73produce proposed X/Y movement. - Proposed movement is clamped to left, right, top, bottom, and horizon-like bounds, producing boundary codes 1 through 4.
- The move is accepted only when
0x4719(object)returns zero and0x56b8(object)returns nonzero. Otherwise the previous X/Y coordinates are restored and placement search helper0x593a(object)is called. - Boundary events are written to
[0x000b]for objects with byte+0x02 == 0or to[0x000d]/[0x000e]for nonzero byte+0x02. If byte+0x22 == 3, helper0x16b9(object)ends that motion/control state. - The pass clears object bit
0x0400before leaving an object.
- It clears event globals
- Documented helper
0x4719(object)as an object-object collision/crossing test. It skips objects with bit0x0200, skips candidates with matching byte+0x02, checks horizontal rectangle overlap from X and width, then checks whether current and previous Y positions cross. - Documented helper
0x56b8(object)as a control/priority-buffer acceptance test. It may derive object byte+0x24from table0x127a, scans high nibbles in the graphics/control buffer at[0x136f], reacts to nibble classes0x10,0x20, and0x30, and returns nonzero to permit a proposed move. - Added targeted-motion notes for helpers
0x1672,0x16ed, and0x16b9: direction-like byte+0x21can be computed from the current object position to target fields+0x27/+0x28, and0x16b9restores byte+0x1efrom+0x29while setting completion flag+0x2aand clearing motion/control byte+0x22.
2026-07-01: graphics/control buffer helper pass
Additional commands run from /Users/peter/ai/agi/reverse:
- Focused
ndisasmdisassembly around main executable image offsets0x4c80,0x5200,0x5480,0x5528,0x5660,0x5700, and0x57c0. xxd -s 0x52b5 -l 8 -g 2 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x0 -e 0x200 build/cleanroom/AGI.decrypted.exe | rg "call 0x4d10|call 0x56a2|call 0x4cbb|call 0x5666|call 0x56b8|call 0x5762|call 0x57cf"- A raw
xxdread at file offset0x127aand an origin-shifted whole-image call-site scan were rejected. The0x127atable described below is a runtime table initialized by code, and the accepted whole-image call-site scan uses-o 0x0 -e 0x200.
Documented result:
- Added a graphics/control buffer helper section to
docs/src/graphics_object_pipeline.md. - Documented helper
0x5257as a buffer fill routine for the segment stored in[0x136f]. It writes0x3480words, matching a0x6900-byte grid. Picture decoding calls it withAX = 0x4f4f, while helper0x5528calls it withAX = 0x4040. - Documented helper
0x5666as the direct coordinate-to-buffer conversionDI = y * 0xa0 + x, withAL = yandAH = x. - Documented helper
0x56a2as the default initializer for the 168-byte table rooted at0x127a: rows0..47map to 4, and subsequent 12-row bands map to values 5 through 14. - Documented helper
0x4cbb(value)as a reverse mapping from a priority/control value toward a Y row. In one mode it scans the0x127atable downward; when[0x124a]is nonzero it uses(value - 5) * 12 + 0x30. - Documented helper
0x57cf(object)as a post-draw buffer marker. It calls the object overlay draw entry0x9db6, ensures object byte+0x24has a low nibble derived from the Y table if absent, and writes the high nibble of+0x24around the object’s buffer footprint while preserving low nibbles. - Refined helper
0x56b8(object): it scans the high nibbles of the selected frame width at the object’s X/Y buffer row. High nibble0x00rejects immediately;0x10requires object flag bit0x0002;0x20records a seen class;0x30continues without changing the tracked class. After the scan, bits0x0100and0x0800can reject the final class state, and objects with byte+0x02 == 0update global flags 3 and 0 through0x74ee/0x74f4.
2026-07-01: transient object and view-preview rendering pass
Additional commands run from /Users/peter/ai/agi/reverse:
sed -n '1,220p' AGENTS.mdsed -n '1,460p' docs/src/graphics_object_pipeline.mdtail -n 180 docs/src/clean_room_executable_notes.mdrg -n "0x57cf|0x5a14|0x5a3a|0x5fd3|0x6006|0x56b8|0x5762|0x980c|0x9812|0x9db6|0x1c54|0x2e1b|0x2e28" docs/src tools- Focused
ndisasmdisassembly around main executable image offsets0x1c20,0x2c40, and0x5e80, using-e image_offset + 0x200. python3 -B tools/disassemble_logic.py --limit 5 | sed -n '1,220p'- Follow-up reads of the existing
0x7a,0x7b,0x81, and0xa2opcode notes indocs/src/logic_bytecode.md. python3 -B tools/inspect_view.py --limit 40 --groups 1 --frames 1sed -n '1,260p' tools/inspect_view.py
Documented result:
- Added a transient/preview object section to
docs/src/graphics_object_pipeline.md. - Refined the
0x7aand0x7bentries indocs/src/logic_bytecode.md. - Updated
tools/inspect_view.pyto print the observed preview/display string offset fromu16(payload + 0x03). - Documented actions
0x7a(setup_transient_object)/0x7b(setup_transient_object_var) as callers of helper0x2d52, which uses a fixed 43-byte object-like record at0x0eb4.- Staged byte
0x0eaeselects the view-like resource. 0x0eafselects the top-level group.0x0eb0selects the frame/derived entry.0x0eb1and0x0eb2are X/Y coordinates.- The low and high nibbles of
0x0eb3feed object byte+0x24. - The helper binds the view through
0x3ae7, selects group/frame through0x3bb7and0x3ccb, places the object with0x593a, draws/marks it through0x57cf, rebuilds update lists with0x6a54/0x6a8e, and calls0x5762.
- Staged byte
- Documented the fixed transient record’s initialization:
- Its selected frame pointer is copied to saved-frame field
+0x12. - Staged X/Y are copied into both current and saved coordinate fields.
- Flag word
+0x25starts as0x020c, combining fixed-priority, horizon-exempt, and collision-skip behavior. - If the staged priority/control low nibble is zero, the helper later
replaces the flag word with
0x0008before drawing/marking.
- Its selected frame pointer is copied to saved-frame field
- Refined the
0x81/0xa2view-resource preview path:- Helper
0x5edbrecords whether the view resource was already cached, temporarily sets[0x0f18] = 1while loading it, and initializes a stack-local 43-byte object-like record with group/frame zero. - The temporary preview object is centered with
x = (0x9f - width) / 2, giveny = 0xa7, fixed priority/control byte+0x24 = 0x0f, and grouping byte+0x02 = 0xff. - If enough memory is available, the helper allocates a render node through
0x9097, saves the backing rectangle with0x9db0, draws with0x9db6, and later restores with0x9db3and frees with0x910a. - The displayed string pointer is
payload + u16(payload + 0x03), giving the first observed consumer for view payload bytes+0x03..+0x04. - The first 40 present SQ2 view resources sampled by
tools/inspect_view.pyall hadu16(payload + 0x03) == 0, so a nonzero local example remains to be found. - If the resource was not cached before the preview, the helper releases it
through
0x3f0d.
- Helper
2026-07-01: object rectangle conditions and configured motion bounds
Additional commands run from /Users/peter/ai/agi/reverse:
sed -n '1,220p' AGENTS.mdrg -n "condition|0x0b|object_rect|rect_test|0x08c6|0x7be6|0x013d|0x0131|0x4719|0x47ef" docs/src toolssed -n '260,360p' docs/src/logic_bytecode.mdsed -n '1,520p' docs/src/graphics_object_pipeline.md- Focused
ndisasmdisassembly around main executable image offsets0x0800,0x0680,0x06c0,0x47e0, and0x7b40, using-e image_offset + 0x200. ndisasm -b 16 -o 0x0 -e 0x200 build/cleanroom/AGI.decrypted.exe | rg "call 0x7be6|call 0x08c6|call 0x08cc|call 0x08db|call 0x08e8|call 0x091a|call 0x47ef"- Follow-up reads of the condition-name table in
tools/disassemble_logic.pyand the condition documentation indocs/src/logic_bytecode.md.
Documented result:
- Refined the local names for condition opcodes
0x0b(object_left_baseline_in_rect),0x10(object_width_baseline_in_rect),0x11(object_center_baseline_in_rect), and0x12(object_right_baseline_in_rect) intools/disassemble_logic.py. - Expanded the condition documentation for object rectangle tests:
- Shared helper
0x091aresolves object indexarg0, loads object X intoDHandCH, and loads object Y intoDL. - Common comparison helper
0x08f0checksDH >= arg1,DL >= arg2,CH <= arg3, andDL <= arg4. - Condition
0x0b(object_left_baseline_in_rect) tests object left X/baseline Y. - Condition
0x10(object_width_baseline_in_rect) tests the full horizontal span from left X tox + width - 1. - Condition
0x11(object_center_baseline_in_rect) tests horizontal center X. - Condition
0x12(object_right_baseline_in_rect) tests right X.
- Shared helper
- Documented the configured rectangle helper:
- Action
0x5a(set_rect_bounds_0131) stores bounds in[0x0131],[0x0133],[0x0135], and[0x0137], and sets[0x013d] = 1. - Action
0x5b(clear_rect_bounds_0131) clears[0x013d]. - Helper
0x7be6(x, y)returns true only for points strictly inside the configured rectangle. - Helper
0x06d9(object)compares whether the object’s current baseline point and next step point are on the same side of that rectangle. A crossing sets object bit0x0080, clears direction byte+0x21, and clears global byte[0x000f]when the object is the first object record. No crossing clears bit0x0080.
- Action
2026-07-01: object dirty rectangles and graphics-overlay refresh entries
Additional commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -n '1,240p' AGENTS.mdsed -n '1,260p' docs/src/graphics_object_pipeline.mdtail -n 120 docs/src/clean_room_executable_notes.mdrg -n "0x5762|0x970c|0x9812|0x9815|0x5546|0x5528|0x9db0|0x9db3|0x9db6|display" docs/src tools- Focused
ndisasmdisassembly around main executable image offsets0x5500,0x5700, and0x9600, using-e image_offset + 0x200. sed -n '430,530p' docs/src/graphics_object_pipeline.mdsed -n '160,210p' docs/src/agi_executable.mdrg -n "0x9800|0x980c|0x9812|0x9815|0x9837|JR_GRAF|CGA_GRAF|IBM_OBJS|load overlay|OVL" docs/src toolsls -l build/cleanroom/AGI.decrypted.exe SQ2/*.OVLndisasm -b 16 -o 0x9800 SQ2/EGA_GRAF.OVLndisasm -b 16 -o 0x9800 SQ2/CGA_GRAF.OVLndisasm -b 16 -o 0x9800 SQ2/VG_GRAF.OVL- Follow-up reads of the render/update section in
docs/src/graphics_object_pipeline.md.
Documented result:
- Replaced the previous tentative note about helper
0x5762with a concrete dirty-rectangle interpretation:- It returns immediately unless word
[0x1216]is nonzero. - It compares the current frame pointer
object+0x10and saved frame pointerobject+0x12, plus current/saved X/Y fields+0x03/+0x05and+0x16/+0x18. - It copies the current frame pointer to
+0x12. - It computes the union rectangle covering the old and new object frame footprints.
- It calls graphics-overlay entry
0x980cwith that union rectangle.
- It returns immediately unless word
- Documented the common rectangle argument contract used by
0x980cand0x9812:AH = left XAL = bottom YBL = widthBH = height
- Documented graphics-overlay entry
0x980cas a rectangle copy from the interpreter’s logical graphics buffer segment[0x136f]to display memory segment[0x1371]. - Documented graphics-overlay entry
0x9812as a rectangle fill; in the EGA and VGA overlays, low byteDLsupplies the fill value. - Refined helpers around the full-screen display path:
0x5528clears the logical graphics buffer with fill word0x4040, calls graphics-overlay entry0x980f, rebuilds the default priority/control table with0x56a2, then calls entry0x9800.0x5546can swap nibbles across the logical graphics buffer when[0x1755] & 1is set, calls HGC-specific helper0x9899in display mode 2, then calls0x980cfor the full0xa0by0xa8screen rectangle.0x5624converts the common coordinate tuple into display-memory offsets, with display-mode branches controlled by[0x1130]and[0x112e].
- Added the EGA graphics overlay entry table from local disassembly of
SQ2/EGA_GRAF.OVLloaded at near origin0x9800:0x9800 -> 0x9815: set graphics mode0x0dand store video segment0xa000in[0x1371].0x9803 -> 0x9835: return to text mode and clear/configure the text screen.0x9806 -> 0x986f: reinitialize graphics and call0x5546.0x9809 -> 0x9884: no-op in EGA.0x980c -> 0x9885: copy a logical-buffer rectangle to EGA display memory.0x980f -> 0x9983: initialize row-offset table0x137band clear a display-memory range.0x9812 -> 0x9907: fill a display rectangle.
2026-07-01: update-list phase order and stationary object flag
Additional commands run from /Users/peter/ai/agi/reverse:
- Focused
ndisasmdisassembly around main executable image offsets0x0300,0x0400, and0x69c0, using-e image_offset + 0x200. ndisasm -b 16 -o 0x0 -e 0x200 build/cleanroom/AGI.decrypted.exe | rg "4000|6a54|6a8e|6aab|045e|0488|0307|0358"- Focused follow-up disassembly around image offsets
0x0b80,0x3f30, and0x67f0, usingsedto limit the visible output.
Documented result:
- Refined the update-list lifecycle:
0x0307(root)walks root nodes, restores each saved rectangle through0x9db3, then calls0x032d(root)to free nodes and clear root pointers.0x032d(root)frees nodes through0x910awithout doing the restore pass.0x045e(root)walks from the list tail backward, saving each node’s backing rectangle with0x9db0and drawing the node’s object through0x9db6.0x6a54restores/frees roots0x16ffand0x1703through0x0307.0x6a71frees both roots through0x032dwithout restoration.0x6a8erebuilds and draws root0x1703first, then root0x16ff.0x6aabruns0x0488over root0x1703first, then root0x16ff.
- Refined helper
0x0488(root):- For each node it calls
0x5762(object)before comparing saved fields. - It only performs the position comparison when object byte
+0x01equals reload byte+0x00. - If current X/Y
+0x03/+0x05equal saved X/Y+0x16/+0x18, it sets flag bit0x4000. - Otherwise it copies current X/Y to saved X/Y and clears bit
0x4000.
- For each node it calls
- Refined object flag bit
0x4000from a generic comparison marker to a stationary/stuck marker used by later motion helpers. - Observed two consumers of bit
0x4000:- Helper
0x3f5a, reached from motion/control mode byte+0x22 == 1, picks a new random direction through0x3fa3when its local countdown expires or when bit0x4000is set. - The helper around
0x0bb3, reached from the+0x22 == 2path, can also replace direction byte+0x21with a random nonzero direction when bit0x4000reports no movement.
- Helper
2026-07-01: logic cache lifetime and room-switch scheduler path
Additional commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -n '1,220p' AGENTS.mdsed -n '1,360p' docs/src/logic_bytecode.mdsed -n '1,280p' docs/src/logic_resources.mdtail -n 220 docs/src/clean_room_executable_notes.mdrg -n "0x117d|0x119a|0x12ae|0x1364|0x13a5|0x1792|heap|cache|logic record|0x0977|0x0985|0x0983" docs/src tools/disassemble_logic.py- Initial raw
ndisasmreads around the same regions. These produced too much output because-eis the input skip amount, not an end offset; they were used only for coarse orientation. - Focused follow-up
ndisasmdisassembly around main executable image offsets0x10d0,0x117d,0x1364, and0x1792, using the decrypted executable header skip andsedto limit the visible output.
Documented result:
- Expanded
docs/src/logic_resources.mdwith the 10-byte logic cache record layout:+0x00next record pointer.+0x02logic number byte.+0x03message count byte.+0x04bytecode base pointer, equal topayload + 2.+0x06current interpreter instruction pointer.+0x08message offset table base pointer.
- Documented helper
0x110f(logic_number)as the logic-cache scan. It walks the list rooted at[0x0977]and stores the link slot for the matching or insertion position in[0x0983]. - Refined loader
0x119a(logic_number):- On cache miss it calls
0x6a54, allocates a 10-byte record through0x13d6, links it through[0x0983], loads the resource through0x4371and0x2e32, derives bytecode/message pointers, temporarily sets[0x0981]while decrypting message text, then calls0x6a8e. - On cache hit it returns the existing record.
- On cache miss it calls
- Documented call helper
0x12ae(logic_number):- It preserves the previous current logic pointer
[0x0981]. - If the target logic is already cached, it interprets that record in place.
- If the target logic is missing, it loads it through
0x119a, runs interpreter0x293c, unlinks it afterward through the saved[0x0983]slot, and rewinds the heap top to the start of that transient record through0x143c. - Actions
0x16(call_logic) and0x17(call_logic_var) propagate a zero interpreter result as a zero next-instruction pointer, stopping the current logic loop.
- It preserves the previous current logic pointer
- Documented routine
0x1364as a snapshot writer for loaded logic execution positions. It emits 4-byte entries at0x0985containing logic number andcurrent_ip - bytecode_base, followed by a0xffffterminator. - Documented routine
0x13a5(record)as the matching restore path, settingrecord[0x06] = record[0x04] + saved_offsetwhen it finds the record’s logic number in the0x0985table. - Added heap-pointer helpers used by this path:
0x13d6(size)allocates from[0x0a55].0x143c(ptr)sets or rewinds[0x0a55].0x1485restores the heap pointer from mark[0x0a59]after freeing update-list nodes.0x14a0updates the free-memory status byte[0x0011].
- Refined room/state switch helper
0x1792, reached by actions0x12(switch_room_like) and0x13(switch_room_like_var):- It stops active sound, restores heap/update-list state through
0x1485, calls cleanup helpers0x4482,0x707c, and0x706d, and resets every object record’s active/resource/frame state. - It sets
[0x0139] = 1, stores0x24in[0x012d], saves old byte variable 0 in byte variable 1, writes the destination logic number to byte variable 0, clears bytes[0x000d]and[0x000e], and stores object 0’s view/resource byte in[0x0019]. - It loads the destination logic through
0x117d, optionally loads the logic named by[0x1d12], may reposition object 0 from boundary byte[0x000b], sets flag 5, and calls redraw/reinitialization helpers.
- It stops active sound, restores heap/update-list state through
2026-07-01: input parsing action and WORDS.TOK dictionary format
Additional commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -n '1,220p' AGENTS.mdrg -n "action_75|0x75|0x1958|0x18ac|0x0c7b|0x0c8f|0x0ca3|input_word_sequence|parsed input|word" docs/src tools/disassemble_logic.py- Focused
ndisasmdisassembly around main executable image offsets0x1800,0x0c00,0x0e00,0x1a30,0x4d80,0x4f50, and0x5000, using the decrypted executable header skip andsedto limit output. xxd -s 0x0940 -l 0x50 -g 1 SQ2/AGIDATA.OVLxxd -s 0x0c60 -l 0x70 -g 1 SQ2/AGIDATA.OVLls -l SQ2xxd -l 160 -g 1 SQ2/WORDS.TOKxxd -s 0x40 -l 160 -g 1 SQ2/WORDS.TOKxxd -s 0x1a50 -l 160 -g 1 SQ2/WORDS.TOK- Local Python sanity check of the inferred
WORDS.TOKdecoder, followed by the deterministic scripttools/inspect_words.py. python3 -B tools/inspect_words.py --limit 60python3 -B tools/inspect_words.py --prefix look --limit 20python3 -B tools/inspect_words.py --id 0x0001 --limit 20python3 -B tools/inspect_words.py --prefix get --limit 20
Documented result:
- Added
tools/inspect_words.py, a deterministic local inspector forSQ2/WORDS.TOKbased on the parser format inferred from the executable. - Named action opcode
0x75(parse_string_slot) asparse_string_slotintools/disassemble_logic.py. - Expanded
docs/src/logic_bytecode.mdwith the producer side for condition opcode0x0e. - Documented action
0x75(parse_string_slot) at image offset0x1958:- It clears flags 2 and 4.
- It reads one immediate string-slot index.
- If the index is below 12, it parses fixed string slot
0x020d + index * 0x28through helper0x18ac.
- Documented parser helper
0x18ac:- It clears parsed-word ID table
0x0c7band parsed-word pointer table0x0c8f. - It normalizes the input string into buffer
0x0ca7through helper0x199d. - It fills
0x0c7b,0x0c8f, word[0x0ca3], and byte variable[0x0012], then sets flag 2 when a parse result exists.
- It clears parsed-word ID table
- Documented normalization helper
0x199d:- Bytes at
DS:0x0c67are separators. SQ2 contains20 2c 2e 3f 21 28 29 3b 3a 5b 5d 7b 7d 00. - Bytes at
DS:0x0c75are ignored punctuation. SQ2 contains27 60 2d 22 00. - It collapses separator runs to single spaces, removes ignored punctuation, trims a trailing space, and zero-terminates the normalized buffer.
- Bytes at
- Documented dictionary lookup helper
0x1a6band theWORDS.TOKformat:- Startup loads
WORDS.TOKinto memory and stores the base pointer at[0x0ca5]. - The file begins with 26 big-endian offsets for lowercase initial letters.
The local SQ2 file has a zero offset for
x. - Entries are prefix-compressed as
u8 prefix_len, encoded suffix bytes with the final byte marked by bit 7, and a big-endian 16-bit word ID. - Decoding each suffix byte with
(byte & 0x7f) ^ 0x7fyields the lowercase character. - Local inspection found 1,099 entries. Sample decoded IDs include
look -> 0x0002,get -> 0x0005, andanyword -> 0x0001.
- Startup loads
- Refined parsed-input behavior:
- Recognized nonzero dictionary IDs are appended to
0x0c7b. - ID zero words are ignored, including the special single-letter
aandipaths in helper0x1a6b. - An unrecognized token stores its pointer in
0x0c8f, records its one-based position in[0x0012]and[0x0ca3], and stops parsing. - Condition
0x0e(input_word_sequence) consumes the parsed IDs from0x0c7band uses dictionary ID0x0001as a wildcard word.
- Recognized nonzero dictionary IDs are appended to
2026-07-01: raw input event queue and condition 0x0d
Additional commands run from /Users/peter/ai/agi/reverse:
rg -n "0x0d|input_or_event_check|0x09be|0x459e|0x001c|0x45d7|0x382e|0x37f7|event|input" docs/src tools- Focused
ndisasmdisassembly around main executable image offsets0x09a0,0x43f0,0x4500,0x4660,0x5a40,0x7f70, and0x3200, using the decrypted executable header skip andsedto limit output. - Full-executable
ndisasmcall-site search for calls to0x44a9,0x44f9,0x459e,0x4529,0x4566,0x45d7,0x45f0,0x4618,0x467f, and0x466f. - A focused
ndisasmread around0x6100was rejected because it used the wrong file skip. Follow-up reads around image offsets0x5f80,0x8e80, and0x93d0used the correctedimage_offset + 0x200skip. xxd -s 0x16b0 -l 0x50 -g 2 SQ2/AGIDATA.OVLxxd -s 0x16d0 -l 0x50 -g 2 SQ2/AGIDATA.OVLxxd -s 0x11ba -l 0x70 -g 2 SQ2/AGIDATA.OVL- Focused
ndisasmdisassembly around image offset0x0c44, showing action handler0x73. A follow-up read around0x0e7eused the wrong skip and was rejected; no conclusions from that shifted read were documented. - Corrected focused
ndisasmdisassembly around image offset0x0e7e. python3 -B tools/disassemble_logic.py --limit 142 | rg -n "\b8f\b|action_8f|logic="python3 -B tools/disassemble_logic.py --limit 142 | sed -n '/action_8f/,+4p'
Documented result:
- Renamed condition opcode
0x0d(raw_key_event_available) intools/disassemble_logic.pytoraw_key_event_available. - Renamed action opcode
0x79(map_key_event) intools/disassemble_logic.pytomap_key_event. - Expanded
docs/src/logic_bytecode.mdwith the raw event queue:- Event records are 4 bytes: type word at
+0, value word at+2. - Queue storage is
0x11ba..0x1209. - Word
[0x120a]is the write pointer, and word[0x120c]is the read pointer. - Helper
0x44a9(type, value)enqueues one record unless the queue is full. - Helper
0x44f9()dequeues one record or returns zero when empty.
- Event records are 4 bytes: type word at
- Documented condition handler
0x09be:- It first checks byte
[0x001c]. - If empty, it calls helper
0x459e. - Helper
0x459edequeues events, normalizes some key values through0x4634, returns the event value for type-1 records, returns zero for no event, and returns0xfffffor non-type-1 records. - Handler
0x09beloops past0xffff, stores a nonzero low byte in[0x001c], and returns true.
- It first checks byte
- Documented keyboard helper
0x5a89as the BIOSint 16hpolling path:- It returns zero when no key is waiting.
- It returns the low ASCII byte when the key has nonzero ASCII.
- It preserves the BIOS scan-code word when ASCII is zero.
- Documented helper
0x467fas the BIOS-key drain into the event queue:- Key words found in table
DS:0x16b3are enqueued as type 2 with a mapped direction-like value. - Other key words are enqueued as type 1 with the raw value.
- The local
0x16b3table maps key words0x4800,0x4900,0x4d00,0x5100,0x5000,0x4f00,0x4b00, and0x4700to values1..8.
- Key words found in table
- Documented helper
0x4566(event_record):- For type-1 events, it scans script-populated four-byte slots rooted at
0x0145. - On a match between event value and slot word
+0, it changes the event type to 3 and replaces the event value with slot word+2. - Action
0x79(map_key_event) appends those mapping slots.
- For type-1 events, it scans script-populated four-byte slots rooted at
- Documented display-mode-specific helper
0x46e8(event_record):- When
[0x112e] == 2, it scans tableDS:0x16d7. - Matching type-1 values are changed to type 2 with mapped values.
- The local table maps ASCII digit key words
8,9,6,3,2,1,4,7to values1..8.
- When
- Named action opcode
0x73(prompt_string_to_slot) asprompt_string_to_slot:- It reads fixed string slot
arg0, message numberarg1, placement-like bytesarg2andarg3, and max-length bytearg4. - It clears the destination slot
0x020d + arg0 * 0x28. - It displays the resolved current-logic message, optionally after calling
0x2b0d(arg2, arg3)whenarg2 < 0x19. - It accepts edited text through helper
0x0da9, usingmin(arg4 + 1, 0x28)as the accepted length.
- It reads fixed string slot
- Recorded a tentative observation for action opcode
0x8f(action_8f) without assigning a name:- Handler
0x0e7ereads one message-number operand. - It resolves that current-logic message through
0x21f0. - It calls
0x4de8(destination=0x0002, source=message, count=7). - It then calls helper
0x5b49. - The one local occurrence is in logic 140 before action
0x6f(set_input_line_config) and string setup actions; the role remains open.
- Handler
Follow-up on action 0x6f (set_input_line_config), action 0x8f (action_8f), and DOS path helpers
Additional commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -n '1,220p' AGENTS.mdrg -n "0x6f|0x8f|action_6f|action_8f|Text-window|input-line|DOS file|file helper" docs/src tools/disassemble_logic.py- Corrected focused
ndisasmdisassemblies around image offsets0x0e7e,0x78f0, and0x5b49, using the decrypted executable header skip andsedto limit the displayed output. xxd -s 0x5d6c -l 0x10 -g 1 build/cleanroom/AGI.decrypted.exexxd -s 0x1320 -l 0x60 -g 1 SQ2/AGIDATA.OVLpython3 -B tools/disassemble_logic.py --limit 141 | sed -n '/action_8f/,+8p'rg -n "0x5dd|0x05dd|0x05d5|0x05db|0x1379|0x5df|0x05df" docs/src
Documented result:
- Named action opcode
0x6f(set_input_line_config) intools/disassemble_logic.pyasset_input_line_config. - Documented handler
0x78f0:- It stores
arg0in[0x05dd],arg0 + 0x15in[0x05df],arg1in[0x05d5], andarg2in[0x05db]. - It computes
[0x1379]fromarg0, normally asarg0 << 3. - In display mode
[0x1130] == 2,[0x1379]isarg0 * 6forarg0 <= 1and is clamped to 6 for larger values. - Nearby redraw helpers use these globals for input-line/status text areas, so the final user-facing meaning remains provisional.
- It stores
- Refined the action
0x8f(action_8f) observation:- Handler
0x0e7ecopies the resolved message into absolute buffer0x0002and calls0x5b49. - Helper
0x5b49compares bytes at0x0002against the embeddedSQ2\0string at image offset0x5b6c. - On the first mismatch it calls helper
0x02ae, already observed in restart/exit-like paths. - This looks like a game-signature/configuration guard, but the exact runtime role remains open until dynamically traced.
- Handler
- Expanded
docs/src/agi_executable.mdwith the DOS file wrapper cluster from image offsets0x5cad..0x5e73, the shared pre-call helper0x5e8d, and the savegame/path helpers around0x5b73and0x5bdd.
Follow-up on relative object positioning and state-file actions
Additional commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -n '1,220p' AGENTS.mdsed -n '1,260p' docs/src/graphics_object_pipeline.mdsed -n '500,580p' docs/src/logic_bytecode.mdpython3 -B tools/disassemble_logic.py --stats | rg " action_|^28 |^7c |^7d |^7e |^80 |^87 |^88 |^89 |^8a |^8b |^8c |^8d |^96 |^a9 |^9a |^6c "- Correct focused
ndisasmdisassemblies around image offsets0x7ce7,0x3726,0x3753,0x38b4,0x2472,0x2512,0x2753,0x28c6,0x26b0,0x31d8, and0x1f2b, using the decrypted executable header skip ofimage_offset + 0x200. - Two preliminary
ndisasmprobes around image offsets0x3726and0x0257were rejected because the file skip was wrong; no conclusions from those shifted outputs were used. python3 -B tools/disassemble_logic.py --limit 141 | rg -n "action (28|7c|7d|7e|80|87|88|89|8a|8b|8c|8d|96|a9|9a|6c)" -C 5xxd -s 0x0d20 -l 0xe0 -g 1 SQ2/AGIDATA.OVLxxd -s 0x1c60 -l 0x50 -g 1 SQ2/AGIDATA.OVLxxd -s 0x0a90 -l 0x70 -g 1 SQ2/AGIDATA.OVLsed -n '170,230p' tools/disassemble_logic.pysed -n '440,545p' docs/src/logic_bytecode.mdsed -n '604,650p' docs/src/logic_bytecode.mdsed -n '45,125p' docs/src/agi_executable.mdsed -n '200,235p' docs/src/graphics_object_pipeline.md- One
rgprobe for a markdown backtick pattern indocs/src/graphics_object_pipeline.mdfailed due shell quoting; it produced no evidence and was replaced by thesedread above.
Documented result:
- Named action opcode
0x28(add_object_pos_from_vars) asadd_object_pos_from_vars.- Handler
0x7ce7reads object indexarg0. - It reads signed deltas from byte variables named by
arg1andarg2. - It adds them to object fields
[+0x03]and[+0x05], clamping underflow at zero. - It sets object flag bit
0x0400and calls placement helper0x593a.
- Handler
- Named action opcode
0x6c(set_input_prompt_char) asset_input_prompt_char.- Handler
0x38b4resolves messagearg0and stores its first byte in[0x05d7]. - Input redraw helpers
0x37f7,0x382e, and0x38d7test[0x05d7]while drawing or erasing the prompt/input marker.
- Handler
- Named action opcodes
0x7d(save_game_state) and0x7e(restore_game_state) assave_game_stateandrestore_game_state.- Save handler
0x2753creates file0x1c8c, writes a 31-byte description/header from0x1c6c, then writes several length-prefixed blocks through helper0x28c6. - Restore handler
0x2512opens file0x1c8c, seeks to offset0x1f, then reads matching length-prefixed blocks through helper0x26b0. - Local strings around
0x0d34,0x0d73,0x0d87,0x0db6, and0x0e46identify the restore/save confirmation and error paths.
- Save handler
Runtime model synthesis and string-table action follow-up
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,220p' docs/src/logic_bytecode.mdsed -n '1,260p' docs/src/graphics_object_pipeline.mdsed -n '1,260p' docs/src/logic_resources.mdndisasm -b 16 -o 0x7350 -e 0x7550 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x0c30 -e 0x0e30 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x1940 -e 0x1b40 build/cleanroom/AGI.decrypted.exe- Local Python dump of
AGIDATA.OVLaction-table entries for opcodes0x70..0x78. xxd -g 1 -s 0xc8f -l 192 SQ2/AGIDATA.OVL- Local Python dump of words at
AGIDATA.OVLoffset0x0c8f.
Documented result:
- Added
docs/src/runtime_model.mdand linked it from the mdBook summary and overview. This page groups the lower-level handler notes into implementation-facing runtime types:- byte variables rooted at
DS:0x0009; - packed flags rooted at
DS:0x0109; - fixed string slots rooted at
DS:0x020d; - parsed-word buffers consumed by condition
0x0e(input_word_sequence); - 10-byte logic cache/activation records linked from
[0x0977]; - resource cache handles for logic, view-like, picture-like, and sound-like payloads;
- 43-byte object records and their operation families;
- the graphics/update pipeline phases needed by a replacement implementation.
- byte variables rooted at
- Decoded action opcode
0x74(set_string_slot_from_table) from handler0x0d70:- It computes destination string slot
0x020d + arg0 * 0x28. - It reads a word pointer from
DS:0x0c8f + arg1 * 2. - It copies up to
0x28bytes from that pointer into the slot through helper0x4de8. - The sampled static SQ2
AGIDATA.OVLtable at0x0c8fis zero-filled, and this opcode was not encountered in the current local SQ2 logic scan, so the label remains provisional.
- It computes destination string slot
- Added the
0x74label totools/disassemble_logic.pyand documented the handler indocs/src/logic_bytecode.md.
Inventory selector, restart prompt, and text-window cleanup actions
Commands run from /Users/peter/ai/agi/reverse:
git status --shortpython3 -B tools/disassemble_logic.py --statssed -n '1,220p' docs/src/logic_bytecode.mdsed -n '1,220p' docs/src/runtime_model.mdrg -n "action (1d|7c|80|87|88|89|8a|8b|8c|8d|96|9a|a9)" -C 4overpython3 -B tools/disassemble_logic.py --limit 141- Local Python dump of action-table entries for opcodes
0x1d,0x7c,0x80,0x87..0x8d,0x96,0x9a, and0xa9. - Initial orientation
ndisasmprobes around image offsets0x1f00,0x7300,0x7700, and0x8d00; these were used only to find nearby functions. Final conclusions below were rechecked with the correct executable header skip. - Correct focused
ndisasmdisassemblies using-e image_offset + 0x200around image offsets0x1f2b,0x2472,0x2b78,0x31d8,0x33bf,0x5546,0x731b,0x7753, and0x8d3d. - Local Python string dump of
SQ2/AGIDATA.OVLoffsets0x0aab,0x0adb,0x0f1e,0x0f26,0x0f38,0x0f5d, and menu diagnostic strings around0x1ccc..0x1d04. rg -n "0x7c|0x80|0x8d|0x96|0x9a|0xa9|0x1d|draw_box|window|text_attr|1755|1d12|1d0a" docs/src tools/disassemble_logic.pysed -n '160,280p' tools/disassemble_logic.pysed -n '520,600p' docs/src/logic_bytecode.mdsed -n '700,940p' docs/src/logic_bytecode.mdsed -n '100,190p' docs/src/runtime_model.mdsed -n '1300,1465p' docs/src/clean_room_executable_notes.md
Documented result:
- Named action opcode
0x7c(show_inventory_selection).- Handler
0x31d8clears the input prompt, saves/restores text attributes, enables the alternate text-attribute mode, and calls helper0x3203. - Helper
0x3203scans 3-byte entries from[0x0971]to[0x0973], keeping only entries whose byte[entry+0x02] == 0xff. - Each kept entry becomes an 8-byte temporary row containing the original
entry index, a name pointer computed as
[0x0971] + word[entry+0x00], and row/column display coordinates. - The strings at
0x0f26,0x0f1e,0x0f38, and0x0f5didentify the UI as the carried-object list, with an interactive selection mode when flag 13 is set. - Enter stores the selected entry index in byte variable
[0x22]; Escape stores0xff.
- Handler
- Named action opcode
0x80(confirm_restart_game).- Handler
0x2472stops sound, clears input, and uses flag 16 to decide whether to skip a confirmation dialog. - The confirmation string at
0x0adbasks whether to restart the game. - On confirmation it resets heap/update state, sets flag 6, preserves flag 9,
clears words
[0x0129]and[0x012b], optionally reloads logic[0x1d12], calls menu/list refresh helper0x930e, redraws the prompt, and returns zero to the dispatcher.
- Handler
- Named action opcode
0x9a(clear_text_rect_bounds).- Handler
0x7753reads five immediates and calls helper0x2bc4. - Helper
0x2bc4is the full-bounds form of the text rectangle clear helper: it saves the cursor, passes top/left/bottom/right and attribute to BIOSint 10hscroll/clear-window serviceAH=0x06, then restores the cursor. - Existing action
0x69is a narrower wrapper that clears full-width rows through helper0x2b78, which in turn calls0x2bc4.
- Handler
- Named action opcode
0xa9(close_text_window_state).- Handler
0x1f2btests word[0x0d1d]; if nonzero, it restores a saved display rectangle by calling helper0x560c([0x0d23], [0x0d25]). - It then clears words
[0x0d0f]and[0x0d1d]. - The same routine is used both as an action handler and as an internal cleanup helper in picture/message/save paths.
- Handler
- Named action opcode
0x8d(show_interpreter_version).- Handler
0x733cdisplays the static string at0x0aab, which identifies the interpreter and version in this executable.
- Handler
- Decoded action opcode
0x96without assigning a stable user-level label yet.- Handler
0x8d3dreads three immediates, storing them in words[0x1d12],[0x1d08], and[0x1d0a]. - The third value is clamped upward to at least 2.
- The first value
[0x1d12]is later used by the restart and room-switch paths as an optional logic resource to load. The other two globals feed the menu/list rendering cluster around0x8e0b, so this remains a configured UI/session state action until that cluster is fully decoded.
- Handler
- Reconfirmed action opcode
0x1das unresolved.- Handler
0x731bsets word[0x1755] = 1, calls full refresh helper0x5546, waits for an event through0x4618, refreshes again, then clears[0x1755]. - Helper
0x5546has a special branch when bit 0 of[0x1755]is set that rotates every byte of the logical graphics buffer before copying it to the display path. The visual/user-level purpose still needs a dynamic trace or screenshot before naming.
- Handler
Follow-up on diagnostic, pause, input-line, display-toggle, and joystick actions
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py --statsndisasm -b 16 -o 0x0250 -e 0x0450 build/cleanroom/AGI.decrypted.exeas an over-broad first probe for the low-offset handler cluster. The useful bytes were later narrowed by direct handler inspection; the extra trailing output was ignored.ndisasm -b 16 -o 0x14a0 -e 0x16a0 build/cleanroom/AGI.decrypted.exeas an over-broad first probe around the diagnostic handler; conclusions were taken only from the aligned handler at0x14bd.ndisasm -b 16 -o 0x3700 -e 0x3900 build/cleanroom/AGI.decrypted.exeas an over-broad first probe around input-line refresh helpers; conclusions were taken from aligned handlers0x3726and0x3753.- Local Python string dump of
SQ2/AGIDATA.OVLoffsets0x0a19,0x0c0d,0x0fce, and0x1e2e. ndisasm -b 16 -o 0x794c -e 0x7b4c build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'ndisasm -b 16 -o 0x613c -e 0x633c build/cleanroom/AGI.decrypted.exe | sed -n '1,140p'python3 -B tools/disassemble_logic.py --limit 141 | rg -n "action (87|88|89|8a|8b|8c|1d|96)" -C 5- Local Python hex/text dump of
SQ2/AGIDATA.OVLoffsets0x1549,0x15c1,0x15c3,0x1531, and0x153d. rg -n "0x87|0x88|0x89|0x8a|0x8b|0x8c|Miscellaneous|Interpreter/session|Text-window" docs/src/logic_bytecode.mdwc -l docs/src/logic_bytecode.md docs/src/clean_room_executable_notes.md docs/src/runtime_model.mdsed -n '780,900p' docs/src/logic_bytecode.mdsed -n '220,260p' tools/disassemble_logic.py
Documented result:
- Named action opcode
0x87(show_heap_status).- Handler
0x14bdformats a 100-byte stack message with helper0x2374and displays it through0x1ce8. - The format string at
0x0a19readsheapsize: %u,now: %u max: %u,rm.0, etc.: %u, andmax script: %d. - The numeric values are computed from heap/script globals
[0x0a55],[0x0a57],[0x0a59],[0x0a5b],[0x0a5f], and[0x170f].
- Handler
- Named action opcode
0x88(pause_game_message).- Handler
0x0257sets[0x0615] = 1, calls helper0x4482, stops sound, displays the fixed pause string at0x0c0d, then clears[0x0615].
- Handler
- Named action opcode
0x89(refresh_input_line).- Handler
0x3753runs only when input-line enabled word[0x05d3]is nonzero. - In display mode
[0x1130] == 2with[0x0d0f] == 0, it displays the string at0x1e2e(ENTER COMMAND) through the alternate display helpers and sends the current input character byte[0x001c]through helper0x3652. - In the other path, helper
0x37a5appends bytes from the buffer/string at0x0fceinto visible input buffer0x0fa4until[0x0ff8]reaches that source string length.
- Handler
- Named action opcode
0x8a(erase_input_line).- Handler
0x3726repeatedly calls helper0x3652(0x08)while input length word[0x0ff8]remains nonzero, except that display mode 2 with[0x0d0f] == 0skips the erase loop.
- Handler
- Named action opcode
0x8b(calibrate_joystick).- Handler
0x613cinitializes joystick/calibration globals, displays the string at0x1549(Please center your joystick...) when joystick state is available, waits for Enter or Escape, then computes centered bounds around[0x15c1]and[0x15c3]into[0x15c9],[0x15cd],[0x15cb], and[0x15cf]. - It then loops helper
0x6425while calibration records at0x1531or0x153dare active, and finishes with helper0x4482.
- Handler
- Named action opcode
0x8c(toggle_display_mode_bit).- Handler
0x794crequires[0x112e] == 0, byte variable 0 nonzero, and display mode word[0x1130]not equal to 2 or 3. - It calls
0x1364, toggles bit 0 of[0x1130], and rebuilds display state through helpers0x2b28,0x5528,0x2b4f, and0x681c.
- Handler
- Added implementation-facing notes to
docs/src/runtime_model.mdgrouping these as UI, diagnostics, and device-state services around the VM.
Priority-screen action and trace-window configuration
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py --statspython3 -B tools/disassemble_logic.py --limit 141 | rg -n "action (1d|87|88|89|8a|8b|8c|96|8f)" -C 6ndisasm -b 16 -o 0x14a0 -e 0x16a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,130p'ndisasm -b 16 -o 0x0250 -e 0x0450 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x36f0 -e 0x38f0 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'ndisasm -b 16 -o 0x6100 -e 0x6300 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x7930 -e 0x7b30 build/cleanroom/AGI.decrypted.exe | sed -n '1,130p'xxd -g 1 -s 0x0a10 -l 0x80 SQ2/AGIDATA.OVLxxd -g 1 -s 0x0c00 -l 0x40 SQ2/AGIDATA.OVLxxd -g 1 -s 0x0fbc -l 0x30 SQ2/AGIDATA.OVL- An attempted
python3 -B tools/inspect_words.py 40 62 63 44 55 102 89 36 146command failed becauseinspect_words.pyaccepts--id, not positional ids. It produced no evidence and was replaced by the local Python import below. - Local Python use of
tools.inspect_words.decode_entriesoverSQ2/WORDS.TOKfor word ids0x0024,0x0028,0x002c,0x0037,0x003e,0x003f,0x0059,0x0066, and0x0092. - Full static
ndisasmwithrgfor references to globals[0x1d08],[0x1d0a],[0x1d10],[0x1d12],[0x1d14],[0x1d16],[0x1d18],[0x1d1a],[0x1d1c], and[0x1d1e]. ndisasm -b 16 -o 0x8c60 -e 0x8e60 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'ndisasm -b 16 -o 0x8e0b -e 0x900b build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x900b -e 0x920b build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'- Local Python action-table dump for opcodes
0x90..0x96. ndisasm -b 16 -o 0x02c0 -e 0x04c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'python3 -B tools/disassemble_logic.py 140 | sed -n '1,80p'- An attempted
rgcommand containing markdown backticks in the search pattern was misparsed by the shell and produced no evidence; the useful searches above were run with simpler patterns.
Documented result:
- Named action opcode
0x1d(show_priority_screen).- Handler
0x731bsets[0x1755] = 1, calls full-screen refresh helper0x5546, waits for an event through0x4618, calls0x5546again, then clears[0x1755]. - Helper
0x5546swaps the high and low nibbles of every logical graphics-buffer byte while[0x1755] & 1is set. - The only observed local phrase reaching this action is
show pri;WORDS.TOKmaps word id0x0028to “show” and word id0x003fto “pri”. - The replacement-level behavior is therefore a temporary priority/control inspection display that returns to the normal display after input.
- Handler
- Named action opcode
0x95(enable_action_trace_window) even though no local SQ2 logic path currently reaches it.- Handler
0x8c91returnsSI + 1when word[0x1d10]is already nonzero. - Otherwise it calls helper
0x8cae, which starts a trace display only if flag 10 is set. - Helper
0x8caesets[0x1d10] = 1, computes box bounds from input-line row[0x05dd], trace row offset[0x1d08], and trace height[0x1d0a], stores derived values in[0x1d14],[0x1d16],[0x1d18],[0x1d1a],[0x1d1c], and[0x1d1e], then draws the trace box with0x5590.
- Handler
- Named action opcode
0x96(configure_action_trace_window).- Handler
0x8d3dstores its three immediates in[0x1d12],[0x1d08], and[0x1d0a], clamping[0x1d0a]upward to at least 2. - The dispatcher at
0x02c3tests[0x1d10] == 1before each action dispatch and calls formatter helper0x8da3. - Formatter helper
0x8e0buses optional logic resource[0x1d12]for trace text, draws opcode/operand values into the trace box, and waits for input while trace mode is active. - Restart and room-switch paths also reload logic
[0x1d12]when nonzero, so a new implementation should treat it as part of VM trace/session configuration, not as ordinary game-state logic.
- Handler
Game-signature guard action
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py --statspython3 -B tools/disassemble_logic.py 140 | rg -n "action 8f|message_count|logic 140|set_input_line_config|set_string_slot_from_message" -C 4- Two initial
ndisasmprobes around image offsets0x0e70and0x5b40accidentally used the image offset as the file skip. Those shifted outputs were rejected and produced no conclusions. - Corrected focused disassemblies with the executable header skip included:
ndisasm -b 16 -o 0x0e70 -e 0x1070 build/cleanroom/AGI.decrypted.exe | sed -n '1,140p'ndisasm -b 16 -o 0x5b40 -e 0x5d40 build/cleanroom/AGI.decrypted.exe | sed -n '1,110p'
- Local Python read of logic 140’s payload/message table for orientation. That
raw dump did not decode the encrypted/compressed message text, but it did
confirm the local bytecode context around the single static
0x8fuse.
Documented result:
- Named action opcode
0x8f(verify_game_signature).- Handler
0x0e7ereads one immediate message number. - It resolves that message through
0x21f0, pushes maximum length 7, and copies the string to absolute buffer0x0002through helper0x4de8. - It then calls helper
0x5b49. - Helper
0x5b49compares bytes at0x0002against an embeddedSQ2\0string at image offset0x5b6c. - On the first mismatch it calls helper
0x02ae, the same helper seen in restart/exit-like paths. - The only observed local static use is in logic 140 immediately before
0x6f(set_input_line_config), consistent with a game-signature or game-configuration guard.
- Handler
Normalized string-slot equality condition
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py --statssed -n '120,230p' docs/src/logic_bytecode.mdrg -n "helper_0eac|0x0f|0eac|09db|condition" docs/src toolsndisasm -b 16 -o 0x09c0 -e 0x0bc0 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x0e80 -e 0x1080 build/cleanroom/AGI.decrypted.exexxd -g 1 -s 0x0b40 -l 0x40 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x4f90 -e 0x5190 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x18a0 -e 0x1aa0 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'sed -n '80,230p' tools/disassemble_logic.pyrg -n "0x094b|0x0c67|0xc67|0x0c75|0xc75|delimiter|punct|normalize|string slot|0x020d|0x20d" docs/src toolsxxd -g 1 -s 0x0e60 -l 0xa0 build/cleanroom/AGI.decrypted.exexxd -g 1 -s 0x0f40 -l 0x80 build/cleanroom/AGI.decrypted.exexxd -g 1 -s 0x1140 -l 0x100 build/cleanroom/AGI.decrypted.exesed -n '280,325p' docs/src/logic_bytecode.mdsed -n '1150,1185p' docs/src/clean_room_executable_notes.mdsed -n '30,65p' docs/src/runtime_model.mdrg -n "094b|0x94b|0x094b" -S .- Local byte-pattern probes over
build/cleanroom/AGI.decrypted.exe,SQ2/AGIDATA.OVL, and related files to map known delimiter tables back to their storage file. strings -a -t x build/cleanroom/AGI.decrypted.exe | rg "ENTER COMMAND|You are carrying|nothing|AGI|COMMAND|carrying|Press ENTER|Press ESC"xxd -g 1 -s 0x0940 -l 0x80 SQ2/AGIDATA.OVLxxd -g 1 -s 0x0c60 -l 0x30 SQ2/AGIDATA.OVL- Local byte reads of zero-terminated data at
SQ2/AGIDATA.OVLoffsets0x094b,0x0c67,0x0c75, and0x020d. sed -n '140,225p' docs/src/logic_bytecode.md
Documented result:
- Renamed condition opcode
0x0ffrom provisionalhelper_0eactostring_slots_equal_normalized. - The condition table entry dispatches to handler
0x09db, has two fixed operands, and has metadata byte0x00. - Handler
0x09dbreads two immediate byte operands, pushes them, and calls helper0x0eac. - Helper
0x0eacallocates two local buffers, calls helper0x0ef8for each operand, and then compares the normalized buffers byte-for-byte through their zero terminators. - Helper
0x0ef8computes a source string slot as0x020d + slot * 0x28, walks it until a zero byte, skips bytes found in the zero-terminated table atDS:0x094b, lowercases ASCII uppercase bytes through helper0x4fea, writes kept bytes to the destination buffer, and appends a zero terminator. DS:0x094bis data inSQ2/AGIDATA.OVL, not the same offset in the EXE body. The local SQ2 table contains20 09 2e 2c 3b 3a 27 21 2d 00, meaning space, tab,.,,,;,:,',!, and-are ignored for this comparison.- Updated
docs/src/runtime_model.mdto separate this direct normalized string comparison from the dictionary-backed parsed-word condition0x0e.
Object diagnostic action and field-name confirmation
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py --statsrg -n "provisional|unknown|needs|still|action_00|object_motion_or_state|refresh_object_helper|picture|draw|0x5546|0x5762|0x593a|0x57cf|0x3ae7|0x39f7|0x4a3b|0x4acf" docs/src toolspython3 -B tools/disassemble_logic.py --limit 142 | rg -n "action 85|display_object_state_summary_var|logic [0-9]+|message_count" -C 8ndisasm -b 16 -o 0x7280 -e 0x7480 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x1c00 -e 0x1e00 build/cleanroom/AGI.decrypted.exe | sed -n '1,200p'rg -n "0x85|display_object_state_summary_var|72b5|0x72b5|object state|summary" docs/src toolsxxd -g 1 -s 0x1700 -l 0x80 SQ2/AGIDATA.OVLpython3 -B tools/inspect_words.py --id 0x0031 --limit 20python3 -B tools/inspect_words.py --id 0x0017 --limit 20python3 -B tools/inspect_words.py --id 0x001a --limit 20python3 -B tools/inspect_words.py --id 0x002c --limit 20sed -n '1,120p' tools/disassemble_logic.pyrg -n "message|messages|decode|crypt|logic_payload|message_count|21f0" tools docs/src/logic_resources.md docs/src/clean_room_executable_notes.mdpython3 -B tools/disassemble_logic.py 99 | sed -n '1,140p'sed -n '92,118p' docs/src/logic_resources.mdsed -n '255,295p' docs/src/clean_room_executable_notes.md- Local Python decoding of logic 99 messages using the previously documented
logic-message format and XOR key at
SQ2/AGIDATA.OVL:0x08f1. sed -n '180,205p' docs/src/graphics_object_pipeline.mdsed -n '704,722p' docs/src/logic_bytecode.mdsed -n '632,646p' docs/src/clean_room_executable_notes.md- An attempted
rgcommand containing a markdown backtick in the search pattern was misparsed by the shell and produced no evidence. rg -n "display_object_state_summary_var|display_object_diagnostics_var|0x85|object #:|Object %d|stepsize" docs/src toolssed -n '130,165p' docs/src/runtime_model.md
Documented result:
- Renamed action opcode
0x85fromdisplay_object_state_summary_vartodisplay_object_diagnostics_var. - Handler
0x72b5reads one operand as a variable slot, then reads the object index fromvar[arg0]. - It multiplies the object index by
0x2b, adds the object array base[0x096b], and formats fields from that object:- object index from the variable value;
[object+0x03]asx;[object+0x05]asy;[object+0x1a]asxsize;[object+0x1c]asysize;[object+0x24]aspri;[object+0x1e]asstepsize.
- The format string at
DS:0x1713inSQ2/AGIDATA.OVLreads:
Object %d:
x: %d xsize: %d
y: %d ysize: %d
pri: %d
stepsize: %d
- The only local SQ2 use is in logic 99. The script accepts WORDS.TOK id
0x0031(object) or0x0017(sp), prompts with decoded message 7 (object #:), stores the number in variable 64, and then calls action0x85. - Logic 99 is a diagnostic command hub: nearby decoded messages include
new room:,x:,y:,object number:,var number:,var value:,flag number:, and flag status messages. - Updated the object runtime model and graphics/object pipeline notes to use this diagnostic template as additional evidence for the field meanings.
Targeted object movement actions
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x6cc0 -e 0x6ec0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x1620 -e 0x1820 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'rg -n "object_motion_or_state|0x51|0x52|0x1672|\\+0x27|\\+0x28|\\+0x29|\\+0x2a|boundary completion|motion/control" docs/src toolspython3 -B tools/disassemble_logic.py --stats | sed -n '/actions/,$p' | sed -n '1,80p'sed -n '430,455p' docs/src/graphics_object_pipeline.mdsed -n '780,812p' docs/src/clean_room_executable_notes.mdsed -n '136,160p' docs/src/runtime_model.mdxxd -g 2 -s 0x0a80 -l 0x40 SQ2/AGIDATA.OVLxxd -g 1 -s 0x0a80 -l 0x28 SQ2/AGIDATA.OVL- Local Python read of the nine little-endian direction table words at
SQ2/AGIDATA.OVL:0x0a85. python3 -B tools/disassemble_logic.py 1 2 3 4 5 6 7 8 9 10 | rg -n "action (51|52)" -C 3python3 -B tools/disassemble_logic.py --limit 141 | rg -n "action (51|52)" -C 2 | sed -n '1,160p'sed -n '40,58p' docs/src/logic_bytecode.mdsed -n '470,488p' docs/src/logic_bytecode.mdsed -n '686,701p' docs/src/logic_bytecode.mdrg -n "object_motion_or_state|object_motion_or_state_var|motion_parameters|Motion control|Targeted-motion|targeted-motion|0x1672" docs/src tools/disassemble_logic.pyrg -n "object_motion_or_state|object_motion_or_state_var|move_object_to|0x0a85|target above|completion flag" docs/src tools/disassemble_logic.pypython3 -B tools/disassemble_logic.py 1 | rg -n "move_object_to|action 5[12]" -C 2
Documented result:
- Renamed action opcode
0x51fromobject_motion_or_statetomove_object_to. - Renamed action opcode
0x52fromobject_motion_or_state_vartomove_object_to_var. - Handler
0x6ce4(0x51) reads:- object index;
- target X;
- target Y;
- optional step-size override, where zero means keep the current step size;
- completion flag.
- Handler
0x6d61(0x52) has the same contract, except target X, target Y, and step-size override are read from variables. - Both handlers set object byte
[+0x22] = 3, store target X/Y in[+0x27]/[+0x28], save old step size[+0x1e]into[+0x29], store the completion flag in[+0x2a], clear the completion flag, set object bit0x0010, and call helper0x1672. - Helper
0x1672calls0x16ed(current_x, current_y, target_x, target_y, step)and stores the returned direction-like byte in object byte[+0x21]. For object 0 it also mirrors that direction byte to global byte[0x000f]. - Helper
0x16edclassifies the target X and Y relative to the current position and step size, then indexes the nine-word table atDS:0x0a85. The local SQ2 table is:
target above: 8 1 2
target level: 7 0 3
target below: 6 5 4
left near right
- The zero center entry means an object already at, or within one step of, the target completes immediately.
- Completion helper
0x16b9restores step byte[+0x1e]from[+0x29], sets flag[+0x2a], clears object byte[+0x22], and for object 0 sets[0x0139] = 1and clears global direction byte[0x000f]. - Updated the bytecode spec, object/graphics pipeline, and runtime model with the higher-level targeted-movement contract.
Autonomous object motion modes 1 and 2
Commands run from /Users/peter/ai/agi/reverse:
sed -n '680,735p' docs/src/logic_bytecode.mdsed -n '180,205p' docs/src/graphics_object_pipeline.mdsed -n '520,550p' docs/src/graphics_object_pipeline.mdsed -n '140,165p' docs/src/runtime_model.mdsed -n '1038,1064p' docs/src/clean_room_executable_notes.mdsed -n '160,190p' tools/disassemble_logic.pyndisasm -b 16 -o 0x6df0 -e 0x6ec0 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x6df0 -e 0x6ff0 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x0b80 -e 0x0d80 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x3f30 -e 0x4130 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x0a80 -e 0x0c80 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x3f30 -e 0x4130 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x6df0 -e 0x6ff0 build/cleanroom/AGI.decrypted.exerg -n "0bb3|0x0bb3|3f5a|0x3f5a|16ed|0x16ed|\\+0x22 == 2|field_22_mode1|object_step_or_state_limited" docs/src toolsndisasm -b 16 -o 0x1660 -e 0x1860 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x09f0 -e 0x0bf0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x0b30 -e 0x0d30 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x1680 -e 0x1880 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'python3 -B tools/disassemble_logic.py --opcode 0x53python3 -B tools/disassemble_logic.py --opcode 0x54python3 -B tools/disassemble_logic.py --opcode 0x55python3 -B tools/disassemble_logic.py --statstail -n 80 docs/src/clean_room_executable_notes.mdrg -n "object_step_or_state_limited|set_object_field_22_mode1|clear_object_field_22\\)|approach_first_object_until_near|start_random_motion|stop_motion_mode|min\\(arg1" docs/src tools/disassemble_logic.py
Rejected or non-evidence probes:
- The first
ndisasmcommand around0x6df0used-e 0x6ec0. For this decrypted executable image, the file skip must include the0x200-byte MZ header, so the correct skip for image offset0x6df0is0x6ff0. The shifted command was treated as rejected evidence. - The three
python3 -B tools/disassemble_logic.py --opcode ...commands only produced argument-parser errors because the local disassembler has no--opcodeoption. They were not used as evidence. - The broad
ndisasmcommands withoutsedproduced excessive trailing disassembly. Only the leading ranges later rechecked with focusedsedcommands were used as evidence.
Documented result:
- Corrected action
0x53: handler0x6e02sets object byte[+0x22] = 2, reads operand 1, compares it with current step byte[+0x1e], and stores the larger value in[+0x27]. The earliermin(...)description was wrong. - Action
0x53stores operand 2 as completion flag byte[+0x28], clears that flag through0x74d0, initializes byte[+0x29] = 0xff, and sets object bit0x0010. - Helper
0x0b36, reached from mode byte+0x22 == 2, computes the first object entry’s center X asfirst[+0x03] + first[+0x1a] / 2and the current object’s center X asobject[+0x03] + object[+0x1a] / 2. - The same helper calls
0x16ed(object_center_x, object_y, first_object_center_x, first_object_y, object[+0x27]). If the returned direction is zero, it clears object bytes[+0x21]and[+0x22]and sets completion flag[+0x28]through0x74c6. - If mode 2 sees object bit
0x4000, it chooses a random nonzero direction through0x3fa3, stores it in[+0x21], and computes a delay in[+0x29]from the object/first-object separation and current step byte. While[+0x29]is nonzero, the helper counts it down by step byte[+0x1e]; when the delay reaches zero it writes the direct approach direction to[+0x21]. - Renamed opcode label
0x53toapproach_first_object_until_near. - Action
0x54handler0x6e68sets[0x0139] = 0when operating on the first object entry, then sets object byte[+0x22] = 1and object bit0x0010. - Helper
0x3f5a, reached from mode byte+0x22 == 1, decrements countdown byte[+0x27]. When the old countdown is zero, or object bit0x4000is set, it calls0x3fa3, stores the random direction0..8in[+0x21], mirrors that direction to global byte[0x000f]for the first object, and reseeds[+0x27]by repeatedly taking random% 0x33until the value is at least 6. - Renamed opcode label
0x54tostart_random_motion. - Action
0x55handler0x6ea1only clears object byte[+0x22]. It does not clear direction byte[+0x21]or update the first-object globals. - Renamed opcode label
0x55tostop_motion_mode. - Updated the bytecode spec, graphics/object pipeline, runtime model, and local disassembler labels with these higher-level motion-mode contracts.
Remaining action-table opcode pass
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' tools/disassemble_logic.pysed -n '260,430p' tools/disassemble_logic.pyrg -n "action_[0-9a-f]{2}|condition_[0-9a-f]{2}|unknown|provisional|thin|remaining|TODO|needs" docs/src toolspython3 -B tools/disassemble_logic.py --statsrg -n "061d|action table|condition table|dispatch table|TableEntry|load_table" docs/src tools/disassemble_logic.pysed -n '1,80p' docs/src/logic_bytecode.mdsed -n '240,330p' docs/src/clean_room_executable_notes.md- Python one-off dump of all unnamed action-table entries at
SQ2/AGIDATA.OVL:0x061dand condition-table bytes through opcode0x25. ndisasm -b 16 -o 0x4b00 -e 0x4d00 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x3ec0 -e 0x40c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x8270 -e 0x8470 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x4c00 -e 0x4e00 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x2700 -e 0x2900 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x7180 -e 0x7380 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x6020 -e 0x6220 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x5040 -e 0x5240 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'rg -n "0x0e72|0xe72|0x1530|1530|0x124a|124a|0x127a|127a|0x0143|0x143|0x05e1|0x5e1|0x1823|1823|0x1809|1809|0x1c8c|1c8c|0x5051|0x4b17|0x3ecd|0x828f|0x3ee9|0x4c15|0x2726|0x718b|0x719d|0x602f|0x4d10" docs/src toolsstrings -a -t x build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'xxd -g 1 -s 0xe60 -l 0x90 SQ2/AGIDATA.OVLxxd -g 1 -s 0x1800 -l 0x60 SQ2/AGIDATA.OVLndisasm -b 16 -o 0x02a0 -e 0x04a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'sed -n '90,140p' docs/src/agi_executable.mdsed -n '780,850p' docs/src/clean_room_executable_notes.mdsed -n '680,710p' docs/src/graphics_object_pipeline.mdsed -n '1360,1380p' docs/src/clean_room_executable_notes.mdsed -n '50,90p' docs/src/graphics_object_pipeline.mdsed -n '600,618p' docs/src/logic_bytecode.mdsed -n '440,470p' docs/src/logic_bytecode.mdsed -n '580,620p' docs/src/logic_bytecode.mdsed -n '138,170p' docs/src/logic_bytecode.mdsed -n '112,170p' docs/src/logic_bytecode.mdsed -n '600,630p' docs/src/logic_bytecode.mdsed -n '900,950p' docs/src/logic_bytecode.mdsed -n '960,1035p' docs/src/logic_bytecode.mdsed -n '930,990p' docs/src/logic_bytecode.mdsed -n '850,930p' docs/src/logic_bytecode.mdsed -n '45,75p' docs/src/graphics_object_pipeline.mdrg -n "action_[0-9a-f]{2}|object_step_or_state_limited|set_object_field_22_mode1|clear_object_field_22\\)|\\b0x1c\\b|\\b0x20\\b|\\b0x90\\b|\\b0x99\\b|\\b0x9b\\b|\\b0xaa\\b|\\b0xab\\b|\\b0xac\\b|\\b0xad\\b|\\b0xae\\b|\\b0xaf\\b" docs/src tools/disassemble_logic.py- Python one-off import of
tools/disassemble_logic.pywithsys.modulesregistration, followed by an unnamed-action audit.
Rejected or non-evidence probes:
- The first Python one-off import of
tools/disassemble_logic.pyomittedsys.modules[spec.name] = module. Python 3.14’sdataclassimplementation expected that registration and raised anAttributeError; this failed probe was not used as evidence.
Documented result:
- Dumped the full action table at
SQ2/AGIDATA.OVL:0x061d. Before this pass, unnamed action entries were0x00,0x1c,0x20,0x7f,0x90,0x99,0x9b, and0xaa..0xaf. - Added local labels for all remaining action-table entries:
0x00(end), a structural main-loop terminator.0x1c(overlay_picture_var), a variable-sourced picture path that selects a cached picture payload and enters picture decoder0x6440instead of0x6445.0x20(discard_view) and0x99(discard_view_var), which release or rewind cached view-like resources through helper0x3f0d.0x7f(noop),0x9b(noop_2), and0xaf(noop_1_table_count), no-op table entries with different observed pointer-advance behavior.0x90(append_message_to_log_file), which opens or createslogfile, appends room/input-line context, appends a resolved message, and closes the handle.0xaa(copy_save_description_to_string_slot), which copies up to0x1fbytes from buffer0x0e72into a logic string slot.0xab(save_event_buffer_count) and0xac(restore_event_buffer_count), which preserve and restore the pair-buffer count[0x0143]through[0x05e1].0xad(increment_global_1530), which only increments byte[0x1530].0xae(rebuild_priority_table_from_y), which rebuilds the 168-byte priority/control table at0x127afrom an immediate row/value.
- The follow-up unnamed-action audit reports
unnamed actions: []. - Dumped condition-table bytes through opcode
0x25. Entries0x00..0x12remain the valid-looking condition table; bytes after0x12decode as string/data bytes and then zero fill if forced through the same 4-byte shape. No local SQ2 condition list uses condition opcodes0x13..0x25, so they are documented as invalid/reserved for this build rather than as real predicates. - Updated the bytecode spec, graphics/object pipeline note, and local disassembler labels.
Symbolic label map setup
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,220p' AGENTS.mdrg -n "symbolic|label|labels|address|offset|0x293c|0x061d|AGI.decrypted|clean_room_executable_notes|SUMMARY" docs/src tools AGENTS.mdsed -n '1,220p' docs/src/SUMMARY.mdgit status --shortsed -n '1,120p' docs/src/README.mdsed -n '1,220p' docs/src/agi_executable.mdsed -n '1,120p' docs/src/runtime_model.mdtail -n 120 docs/src/clean_room_executable_notes.mdrg -n "0x293c|0x02c4|0x07e3|0x061d|0x08fd|0x07ab|0x08f1|0x21f0|0x119a|0x117d|0x12ae|0x4305|0x2e32|0x2e56|0x4a3b|0x4acf|0x4b3b|0x4bce|0x6445|0x6440|0x6475|0x5546|0x5528|0x5257|0x52f9" docs/srcrg -n "0x39f7|0x3ae7|0x3bb7|0x3c1b|0x3ccb|0x593a|0x5762|0x9db6|0x9db0|0x9db3|0x150a|0x16ed|0x1672|0x16b9|0x3fa3|0x3f5a|0x0b36|0x1ce8|0x1f2b|0x2374|0x1f54|0x2753|0x2512|0x28c6|0x26b0|0x85e5" docs/srcrg -n "0x096b|0x096d|0x0971|0x0977|0x0981|0x11b2|0x1377|0x1130|0x112e|0x136f|0x127a|0x0a85|0x1707|0x1709|0x0143|0x0009|0x0109|0x020d|0x0c7b|0x0ca3|0x094b" docs/srcsed -n '18,34p' docs/src/resource_files.mdsed -n '60,72p' docs/src/resource_files.mdrg -n "11b2|11b4|11b6|11b8|11ba" docs/src/clean_room_executable_notes.md docs/src/resource_files.md docs/src/symbolic_labels.mdrg -n 'Symbolic label map setup|symbolic_labels|Maintain `docs/src/symbolic_labels.md`|Symbolic Labels' AGENTS.md docs/src/SUMMARY.md docs/src/README.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md
Rejected or non-evidence probes:
- A final
rgsanity command used double quotes around a pattern containing backticks. The shell attempted command substitution ofdocs/src/symbolic_labels.mdand printed a permission-denied diagnostic. The same search was rerun with single quotes and the failed command was not used as evidence.
Documented result:
- Added
docs/src/symbolic_labels.mdas the cross-version label map. The map separates stable project names from SQ2-specific image, overlay, and data offsets. - Seeded the first map with labels already supported by the existing evidence trail: logic interpreter dispatch, message handling, resource loading, DOS wrappers, picture/display helpers, object/view/motion helpers, save/text helpers, and key runtime globals.
- Rechecked the directory pointer order against the resource-file chapter and
recorded view directory
[0x11b4], picture directory[0x11b6], and sound directory[0x11b8]. - Updated the mdBook summary and overview so the label map is part of the rendered documentation.
- Updated
AGENTS.mdto require future passes to update the symbolic label map when assigning or revising routine/global/table names.
Picture and view decode/draw pass
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,240p' AGENTS.mdsed -n '1,260p' docs/src/graphics_object_pipeline.mdsed -n '260,760p' docs/src/graphics_object_pipeline.mdsed -n '1,220p' docs/src/symbolic_labels.mdndisasm -b 16 -o 0x6440 -e 0x6640 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x5200 -e 0x5400 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x5680 -e 0x5880 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVLxxd -g 2 -s 0x15d6 -l 0x20 SQ2/AGIDATA.OVLndisasm -b 16 -o 0x6475 -e 0x6675 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'ndisasm -b 16 -o 0x6600 -e 0x6800 build/cleanroom/AGI.decrypted.exe | sed -n '1,210p'ndisasm -b 16 -o 0x52f9 -e 0x54f9 build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'xxd -g 2 -s 0x15f8 -l 0x60 SQ2/AGIDATA.OVLxxd -g 2 -s 0x1618 -l 0x50 SQ2/AGIDATA.OVLndisasm -b 16 -o 0x526f -e 0x546f build/cleanroom/AGI.decrypted.exe | sed -n '1,80p'ndisasm -b 16 -o 0x533b -e 0x553b build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'sed -n '1,260p' tools/inspect_view.pypython3 -B tools/inspect_view.py 11ndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVL | sed -n '1,180p'ndisasm -b 16 -o 0x587d -e 0x5a7d build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'- Python one-off scan of all local view payloads through
tools/inspect_view.pyto count frame control-byte values and nonzero preview string offsets. - Python one-off scan to find the first local frame with control bit
0x80; the first match was view 0, group 0, frame 0.
Rejected or non-evidence probes:
- The broad
ndisasmreads around0x6440,0x5200, and0x5680produced excessive trailing disassembly because no output filter was applied. They were useful for orientation only; the focusedsed-limited reruns above are the cited evidence for the documented details.
Documented result:
- Expanded the picture decoder notes from a handler sketch into opcode-level
semantics for command bytes
0xf0..0xfa, grounded in the local dispatch table atSQ2/AGIDATA.OVL:0x15d6. - Identified
0xf0/0xf1as low-nibble draw enable/disable commands and0xf2/0xf3as high-nibble control draw enable/disable commands. - Identified the coordinate reader contract:
0x66c1reads/clamps X to0x9f,0x66d4reads/clamps Y to0xa7, and bytes above0xefterminate the current drawing command for the main scanner. - Split the path-drawing families:
0xf4starts with a vertical segment and then alternates horizontal/vertical corners.0xf5starts with a horizontal segment and then alternates vertical/horizontal corners.0xf6draws absolute point-to-point lines through helper0x66e1.0xf7draws relative vector steps from packed delta bytes.
- Documented
0xf8conservatively as a seed-fill command through helper0x533b; its stack-state names remain open, but its seed/expand/write shape is stable. - Documented pattern command
0xf9and patterned draw command0xfa, including pattern pointer tableDS:0x1619and mask data rooted atDS:0x15f9. - Expanded the view frame model from the IBM object overlay:
- frame byte
+0x00is width; - frame byte
+0x01is height/row count; - frame byte
+0x02low nibble is the transparent color/run code; - row data begins at
+0x03; - zero ends a row;
- nonzero bytes encode high-nibble color and low-nibble run length.
- frame byte
- Documented object drawing’s priority/control comparison: the overlay writes
object priority/control from object byte
+0x24into the destination high nibble, skips writes behind higher existing control values, and may scan downward in the same column to find a comparison value. - Documented helper
0x587das the bit-0x80frame rewrite path. It compares frame control bits0x70with object byte+0x0a, updates those bits, and rebuilds the row-encoded frame stream in place when they differ. - Added symbolic labels for the new picture command handlers, line/fill helpers, pattern data, picture draw-state globals, and the view-frame rewrite helper.
2026-07-03: source-first lifecycle pass for room, menu, save, and sound
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' docs/src/symbolic_labels.mdrg -n "room\\.switch|switch_state|main_cycle|menu_direction|sound|save|restore|selection|0x1792|0x0150|0x44a9|0x16b3" docs/src tools testspython3 -B tools/disassemble_logic.py 0ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x0150 -e 0x0350 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x4529 -e 0x4729 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x93d1 -e 0x95d1 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x2470 -e 0x2670 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x2700 -e 0x2900 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x51d0 -e 0x53d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x85e0 -e 0x87e0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x10d0 -e 0x12d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x1485 -e 0x1685 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x7060 -e 0x7260 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x681c -e 0x6a1c build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x4480 -e 0x4680 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'xxd -g 2 -s 0x16b3 -l 0x30 SQ2/AGIDATA.OVLxxd -g 2 -s 0x16d7 -l 0x30 SQ2/AGIDATA.OVL
Rejected or corrected observations:
- A first
xxdread of offset0x16b3usedbuild/cleanroom/AGI.decrypted.exe. That offset belongs to AGIDATA runtime data, so the dump was not used. The command was rerun againstSQ2/AGIDATA.OVL, confirming the movement table bytes.
Documented result:
- Refined
code.room.switch_state(0x1792) from disassembly. The helper stops sound, resets heap/update-list state, clears parser/input state, initializes and enables resource-event recording, resets all object records, resets room caches through0x10d0, sets object-boundary word[0x0139], resets horizon-like word[0x012d]to0x24, updates current/previous room byte variables, loads the destination logic, reloads trace logic[0x1d12]when configured, consumes byte variable 2 as the entry-boundary selector, sets flag 5, refreshes display/status/input state, and returns zero. The exact cache behavior was refined later:0x10d0preserves the first logic cache record while clearing view, sound, and picture cache roots. - Re-read
code.engine.main_cycle(0x0150) to explain the zero return. Whencode.logic.call_logic(0)returns zero, the main cycle clears temporary boundary bytes and calls logic 0 again immediately. This supports the model that room-switch bytecode intentionally aborts the current script stream so logic 0 can re-enter and later dispatch the current room withcall_logic_var(v0). - Mapped input/event queue helpers around
0x44a9,0x44f9,0x4529,0x467f,0x46b6, and0x46e8. The raw event queue is a 20-record circular queue rooted atDS:0x11ba, with write pointer0x120aand read pointer0x120c. - Confirmed
data.input.menu_direction_event_mapat AGIDATA0x16b3maps raw BIOS arrow/keypad words0x4800,0x4900,0x4d00,0x5100,0x5000,0x4f00,0x4b00, and0x4700to movement codes1..8. The adjacent display-adapter remap table at0x16d7maps numeric keypad bytes0x38,0x39,0x36,0x33,0x32,0x31,0x34, and0x37to movement codes1..8. - Refined
code.menu.interact(0x93d1). Event type 1 handles Enter/Escape: Enter enqueues type-3 item ids only for enabled items, while Escape exits without a selection. Event type 2 dispatches movement codes through the navigation table and persists current heading/item globals[0x1d2e]and[0x1d30]after each non-exit movement. - Refined save/restore from
0x2472,0x2512,0x2753, and0x85e5. The shared selector handles modal text/path UI and returns zero for cancel. Save writes a 31-byte header followed by length-prefixed engine, object, inventory, resource-event, and logic/cache blocks. Restore reads the same block families and callscode.restore.replay_resource_events(0x681c) to rebuild loaded resource/display/object state before menu refresh. - Refined sound source notes. Action
0x63stops any prior active sound, stores and clears the completion flag, looks up an already loaded sound record through0x50d8, and calls driver start helper0x7f96. Action0x64calls0x5234; that helper only sets the configured completion flag when active-state word[0x1258]is nonzero, then clears active state and calls driver stop helper0x80af. - Added symbolic labels for the newly mapped room, heap, input/event, menu, restore, save, sound, and event-queue roles.
Initial graphics compatibility test scaffold
Commands run from /Users/peter/ai/agi/reverse:
git status --shortfind . -maxdepth 3 -type f | sort | sed -n '1,220p'sed -n '1,260p' tools/disassemble_logic.pysed -n '1,260p' tools/inspect_view.pysed -n '1,200p' .gitignoresed -n '1,200p' docs/.gitignorerg -n "test|suite|compat|QEMU|picture decoder|View payload|0xf8|seed_fill" docs/src tools AGENTS.mdfind . -maxdepth 2 -type d | sortpython3 -B -m unittest tests.test_graphics_rendering- Python one-off import of
tools.agi_graphicsto locate presentPICDIRentries; the first present picture entry was picture 1. - Python one-off import of
tools.agi_graphicsto calculate current renderer hashes for picture 1, view 0 group 0 frame 0, and view 11 group 0 frame 0. python3 -B tools/render_picture.py 1 --output build/rendered/picture_001_visual.ppmpython3 -B tools/render_picture.py 1 --channel control --output build/rendered/picture_001_control.ppmpython3 -B tools/render_view.py 0 0 0 --output build/rendered/view_000_00_00.ppmpython3 -B tools/render_view.py 11 0 0 --output build/rendered/view_011_00_00.ppmmagick build/rendered/picture_001_visual.ppm build/rendered/picture_001_visual.pngmagick build/rendered/picture_001_control.ppm build/rendered/picture_001_control.pngmagick build/rendered/view_000_00_00.ppm build/rendered/view_000_00_00.pngmagick build/rendered/view_011_00_00.ppm build/rendered/view_011_00_00.pngidentify -verbose build/rendered/picture_001_visual.png | sed -n '1,80p'identify -verbose build/rendered/picture_001_control.png | sed -n '1,80p'identify -verbose build/rendered/view_000_00_00.png | sed -n '1,80p'identify -verbose build/rendered/view_011_00_00.png | sed -n '1,80p'python3 -B -m unittestpython3 -B -m unittest discover -s testsmdbook build docsgit diff --check
Rejected or non-evidence probes:
- The first renderer smoke test assumed picture 0 was present. The local
directory entry for picture 0 is absent, so this failed with
ValueError: picture 0 is absentand was not used as evidence. - The first unit-test run used placeholder hashes. Those failures only proved that expected values had not yet been seeded.
- Plain
python3 -B -m unittestreported zero tests, so it is not the suite command for this repository. The documented command now uses explicit test discovery withdiscover -s tests.
Documented result:
- Added
tools/agi_graphics.pyas the first reusable local graphics decoding module. It parses picture and view resources from the local directory and volume files, writes simple PPM output, and exposes deterministic render buffers for tests. - Added
tools/render_picture.pyandtools/render_view.pyas command-line helpers for generating picture and view fixtures underbuild/rendered/. - Added
tests/test_graphics_rendering.pywith six initial unit tests covering picture directory presence, picture 1 scan termination, deterministic picture rendering, all-view frame parsing, and deterministic rendering for two sample view cels. - The local test suite passed with
python3 -B -m unittest discover -s tests. - ImageMagick inspection reported nonblank sample outputs:
picture_001_visual.png: 160 by 168, 11 colors.picture_001_control.png: 160 by 168, 2 colors.view_000_00_00.png: 7 by 33, 6 colors.view_011_00_00.png: 20 by 5, 7 colors.
- The picture renderer remains provisional for seed fill and pattern plotting. The new picture hashes are regression checks for the current implementation hypothesis, not final original-engine compatibility claims.
Graphics compatibility census expansion
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' docs/src/graphics_object_pipeline.mdsed -n '1,240p' docs/src/compatibility_testing.mdsed -n '1,460p' tools/agi_graphics.pysed -n '1,220p' tests/test_graphics_rendering.py- Python one-off scan of all non-null
PICDIRentries usingread_volume_payload; 74 entries had valid volume headers, and entry 147 decoded to invalid target(0, 0x2ffff). - Python one-off scan of all valid picture payloads through
render_picture; all 74 valid pictures rendered without an exception. - Python one-off command-byte census over all valid picture payloads.
- Python one-off view-row scan over all valid view payloads; all decoded rows stayed within their declared frame widths.
xxd -g 1 SQ2/PICDIR | tail -n 8- Python one-off print of final bytes for
LOGDIR,PICDIR,VIEWDIR, andSNDDIR. python3 -B -m unittest discover -s testspython3 -B tools/render_picture.py 45 --output build/rendered/picture_045_visual.ppmpython3 -B tools/render_picture.py 45 --channel control --output build/rendered/picture_045_control.ppmmagick build/rendered/picture_045_visual.ppm build/rendered/picture_045_visual.pngmagick build/rendered/picture_045_control.ppm build/rendered/picture_045_control.pngidentify -verbose build/rendered/picture_045_visual.ppm | sed -n '1,80p'identify -verbose build/rendered/picture_045_control.ppm | sed -n '1,80p'- Python one-off hash calculation for rendered picture 45 cells, visual nibbles, and control nibbles.
Rejected or non-evidence probes:
- The first all-picture scan treated every non-null directory entry as a valid
resource. It failed on
PICDIRentry 147 withValueError('bad VOL.0 resource header at 0x2ffff'). This failure is now recorded as evidence of a sentinel-like directory entry, but the failed scan’s incomplete totals were not used.
Documented result:
- Added
iter_valid_resources(dir_name)totools/agi_graphics.py. It keeps the raw directory reader unchanged but skips entries whose volume headers do not validate. - Expanded
tests/test_graphics_rendering.pyfrom 6 to 12 tests. - The picture tests now assert:
- 74 valid
PICDIRpayloads; - invalid/sentinel-like entry 147 as
(0, 0x2ffff); - every valid picture renders to a 160 by 168 buffer;
- every valid picture payload ends with
0xff; - the exact all-picture command-byte census;
- deterministic hashes for picture 1 and picture 45.
- 74 valid
- The view tests now assert:
- 2,066 decoded frames;
- 50,640 decoded rows;
- no decoded row exceeds its frame width;
- maximum observed cel dimensions of 88 by 129;
- deterministic hashes for two sample cels.
- Picture 45 is the longest valid picture payload observed in this pass, at
4,974 bytes. Its current provisional renderer full-cell hash is
7e8132ddf0658ada246440e409f0801a416d88f003495b7a9f55fbee23fb3974. - The all-picture command-byte census over valid payloads is:
0xf0: 4,7460xf1: 3090xf2: 1,0180xf3: 4250xf6: 7,7360xf7: 9,2820xf8: 1,4470xf9: 220xfa: 7010xff: 74
- No valid local SQ2 picture payload uses command
0xf4or0xf5in this scan. - ImageMagick inspection reported nonblank picture 45 samples:
picture_045_visual.ppm: 160 by 168, 11 colors.picture_045_control.ppm: 160 by 168, 11 colors.
PPM inspection helper for QEMU validation
Commands run from /Users/peter/ai/agi/reverse:
find build -maxdepth 3 -type f | sort | sed -n '1,160p'find . -maxdepth 3 -type f -name '*screen*' -o -name '*.ppm' -o -name '*.png' | sort | sed -n '1,160p'identify build/dos622/sq2_01.ppm build/dos622/sq2_02.ppm build/dos622/screen0.ppm build/rendered/picture_001_visual.ppm build/rendered/picture_045_visual.ppm- Python one-off read of the first 64 bytes of selected QEMU PPM captures.
python3 -B -m unittest discover -s testspython3 -B tools/inspect_ppm.py build/dos622/sq2_01.ppmpython3 -B tools/inspect_ppm.py build/rendered/picture_045_visual.ppmpython3 -B tools/inspect_ppm.py build/rendered/picture_045_control.ppm
Documented result:
- Added
tools/ppm_tools.pywith a small binary PPM reader, RGB digest helper, unique-color collection, and first-pixel-background bounding-box helper. - Added
tools/inspect_ppm.pyas a CLI wrapper around those helpers. - Added a unit test proving local picture PPM output can be parsed by the same helper intended for QEMU screenshots.
- Existing QEMU screenshots under
build/dos622/include both 720 by 400 DOS text-mode captures and 640 by 400 SQ2 game captures. For example,build/dos622/sq2_01.ppmparsed as 640 by 400, 4 colors, RGB SHA-25680605890a86b4cfe5304c389a7fec9c7ece9c809812bec8923c60e464fcda12f, with non-background bounds(0, 16, 639, 331). - Local generated picture renders remain in the logical 160 by 168 coordinate
space. Picture 45 visual PPM parsed as 160 by 168, 11 colors, RGB SHA-256
92dc42b905eab360dcec460dbdba5f2382c7c833d461efa2c9c5fc3e86ba213b; its control PPM parsed as 160 by 168, 11 colors, RGB SHA-256354e29e62f1e27ef9f56a3a4db251ac04d5e86a2e095f3ff541d9232a08ef055. - The QEMU-to-local comparison layer still needs an explicit normalization transform because the emulator screenshot is a full VGA frame, not the raw 160 by 168 logical picture buffer.
Pattern plot renderer refinement
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x6524 -e 0x6724 build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'ndisasm -b 16 -o 0x64f0 -e 0x66f0 build/cleanroom/AGI.decrypted.exe | sed -n '1,80p'xxd -g 1 -s 0x15f8 -l 0x80 SQ2/AGIDATA.OVLxxd -g 2 -s 0x1618 -l 0x40 SQ2/AGIDATA.OVLrg -n "pattern|0x652a|0x15f8|0x15f9|0x1619|0xfa|0xf9" docs/src tools- Python one-off print of
pattern_column_mask()andpattern_row_words()from the local AGIDATA bytes. python3 -B -m unittest discover -s tests
Documented result:
- Replaced the local picture renderer’s placeholder circular pattern plotting
with the observed helper
0x652aalgorithm. - Added
pattern_column_mask(column)andpattern_row_words(radius)helpers that read the localAGIDATA.OVLpattern tables instead of hard-coding row shapes in the renderer. - Added a unit test for the observed column masks and selected row-word tables.
- The observed column masks selected from
DS:0x15f9 + column * 4are:0x8000,0x2000,0x0800,0x0200,0x0080,0x0020,0x0008, and0x0002. - The pattern helper draws
radius + 1columns and2 * radius + 1rows after clipping from the source coordinate. - Mode bit
0x10bypasses the row-word/column-mask test. - Mode bit
0x20enables the byte recurrence seeded from[0x15f8] | 1: shift right, XOR with0xb8when carry was set, and draw only when bit 0 is clear and bit 1 is set. - Existing picture regression hashes for pictures 1 and 45 remained unchanged after this refinement, but they still require QEMU comparison before being treated as original-engine parity checks.
Seed fill renderer refinement
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x533b -e 0x563b build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x52f9 -e 0x543b build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'rg -n "0x533b|seed_fill|fill|0xf8|0x534|0x53" docs/src toolsndisasm -b 16 -o 0x53f9 -e 0x553b build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x54a0 -e 0x55e2 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x5724 -e 0x5866 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'python3 -B -m unittest discover -s tests- Python one-off hash calculation for pictures 1 and 45 after the seed-fill model update.
python3 -B tools/render_picture.py 45 --output build/rendered/picture_045_visual.ppmpython3 -B tools/render_picture.py 45 --channel control --output build/rendered/picture_045_control.ppmpython3 -B tools/inspect_ppm.py build/rendered/picture_045_visual.ppmpython3 -B tools/inspect_ppm.py build/rendered/picture_045_control.ppmmagick build/rendered/picture_045_visual.ppm build/rendered/picture_045_visual.pngmagick build/rendered/picture_045_control.ppm build/rendered/picture_045_control.png
Rejected or non-evidence probes:
- The first disassembly command in this pass used the previously noted seed
label but landed on an unhelpful alignment window. The focused reruns around
0x53f9..0x55e5are the evidence for this pass.
Documented result:
- Refined the local seed-fill model from “fill each active channel separately”
to the observed interpreter contract:
- if visual drawing is active, select low nibble target
0xf; - otherwise, if control drawing is active, select high nibble target
0x40; - exit immediately if the selected replacement is the default target value;
- for accepted cells, call the normal pixel write path, so both active channels can be changed by the fill.
- if visual drawing is active, select low nibble target
- The executable helper is a stack-backed horizontal span fill. The local renderer still uses an explicit queue over four-neighbor cells for traversal, but now uses the observed test-channel priority and normal pixel-write rule.
- Picture 1 hashes did not change. Picture 45’s visual hash did not change, but its control and combined-cell hashes changed, matching the expectation that the refinement affects control side effects when both draw channels are active.
- Updated the picture 45 full-cell regression hash to
7e8132ddf0658ada246440e409f0801a416d88f003495b7a9f55fbee23fb3974. - Updated the generated picture 45 control PPM RGB hash to
354e29e62f1e27ef9f56a3a4db251ac04d5e86a2e095f3ff541d9232a08ef055. - Added two synthetic picture bytecode tests for seed fill:
f2 02 f0 01 f8 00 00 ffstarts with both control and visual drawing active. It expands through the low-nibble default target and writes every cell to0x21, proving that visual is the test channel but both active channels can be written.f2 02 f8 00 00 ffhas only control drawing active. It expands through the high-nibble default target and writes every cell to0x2f.
- The local compatibility suite passed with 16 tests after these synthetic checks were added.
Picture line helper refinement
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x66e1 -e 0x68c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x526f -e 0x5460 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x66e1 -e 0x68e1 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x526f -e 0x546f build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'- Python one-off comparison of the observed accumulator stepping model with the previous generic line algorithm over small coordinate ranges.
python3 -B -m unittest discover -s tests
Rejected or non-evidence probes:
- The first
0x66e1disassembly command used the wrong file skip offset and landed slightly early. The rerun with-e 0x68e1is the evidence used for the line-helper notes.
Documented result:
- Replaced the local diagonal line routine with the observed helper
0x66e1accumulator structure:- horizontal and vertical special cases use dedicated helpers;
- the caller plots the starting point before entering the helper;
- the major axis supplies the loop count;
- the minor-axis accumulator starts at half the major delta;
- Y accumulator/step is processed before X accumulator/step;
- each generated point is written through the normal pixel helper.
- Existing SQ2 picture regression hashes did not change after this refinement.
- Added synthetic tests for an absolute
0xf6line and a packed relative0xf7line. Both forms currently assert the plotted point set(0,0),(1,0),(2,1),(3,1). - The local compatibility suite passed with 18 tests after these line checks were added.
Object-frame composition helper
Commands run from /Users/peter/ai/agi/reverse:
rg -n "object drawing|IBM_OBJS|0x9db|view frame|transparent|priority|\\+0x24|0x587d|rewrite" docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md toolsndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVL | sed -n '1,260p'ndisasm -b 16 -o 0x587d -e 0x5a7d build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'sed -n '260,860p' docs/src/graphics_object_pipeline.mdsed -n '1,260p' tools/inspect_view.pypython3 -B -m unittest discover -s tests
Documented result:
- Added
draw_frame_on_buffer()andcompose_frame_on_picture()totools/agi_graphics.py. - The helper models the central object overlay draw rule from
IBM_OBJS.OVL:0x9db6: object top isbaseline_y - frame.height + 1, transparent-color pixels do not write, and nontransparent pixels write(priority << 4) | coloronly when the destination high-nibble priority/control comparison permits it. - Added tests for baseline placement, transparent pixels, direct rejection by a higher existing priority, and rejection after scanning downward from a low-control cell.
Generated logic fixture for QEMU picture validation
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' docs/src/logic_resources.mdsed -n '1,220p' docs/src/logic_bytecode.mdrg -n "load_picture|prepare_picture|show_picture|draw|return|end|0x18|0x19|0x1a|logic header|message|messages|LOGIC" docs/src tools/disassemble_logic.pypython3 -B tools/disassemble_logic.py 0 | sed -n '1,220p'xxd -g 1 -l 160 SQ2/VOL.1- Python one-off print of action-table entries for
assignn,load_picture_var,prepare_picture_var,show_picture_like, and related display/input actions. - Python one-off scan of valid local logic resources to inspect zero-message payload trailers.
- Python one-off print of
VOL.*file sizes and firstLOGDIRentries. xxd -g 1 -l 32 SQ2/VOL.3python3 -B -m unittest discover -s testspython3 -B tools/qemu_fixture.py picture 45 --output build/qemu-fixtures/picture_045xxd -g 1 -l 32 build/qemu-fixtures/picture_045/LOGDIRxxd -g 1 -l 64 build/qemu-fixtures/picture_045/VOL.3find build/qemu-fixtures/picture_045 -maxdepth 1 -type f | wc -l- Python one-off check of generated
VOL.3header/payload bytes and patchedLOGDIR[0]. mdir -i build/dos622/dos622.img@@32256 ::mmd -i build/dos622/dos622.img@@32256 ::/SQ2P45mcopy -i build/dos622/dos622.img@@32256 build/qemu-fixtures/picture_045/* ::/SQ2P45qemu-system-i386 -m 16 -boot c -drive file=build/dos622/dos622.img,format=raw,if=ide,index=0,media=disk -display vnc=127.0.0.1:5 -monitor stdio- QEMU monitor
sendkeycommands forcd \SQ2P45andSIERRA. - QEMU monitor command
screendump build/qemu-fixtures/picture_045/qemu_picture_045.ppm. - QEMU monitor command
quit. python3 -B tools/inspect_ppm.py build/qemu-fixtures/picture_045/qemu_picture_045.ppmidentify build/qemu-fixtures/picture_045/qemu_picture_045.ppm- Python one-off nearest-palette/downsample comparison between the QEMU capture
and
render_picture(45).visual_nibbles. python3 -B tools/compare_picture_capture.py 45 build/qemu-fixtures/picture_045/qemu_picture_045.ppmmagick build/qemu-fixtures/picture_045/qemu_picture_045.ppm build/qemu-fixtures/picture_045/qemu_picture_045.png
Rejected or non-evidence probes:
python3 -B tools/disassemble_logic.py --logic 0failed because the local disassembler takes logic numbers as positional arguments.- One
rgcommand used a search pattern containing markdown backticks and failed shell quoting; it was not used as evidence. - The first synthetic scaled-PPM unit test wrote only 200 rows while declaring a 400-row image. The PPM parser rejected it, the test was corrected, and the malformed generated file was not used as evidence.
Documented result:
- Added
tools/qemu_fixture.py. Forpicture N, it copies the localSQ2/files intobuild/qemu-fixtures/picture_NNN, replacesVOL.3with a custom logic resource, and patchesLOGDIR[0]to point toVOL.3offset zero. - The generated picture-45 logic payload is:
- resource header in
VOL.3:12 34 03 10 00; - logic code length:
0b 00; - bytecode:
03 fa 2d 18 fa 19 fa 1a fe fd ff; - message trailer:
00 02 00.
- resource header in
- The bytecode means:
assignn(v250, 45);load_picture_var(v250);prepare_picture_var(v250);show_picture_like();jump -3, looping on the jump after the picture has been shown.
- Copied the generated fixture to
C:\SQ2P45insidebuild/dos622/dos622.imgand launched it with the original interpreter in QEMU. - Captured
build/qemu-fixtures/picture_045/qemu_picture_045.ppmfrom QEMU. The capture is 640 by 400, has 11 nearest-palette colors, and has RGB SHA-256615a1a8ae22d4e04774f725adb395bc3d05372b10d41c81a61a99eb098d1d34c. - A top-left aligned
4x2downsample from the 640 by 400 QEMU capture to the 160 by 168 logical picture space matchedrender_picture(45).visual_nibbleswith 0 mismatches out of 26,880 pixels. - Added
tools/compare_picture_capture.pyand a synthetic scaled-capture unit test for the comparison path. - The local compatibility suite passed with 25 tests after adding the fixture and comparison helpers.
Generated logic fixture for QEMU picture plus view validation
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/qemu_fixture.py picture-view 1 11 0 0 20 80 15 --output build/qemu-fixtures/picture_001_view_011_00_00python3 -B -m unittest discover -s testsmmd -i build/dos622/dos622.img@@32256 ::/SQ2V11mcopy -i build/dos622/dos622.img@@32256 build/qemu-fixtures/picture_001_view_011_00_00/* ::/SQ2V11qemu-system-i386 -m 16 -boot c -drive file=build/dos622/dos622.img,format=raw,if=ide,index=0,media=disk -display vnc=127.0.0.1:5 -monitor stdio- QEMU monitor
sendkeycommands forcd \SQ2V11andSIERRA. - QEMU monitor command
screendump build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm. - QEMU monitor command
quit. python3 -B tools/inspect_ppm.py build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppmmagick build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.pngpython3 -B tools/compare_picture_capture.py 1 build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppm --view 11 0 0 --view-x 20 --view-baseline-y 80 --view-priority 15
Documented result:
- Extended
tools/qemu_fixture.pywithpicture-view, which draws a selected picture, loads a selected view, draws one selected cel at a controlled position, and then loops. - The generated picture-1/view-11/group-0/frame-0 logic payload is:
- resource header in
VOL.3:12 34 03 1a 00; - logic code length:
15 00; - bytecode:
03 fa 01 18 fa 19 fa 1a 1e 0b 7a 0b 00 00 14 50 0f 0f fe fd ff; - message trailer:
00 02 00.
- resource header in
- The bytecode means:
assignn(v250, 1);load_picture_var(v250);prepare_picture_var(v250);show_picture_like();load_view(11);setup_transient_object(view=11, group=0, frame=0, x=20, baseline_y=80, priority=15, control=15);jump -3, looping on the jump after the picture and cel have been shown.
- Copied the generated fixture to
C:\SQ2V11insidebuild/dos622/dos622.imgand launched it with the original interpreter in QEMU. - Captured
build/qemu-fixtures/picture_001_view_011_00_00/qemu_picture_001_view_011_00_00.ppmfrom QEMU. The capture is 640 by 400, has 15 unique RGB colors, has a non-background bounding box of(0, 0, 639, 335), and has RGB SHA-256f63b82fb30ab0c2796f695e2678937f1c0a90e9cb3bbb85338bfccea5a6ac816. - Using the same top-left aligned
4x2downsample as the picture-only fixture, the QEMU capture matchedcompose_frame_on_picture(render_picture(1), render_view_frame(11, 0, 0), left=20, baseline_y=80, priority=15)with 0 mismatches out of 26,880 pixels. - The local compatibility suite passed with 26 tests before running the QEMU fixture.
Bit-0x80 view-frame orientation rewrite
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/inspect_view.py 0 1 2 11 --groups 8 --frames 8ndisasm -b 16 -o 0x587d -e 0x5a7d build/cleanroom/AGI.decrypted.exe- Python one-off scan of valid
VIEWDIRresources for frames with control bit0x80whose cached bits(control & 0x70) >> 4differ from the selected group number. - Python one-off print of view 0/group 0/frame 0 row bytes before and after the local rewrite model.
python3 -B -m unittest discover -s testspython3 -B tools/qemu_fixture.py picture-view 1 0 1 0 20 80 15 --output build/qemu-fixtures/picture_001_view_000_01_00mmd -i build/dos622/dos622.img@@32256 ::/SQ2V01mcopy -i build/dos622/dos622.img@@32256 build/qemu-fixtures/picture_001_view_000_01_00/* ::/SQ2V01qemu-system-i386 -m 16 -boot c -drive file=build/dos622/dos622.img,format=raw,if=ide,index=0,media=disk -display vnc=127.0.0.1:5 -monitor stdio- QEMU monitor
sendkeycommands forcd \SQ2V01andSIERRA. - QEMU monitor command
screendump build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm. - QEMU monitor command
quit. python3 -B tools/inspect_ppm.py build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppmmagick build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.pngpython3 -B tools/compare_picture_capture.py 1 build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppm --view 0 1 0 --view-x 20 --view-baseline-y 80 --view-priority 15
Rejected or non-evidence probes:
- The first local row-rewrite model treated the mirrored row’s emitted leading
transparent width as
width - tail_width, wheretail_widthstarted at the first nontransparent run. The disassembly keeps explicit leading transparent width in the accumulator before the first nontransparent run, so the correct emitted width iswidth - total_explicit_row_width. The failing unit test was not used as evidence.
Documented result:
- Added
_mirror_view_row_runs()and_orient_view_rows()totools/agi_graphics.py. - The rewrite model matches helper
0x587d:- only frames with control bit
0x80are candidates; - bits
0x70of the frame control byte cache the current orientation/group; - when cached bits differ from the selected group, those bits are replaced;
- each row is rebuilt by emitting the original implicit trailing transparent width, then copying the counted run bytes in reverse order, then writing a zero row terminator.
- only frames with control bit
- A local scan found 229 valid SQ2 frames where bit
0x80is set and the selected group differs from the cached bits. View 0/group 1/frame 0 is the first such sample. - Added a unit test proving that
render_view_frame(0, 1, 0)is the horizontal mirror ofrender_view_frame(0, 0, 0)and reports rewritten control byte0x91. - Captured
build/qemu-fixtures/picture_001_view_000_01_00/qemu_picture_001_view_000_01_00.ppmfrom QEMU. The capture is 640 by 400, has 14 unique RGB colors, has a non-background bounding box of(0, 0, 639, 335), and has RGB SHA-2561fb4fbfaa4d7b93b15fa007930e87d7c1982cb78626441a28d56ae46fdd8bd96. - Using the same top-left aligned
4x2downsample as the previous fixtures, the QEMU capture matchedcompose_frame_on_picture(render_picture(1), render_view_frame(0, 1, 0), left=20, baseline_y=80, priority=15)with 0 mismatches out of 26,880 pixels. - The local compatibility suite passed with 27 tests after the rewrite model and orientation test were added.
Synthetic picture fuzzing framework
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' tools/qemu_fixture.pysed -n '1,220p' tools/compare_picture_capture.pysed -n '1,620p' tools/agi_graphics.pyfind tests -maxdepth 2 -type f -printsed -n '1,220p' tests/test_qemu_fixture.pypython3 -B -m unittest discover -s testspython3 -B tools/picture_fuzz.py generate --count 64 --seed 4097 --output build/picture-fuzz/corpus --cleanpython3 -B tools/picture_fuzz.py run-qemu base_003_visual_point --dos-dir FZVPOINT --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --cleanpython3 -B tools/picture_fuzz.py run-qemu base_002_unknown_commands --dos-dir FZUNK --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py run-qemu base_004_clamped_absolute --dos-dir FZCLAMP --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py run-qemu base_009_visual_control_fill --dos-dir FZFILL --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py run-qemu base_011_pattern_random --dos-dir FZPATT --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py run-qemu base_013_truncated_pair --dos-dir FZTRUNC --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py compare-capture base_004_clamped_absolute build/picture-fuzz/fixtures/base_004_clamped_absolute/qemu_capture.ppmndisasm -b 16 -o 0x66e1 -e 0x68e1 build/cleanroom/AGI.decrypted.exe- Python one-off comparing captured and locally rendered changed pixels for
base_004_clamped_absolute. xxd -g 1 -l 96 build/picture-fuzz/fixtures/base_004_clamped_absolute/VOL.3xxd -g 1 -l 12 build/picture-fuzz/fixtures/base_004_clamped_absolute/PICDIRcat build/picture-fuzz/corpus/base_004_clamped_absolute/case.json- Python one-off simulating line helper
0x66e1with and without 8-bit accumulator wrap. python3 -B tools/picture_fuzz.py run-qemu base_005_exact_edge_absolute --dos-dir FZEDGE --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py compare-capture base_004_clamped_absolute build/picture-fuzz/fixtures/base_004_clamped_absolute/qemu_capture.ppmpython3 -B tools/picture_fuzz.py run-qemu base_010_visual_control_fill --dos-dir FZFIL3 --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py run-qemu base_012_pattern_random --dos-dir FZPAT3 --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py run-qemu base_014_truncated_pair --dos-dir FZTRN3 --boot-wait 5 --draw-wait 8
Rejected or non-evidence probes:
- The first
run-qemu base_003_visual_pointattempt hid QEMU output and surfaced only a broken pipe. The harness was corrected to preserve QEMU output on early exit. - Picture fuzz cases marked
safe_for_qemu: falseare excluded from original-engine compatibility evidence. They can make the interpreter read past the synthetic resource and begin interpreting unrelated memory, which is security/exploit behavior rather than a stable semantic contract for AGI resource decoding. - Python-launched QEMU initially failed under the restricted sandbox with
Failed to bind socket: Operation not permitted; the same command succeeded after the user allowed full access. - Running three QEMU fuzz cases in parallel was rejected as a bad probe. QEMU needs a single VNC display socket, and concurrent mtools/QEMU fixture copying can collide on the DOS image.
- A compare for
base_005_exact_edge_absolutewas started in parallel with a corpus regeneration command. The compare raced the regenerated manifest and failed withFileNotFoundError; the case was present after regeneration and the QEMU comparison was rerun sequentially. - While probing mtools behavior, directory
FZNEW99was created in the DOS image. It was not used as evidence.
Documented result:
- Added generic
patch_dir_entry()andbuild_synthetic_picture_fixture()totools/qemu_fixture.py. A synthetic fixture copies localSQ2/, writesVOL.3with both customLOGIC.0and a synthetic picture payload, patchesLOGDIR[0]to the logic record, and patchesPICDIR[picture_no]to the picture record. - Added
tools/picture_fuzz.py. It can:- generate deterministic curated and random picture-resource corpora;
- record payload hashes and Python render hashes in
case.jsonandmanifest.json; - build original-engine fixture directories for a selected fuzz case;
- copy a fixture into the DOS image, run it in QEMU, capture a
screendump, and compare the original-engine output against the Python renderer; - compare an existing QEMU capture and report mismatch count, bounding box, and sample mismatched pixels.
- Added
tests/test_picture_fuzz.pyand expandedtests/test_qemu_fixture.py. The local suite passed with 34 tests after the fuzz framework and line regression were added. - The current corpus command
python3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --cleangenerates 1,040 cases: 16 curated base cases and 1,024 deterministic random cases. It currently marks 1,038 cases safe for automated QEMU runs. - Updated
tools/picture_fuzz.py run-qemuto reject cases markedsafe_for_qemu: falsebefore building or launching a fixture. This preserves the project boundary that malformed overread/exploit behavior is not part of the compatibility spec being built. - The first representative QEMU fuzz cases matched:
base_002_unknown_commands: 0 mismatches;base_003_visual_point: 0 mismatches;base_009_visual_control_fillbefore renumbering: 0 mismatches;base_011_pattern_randombefore renumbering: 0 mismatches;base_013_truncated_pairbefore renumbering: 0 mismatches.
base_004_clamped_absoluteinitially mismatched with 312 pixels. The mismatch samples showed a screen-scale diagonal line displaced relative to the Python renderer. The exact payload in the fixture wasf0 02 f6 ef ef 00 00 ff, andPICDIR[0]correctly pointed to the synthetic picture inVOL.3, so the mismatch was treated as behavioral evidence rather than a fixture error.- A new curated case,
base_005_exact_edge_absolute, using payloadf0 02 f6 9f a7 00 00 ff, reproduced the same mismatch. That proved the gap was not out-of-range coordinate clamping but the diagonal line helper. - Simulating
code.picture.draw_line(0x66e1) with 8-bit accumulator wrap reproduced the original-engine edge shape. In particular, the long line from(159,167)to(0,0)includes(25,0)and(25,1)and does not include(0,0). - Updated
PictureRenderer.draw_line()to wrap the X and Y accumulators to 8 bits after addition and subtraction. Addedtest_long_diagonal_uses_byte_width_line_accumulators. - After the fix,
base_004_clamped_absoluteandbase_005_exact_edge_absoluteboth matched QEMU with 0 mismatches out of 26,880 pixels. - Reran representative cases after the fix:
base_010_visual_control_fill: 0 mismatches;base_012_pattern_random: 0 mismatches;base_014_truncated_pair: 0 mismatches.
Picture fuzz batch runner and pattern edge correction
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_picture_fuzzpython3 -B tools/picture_fuzz.py generate --count 64 --seed 4097 --output build/picture-fuzz/corpus --cleanpython3 -B tools/picture_fuzz.py batch-qemu --case base_000_stop_only --case base_001_ignored_data --case base_002_unknown_commands --case base_003_visual_point --case base_004_clamped_absolute --case base_005_exact_edge_absolute --case base_006_relative_mixed --case base_007_corner_y_first --case base_008_corner_x_first --case base_009_control_fill --case base_010_visual_control_fill --case base_011_pattern_mask --case base_012_pattern_random --case base_014_truncated_pair --dos-prefix FB --output build/picture-fuzz/batches/base_curated_001.json --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py batch-qemu --case rand_00000 --case rand_00001 --case rand_00002 --case rand_00003 --case rand_00004 --case rand_00005 --case rand_00006 --case rand_00007 --case rand_00008 --case rand_00009 --case rand_00010 --case rand_00011 --case rand_00012 --case rand_00013 --case rand_00014 --case rand_00015 --dos-prefix FR --output build/picture-fuzz/batches/random_00000_00015.json --boot-wait 5 --draw-wait 8python3 -B tools/picture_fuzz.py batch-qemu --case base_016_visual_fill_box --case base_017_visual_fill_outside_box --case base_018_pattern_edge_circle --case base_019_pattern_edge_rectangle --case base_020_pattern_random_sequence --dos-prefix FT --output build/picture-fuzz/batches/targeted_fill_pattern_001.json --boot-wait 5 --draw-wait 8mdir -i build/dos622/dos622.img@@32256 ::python3 -B tools/picture_fuzz.py batch-qemu --case base_016_visual_fill_box --case base_017_visual_fill_outside_box --case base_018_pattern_edge_circle --case base_019_pattern_edge_rectangle --case base_020_pattern_random_sequence --dos-prefix FV --output build/picture-fuzz/batches/targeted_fill_pattern_003.json --boot-wait 5 --draw-wait 8
Documented result:
- Added
batch-qemutotools/picture_fuzz.py. It selects only safe cases, runs them serially through QEMU, prints per-case progress, and writes JSON reports containing status, capture paths, elapsed seconds, mismatch boxes, and mismatch samples. - The 14-case curated safe batch matched with 0 mismatches and 0 errors.
- The first 16 safe random cases matched with 0 mismatches and 0 errors.
- Added curated cases
base_016throughbase_020for bounded fill barriers, lower-right pattern edge placement, rectangular pattern masks, and multiple pseudo-random pattern seeds. - The first targeted fill/pattern batch showed that bounded fill and
pseudo-random pattern sequences matched, but lower-right circle and rectangle
pattern plots mismatched at X
0. - The mismatch proved that pattern plotting can compute X
160and write through the linear picture buffer address. This makes the byte appear at X0on the next scanline instead of being clipped. - Updated
PictureRenderer.pattern_plot()to write through a linear buffer helper. After regenerating the corpus,base_018_pattern_edge_circleandbase_019_pattern_edge_rectangleboth matched QEMU with 0 mismatches. - A rerun initially failed with DOS-image
Disk fullerrors, not renderer mismatches. The cause was old generated.ppmcaptures being copied back into the DOS image with fixture files.copy_fixture_to_dos()now excludes.ppmfiles, and generated fuzz directories were removed from the DOS image.
View/object QEMU batch validation
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest discover -s testspython3 -B tools/view_batch.py --dos-prefix VB --output build/view-batch/batches/view_base_001.json --boot-wait 5 --draw-wait 8- Cached Python comparison of
view_011_top_clipagainst alternate localleftandbaseline_yvalues. python3 -B -m unittest tests.test_graphics_rendering tests.test_view_batchpython3 -B tools/view_batch.py --dos-prefix VC --output build/view-batch/batches/view_base_002.json --boot-wait 5 --draw-wait 8
Documented result:
- Added
tools/view_batch.py, a serial QEMU harness for picture-plus-view fixtures. It builds fixtures withbuild_picture_view_fixture(), runs each one in QEMU, compares the capture with local picture/view composition, and writes a JSON report. - Extended
tools/compare_picture_capture.pyto include mismatch bounding boxes and sample pixels in addition to mismatch counts. - Added
tests/test_view_batch.pyfor case loading, stable DOS directory names, and report summaries. - The first six-case view batch matched normal view drawing, cached group-0
bit-
0x80drawing, mirrored group-1 bit-0x80drawing, left-edge clipping, and a low-priority object case. The top-edge case mismatched with 75 pixels in rows 0 through 4. - Comparing the top-edge capture against local placements found an exact match
at left
18, baseline4, even though the fixture requested left20, baseline2. Since view 11/group 0/frame 0 has height 5, the requested top was-2; the observed overlay behavior is to add that negative top to left, raise the baseline by the absolute amount, and draw from top row 0. - Updated
draw_frame_on_buffer()with that top-edge adjustment and added a regression test. The second view batch matched all six cases with 0 mismatches and 0 errors.
QEMU host-directory fixture access
Commands run from /Users/peter/ai/agi/reverse:
qemu-system-i386 --version- Python QEMU launch probe with:
-drive file=fat:rw:build/qemu-share,format=raw,if=ide,index=1,media=disk python3 -B tools/qemu_fixture.py picture 1 --output build/qemu-share/PIC001- QEMU monitor-driven run from DOS drive
D::D:,cd \PIC001,SIERRA, followed byscreendump build/qemu-share/from_share_pic001.ppm python3 -B tools/compare_picture_capture.py 1 build/qemu-share/from_share_pic001.ppmqemu-img create -f qcow2 -F raw -b /Users/peter/ai/agi/reverse/build/dos622/dos622.img build/dos622/dos622-test.qcow2- QEMU snapshot probes with writable vvfat hard disk, read-only vvfat CD-ROM,
and
fat:floppy:host shares.
Documented result:
- QEMU 11.0.2 accepts
file=fat:rw:build/qemu-shareas a secondary IDE disk. DOS 6.22 sees it as driveD:with volume labelQEMU VVFAT. - A generated picture-1 fixture placed at
build/qemu-share/PIC001ran directly fromD:\PIC001without copying it into the DOS hard disk image. The capturebuild/qemu-share/from_share_pic001.ppmmatched the local picture-1 renderer with 0 mismatches. - The generated fixture does not return to DOS after drawing; sending
DIRafter the draw left the game screen visible. Running multiple cases in one guest session therefore needs a reset/restore mechanism. - QEMU
savevmfails when a writable vvfat drive is attached:The vvfat (rw) format ... does not support live migration. - A qcow2 overlay for the DOS boot disk can hold VM snapshots. A read-only
vvfat CD-ROM permits
savevm, but the current DOS image lacks an IDE/ATAPI CD-ROM driver, soDIR D:\reportsInvalid drive specification. fat:floppy:read-only host shares can be snapshotted and are visible asA:only when the host directory fits FAT12 constraints. This is too small and awkward for full AGI fixture directories; a nested fixture directory appeared empty, and a root-level fixture exposed only a subset of files.- Practical next options:
- use
fat:rw:asD:now to avoid mtools copies and DOS-image pollution, still launching/resetting QEMU per case; - install/configure a DOS CD-ROM driver and use read-only vvfat CD-ROM plus
savevm/loadvmfor no-boot batches; - or generate a qcow2/FAT test disk containing prebuilt fixtures and use QEMU snapshots at the DOS prompt.
- use
QEMU snapshot fixture disk
Commands run from /Users/peter/ai/agi/reverse:
- Built a picture-only fixture and a picture-plus-view fixture:
python3 -B tools/qemu_fixture.py picture 1 --output build/qcow-fixture-test/fixtures/PIC001andpython3 -B tools/qemu_fixture.py picture-view 1 11 0 0 20 80 15 --output build/qcow-fixture-test/fixtures/VIEW11. - Probed a partitionless FAT image, an MBR-partitioned FAT image starting at
sector 63, and a DOS-geometry-like secondary hard disk. All could either be
manipulated by mtools or attached to QEMU, but DOS did not expose them as a
usable
D:drive. - Copied
build/dos622/dos622.imgto a disposable raw image, copied fixture directories into its DOS partition with mtools at offset32256, converted it to qcow2, booted it asC:, ransavevm ready, then usedloadvm readybetweenPIC001andVIEW11. - Compared the captures with:
python3 -B tools/compare_picture_capture.py 1 build/qcow-fixture-test/snapshot_pic001.ppmandpython3 -B tools/compare_picture_capture.py 1 build/qcow-fixture-test/snapshot_view11.ppm --view 11 0 0 --view-x 20 --view-baseline-y 80 --view-priority 15.
Documented result:
- The secondary qcow2/FAT fixture disk approach is not yet usable with DOS 6.22 in this setup.
- A disposable qcow2 clone of the DOS boot disk with fixture directories
preloaded onto
C:supports QEMU internal snapshots. - Both snapshot-run captures matched with 0 mismatches, proving that one QEMU
boot plus
savevm/loadvmcan replace repeated boots for batches whose fixture set is known in advance. - Added
tools/qemu_snapshot.pyandtools/view_batch.py --snapshotto make this reusable for the view/object validation cases. - Added
tools/picture_fuzz.py batch-qemu --snapshotso known-ahead fuzz batches can use the same one-boot fixture disk rather than rebooting QEMU for each case. - Verified the reusable path with
tools/view_batch.py --snapshot: all six built-in view/object cases matched from one QEMU boot. - Verified
tools/picture_fuzz.py batch-qemu --snapshotwithbase_016_visual_fill_boxandbase_019_pattern_edge_rectangle: both cases matched with 0 mismatches from one QEMU boot.
2026-07-02: object overlay priority probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest discover -s testspython3 -B tools/object_overlay_probe.py --dos-prefix OP --output build/object-overlay-probes/batches/base_priority.json --boot-wait 5 --draw-wait 8python3 -B tools/object_overlay_probe.py --dos-prefix OQ --output build/object-overlay-probes/batches/priority_scan_down.json --boot-wait 5 --draw-wait 8python3 -B tools/object_overlay_probe.py --dos-prefix OR --output build/object-overlay-probes/batches/priority_nibbles.json --boot-wait 5 --draw-wait 8python3 -B tools/object_overlay_probe.py --dos-prefix OG --output build/object-overlay-probes/batches/expanded_all5_final.json --boot-wait 5 --draw-wait 8
Documented result:
- Added
build_synthetic_picture_view_fixture()totools/qemu_fixture.py. This patches bothLOGDIR[0]and a chosenPICDIRentry so the original engine can draw a generated picture resource and then overlay a chosen view cel through action0x7a(setup_transient_object). - Added
tools/object_overlay_probe.py, a snapshot-backed QEMU harness for targeted object overlay cases with controlled synthetic picture backgrounds. - The eight built-in priority probes matched QEMU with 0 mismatches. The cases
confirm:
- default cleared control priority
4hides object priority3and allows object priority4; - full-screen synthetic control priority
6hides object priority5and allows object priority6; - visible overlay gating uses the low nibble of object byte
+0x24, not the high nibble staged by operand 7 of action0x7a; - when the destination cell contains low control
2, the overlay routine scans downward to a control-6row and applies the same comparison there.
- default cleared control priority
- The expanded 19-case object overlay batch matched QEMU with 0 mismatches.
It adds evidence for:
- right-edge transient placement: requested left
154, baseline80for view 11/group 0/frame 0 matched a local draw at left140, baseline67; - bottom-edge drawing for view 11 at baseline
167; - transparent-color variants in views 21, 29, and 10;
- derived priority when
0x7astages low priority zero, including the effect of action0xaerebuilding the priority table at row100; - persistent object-table setup and activation for a static object;
- persistent fixed priority bytes with nonzero high nibbles hiding in the controlled priority probes, so those byte values should not yet be treated as normal visible priorities.
- right-edge transient placement: requested left
2026-07-02: targeted object movement QEMU probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest discover -s testspython3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_once.json --boot-wait 5 --draw-wait 8- Local best-position scan over the failed one-shot captures.
python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_reissued.json --boot-wait 5 --draw-wait 8python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_edges.json --boot-wait 5 --draw-wait 8python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_control.json --boot-wait 5 --draw-wait 8python3 -B tools/object_movement_probe.py --dos-prefix MV --output build/object-movement-probes/batches/base_movement_control_final.json --boot-wait 5 --draw-wait 8python3 -B tools/object_movement_probe.py --dos-prefix MX --output build/object-movement-probes/batches/expanded_movement_edges.json --boot-wait 5 --draw-wait 8python3 -B tools/object_movement_probe.py --dos-prefix MX --output build/object-movement-probes/batches/expanded_movement_edges_final.json --boot-wait 5 --draw-wait 8ndisasm -b 16 -o 0x4719 -e 0x4919 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'- Focused search for writes to object byte
+0x02. python3 -B tools/object_movement_probe.py --dos-prefix MC --output build/object-movement-probes/batches/movement_collision.json --boot-wait 5 --draw-wait 8python3 -B tools/object_movement_probe.py --dos-prefix MA --output build/object-movement-probes/batches/autonomous_modes_001.json --boot-wait 5 --draw-wait 8python3 -B tools/object_movement_probe.py --dos-prefix MB --output build/object-movement-probes/batches/autonomous_modes_002.json --boot-wait 5 --draw-wait 8python3 -B tools/object_movement_probe.py --dos-prefix MD --output build/object-movement-probes/batches/autonomous_modes_003.json --boot-wait 5 --draw-wait 8- Local Python scan for near call sites to
0x0b36,0x3f5a,0x1672, and0x16b9. ndisasm -b 16 -o 0x0563 -e 0x0763 build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'
Documented result:
- Added reusable generated-logic helpers to
tools/qemu_fixture.py:logic_resource,if_then,not_flag_set_condition,set_flag_action,run_once_logic, and persistent-object fixture support for guarded per-cycle action blocks. - Added
tools/object_movement_probe.py, a snapshot-backed QEMU harness for persistent object-table movement tests. It builds generated logic resources that load a synthetic picture, initialize a persistent object once, then run selected per-cycle actions while a completion flag is clear. - A one-shot
0x51(move_object_to) fixture initialized the object and called the action once. QEMU showed that the object moved in the initial direction but did not stop at the requested target: the horizontal case matched exactly at left140, and the vertical case matched exactly at baseline167. This establishes that a single action call does not recompute target arrival on later ticks. - The corrected fixture reissues
0x51every interpreter cycle while the completion flag is clear. With that shape, the base horizontal target(50,80)and vertical target(20,100)both matched QEMU with 0 mismatches. - The expanded four-case movement batch also matched QEMU with 0 mismatches.
It adds right-edge completion for an unreachable target X of
250, where view 11/group 0/frame 0 stops at left140, and bottom-edge completion for an unreachable target Y of250, where the same cel stops at baseline167. - A first control-buffer acceptance hypothesis used a synthetic picture payload
f2 00 f8 00 00 ff, which fills the decoded control channel with zero in the local renderer. The initial expectation was that movement would be rejected, but QEMU’s capture matched exact target arrival at(50,80). After updating the expected result, the final five-case batch matched QEMU with 0 mismatches. This records that this controlled control-zero picture is not a blanket movement blocker. - The movement comparison report now includes an optional
best_positiontuple(mismatches, x, baseline_y)for mismatches, so future failures can identify where QEMU actually drew the object. - The expanded 12-case movement batch matched QEMU with 0 mismatches after
correcting the zero-step expectation. It confirms:
- left and up directions use the same repeated-call target semantics;
- diagonal movement can continue on one axis after the other axis enters the target band;
- non-divisible distances complete when the remaining distance is within one step, not necessarily at the exact target coordinate;
- already-at-target and already-within-step cases complete without movement;
- operand 3 value zero preserves the current object step byte, and the generated persistent object has current step zero unless the fixture sets it.
- Disassembled
0x4719with the corrected MZ-header offset. The helper returns zero immediately when the moving object has bit0x0200; otherwise it scans active candidates with(flags & 0x41) == 0x41, skips candidates with bit0x0200, skips equal+0x02grouping bytes, checks horizontal rectangle overlap, then checks baseline equality/crossing using current+0x05and saved+0x18. - Startup initialization writes object byte
+0x02 = object_index, so ordinary generated persistent object 0 and object 1 naturally have different grouping bytes and can collide. - Added two-object cases to
tools/object_movement_probe.py. The final 14-case batch matched QEMU with 0 mismatches:- object 0 moving from
(20,80)toward(80,80)stops at(25,80)before touching object 1 parked at(50,80); - setting bit
0x0200on object 0 with action0x43lets the same movement reach(80,80).
- object 0 moving from
- Added bytecode fixture helpers for
assignn,set_object_field_1e_var,set_object_field_01_var,approach_first_object_until_near,start_random_motion, andstop_motion_mode, then added a mode-2 approach case to the movement probe. - The first mode-2 QEMU pass used threshold
25and did not isolate direct completion: the capture best fit was object 1 at(60,75), indicating that the object had reached the collision/stuck-recovery region near object 0. - The second pass used threshold
35and initially expected boundary position(45,80), but QEMU’s best fit was(50,80). This records that the near-band completion check did not complete at the exact threshold boundary. - The corrected 15-case batch in
build/object-movement-probes/batches/autonomous_modes_003.jsonmatched QEMU with 0 mismatches. The new passing case initializes object 1 once, sets step5, sets countdown byte+0x01to1, starts0x53toward object 0 with near threshold35, and confirms autonomous mode 2 completes at(50,80)without reissuing0x53from script logic. - Later call-site analysis corrected this per-cycle ordering. Helper
0x0644, not0x0563, scans active/update-eligible objects before logic execution and calls dispatcher0x067afor objects whose countdown byte+0x01is exactly1. Dispatcher0x067acalls0x3f5afor mode+0x22 == 1,0x0b36for mode2, and0x1672for mode3. It then calls extra rectangle-boundary logic when global[0x013d]is nonzero and direction byte+0x21is nonzero. Helper0x0563is called later, after logic 0, and performs automatic group/frame work before invoking0x150aand rebuilding/drawing/refreshing the update list rooted at0x16ff. A later generated QEMU movement fixture isolates the countdown-gated0x067a -> 0x1672path directly.
Countdown-gated motion and additional view cel probes
Commands/evidence:
python3 -B -m unittest discover -s testspython3 -B tools/object_movement_probe.py --dos-prefix ME --output build/object-movement-probes/batches/motion_modes_004.json --boot-wait 5 --draw-wait 8python3 -B tools/object_overlay_probe.py --dos-prefix OE --output build/object-overlay-probes/batches/view_cel_selection_002.json --boot-wait 5 --draw-wait 8python3 -B tools/inspect_view.py 11 --groups 4 --frames 8ndisasm -b 16 -o 0x0b36 -e 0x0d36 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x6b82 -e 0x6d82 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'python3 -B tools/disassemble_logic.py 1 | rg -n "set_object_field_23|clear_object_bit_0020|set_object_bit_0020|0x48|0x49|0x4a|0x4b" -C 2
Documented result:
- Added two more motion cases to
tools/object_movement_probe.py:move_to_once_countdown_gated_completionsets object countdown byte+0x01to1, calls0x51once, and expects completion at(50,80);random_motion_visible_somewheresets step5, sets countdown byte+0x01to1, starts0x54, and accepts any capture that exactly matches the object at a valid final position.
- The 17-case QEMU movement batch matched with 0 mismatches. It confirms that
target mode 3 can complete through the autonomous
0x067a -> 0x1672path without reissuing0x51from script logic when countdown byte+0x01is ready. The recorded random-motion final position was(140,112). - Added three object overlay cases for view 11 group/frame selection: group 0 frame 1, group 1 frame 0, and group 1 frame 1. The 22-case QEMU overlay batch matched with 0 mismatches.
- Disassembly of
0x0b36refined the approach stuck-recovery model:- mode 2 computes object and first-object center X values by adding half the frame width to object X;
- it calls
0x16edwith the near threshold stored in object byte+0x27; - a zero direction clears
+0x21and+0x22and sets completion flag+0x28; - initial sentinel
+0x29 == 0xffchanges to0after the first non-complete direct approach step; - if bit
0x4000says the object did not move, the helper chooses a random nonzero direction, computes a delay from half the center/baseline distance plus one, and stores either the current step size or a random delay at least as large as the step in+0x29; - while
+0x29is nonzero, the helper subtracts the current step byte from it on each pass and delays returning to the direct approach direction.
- A source pass over action handlers
0x48..0x4bconfirmed the setup side of object byte+0x23: mode 0 and mode 3 set bit0x0020; mode 1 and mode 2 set bits0x1030, store an immediate flag in+0x27, and clear that flag. The current QEMU work validates static cel/group selection, not automatic frame-cycling semantics; the runtime consumers of+0x23remain a separate follow-up target.
2026-07-02: logic interpreter opcode coverage and control-flow probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B - <<'PY' ...comparingtools/disassemble_logic.pyACTION_NAMESandCOND_NAMESagainst opcode paragraphs indocs/src/logic_bytecode.md.python3 -B -m unittest discover -s testspython3 -B tools/logic_interpreter_probe.py --dos-prefix LI --output build/logic-interpreter-probes/batches/control_flow_001.json --boot-wait 5 --draw-wait 8- Local best-position scan over the failed logic-interpreter captures.
ndisasm -b 16 -o 0x293c -e 0x2b3c build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'python3 -B tools/logic_interpreter_probe.py --dos-prefix LJ --output build/logic-interpreter-probes/batches/control_flow_002.json --boot-wait 5 --draw-wait 8python3 -B tools/disassemble_logic.py --stats | sed -n '1,140p'
Documented result:
- The opcode-label audit found that
tools/disassemble_logic.pylabels all action opcodes0x00..0xafand all valid-looking SQ2 condition-table entries0x00..0x12. The only missing paragraph inlogic_bytecode.mdwas the structural action byte0x00(end), which is now documented as a normal catalog entry as well as in the interpreter-loop prose. - The logic bytecode chapter now also records the invalid and structural byte
ranges: action bytes
0xb0..0xfbtake the action dispatcher’s invalid-opcode path,0xfcand0xfdare invalid outside conditions,0xfeis the main-stream jump byte, and0xffis the main-stream condition byte. Condition bytes0x13..0x25are reserved/invalid for this SQ2 build,0x26..0xfbare rejected by the condition dispatcher, and0xfc..0xffare condition-list markers rather than predicates. - Added
tests/test_logic_doc_coverage.pyso the documentation now fails the local test suite if any action label or known condition label disappears fromlogic_bytecode.md. - Added
tools/logic_interpreter_probe.py, a snapshot-backed QEMU harness that patches generated logic bytecode intoLOGIC.0, replaces picture 0 with a blank synthetic picture, captures the original interpreter’s visible output, and compares it to localcompose_frame_on_picture()expectations. - Added
setup_transient_object_action()totools/qemu_fixture.pyso logic probes can draw a view cel without duplicating bytecode encoding. - The first QEMU run,
control_flow_001.json, matched onlyif_false_skips_then_draw.jump_skips_first_draw,not_condition_runs_then_draw, andor_group_true_runs_then_drawcaptured a blank background where the expected transient view should have been. A local best-position scan found no exact object match anywhere in those captures. - Disassembly of
code.logic.interpret_main(0x293c) confirmed the static control-flow model:0xfereads a little-endian word throughlodswand adds it toSI;0xffscans a condition list and, on the true path, skips the false-delta word before executing the block;- on the false path, the scanner skips to the condition-list terminator,
reads the false-delta word, and adds it to
SI; - the
0xfdand0xfcmarkers are handled in the condition-list parser, not the action dispatcher.
- The failing generated bytecode ended immediately after a transient draw. The
corrected fixtures append the same self-loop shape used by existing
picture/view fixtures (
fe fd ff) after the final draw so the screenshot is taken while the intended draw state remains visible. - The rerun saved at
build/logic-interpreter-probes/batches/control_flow_002.jsonmatched all four cases with 0 mismatches and 0 errors:jump_skips_first_draw;if_false_skips_then_draw;not_condition_runs_then_draw;or_group_true_runs_then_draw.
2026-07-02: expanded logic opcode-family probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_logic_interpreter_probepython3 -B - <<'PY' ...to list the expandedtools.logic_interpreter_probe.base_cases()set.python3 -B tools/logic_interpreter_probe.py --dos-prefix LK --output build/logic-interpreter-probes/batches/opcode_families_001.json --boot-wait 5 --draw-wait 8 --stop-on-failurepython3 -B tools/logic_opcode_evidence.pypython3 -B tools/logic_opcode_evidence.py --check
Documented result:
- Expanded
tools/logic_interpreter_probe.pyfrom four control-flow cases to 27 default QEMU cases. The new cases use a common pattern: set up state, execute the opcode or opcode family under test, then draw view 11 only if a condition observes the expected result. If the original interpreter disagrees with the expected behavior, the capture is missing the transient object and the comparison fails. - The expanded batch matched QEMU with 27 matches, 0 mismatches, and 0 errors.
- Newly QEMU-validated families include:
- condition
0x00(always_false); - variable
inc,dec,assignv,addn,addv,subn,subv; - indirect variable forms
0x09..0x0b; - multiplication/division forms
0xa5..0xa8, including low-product-byte and quotient behavior; - immediate and variable-selected flag actions
0x0c..0x11; - comparison predicates
0x02..0x06; - object position setter/getter
0x25/0x27; - object field
+0x24setter/getter0x36/0x39; - object field
+0x21setter/getter0x56/0x57.
- condition
- Added
tools/logic_opcode_evidence.pyand generateddocs/src/logic_opcode_evidence.md. The chapter records every action opcode0x00..0xaf, every known condition opcode0x00..0x12, and structural or invalid byte ranges with an evidence level. Rows are marked QEMU-validated only when a generated or existing QEMU harness has actually exercised the behavior; otherwise they remain source-backed or reserved/invalid.
2026-07-02: five follow-up logic-interpreter probe groups
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_fixturepython3 -B tools/logic_interpreter_probe.py --dos-prefix LL --output build/logic-interpreter-probes/batches/five_steps_001.json --boot-wait 5 --draw-wait 8 --stop-on-failurepython3 -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_drawpython3 -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_targetpython3 -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_truepython3 -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_sequencepython3 -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_varpython3 -B tools/logic_opcode_evidence.py
Documented result:
- Extended
tools/qemu_fixture.pysologic_resource()can encode custom logic message tables. The helper builds the offset table relative to the table base and XOR-encrypts the message text with the locally documentedAvis Durgankey, matching the loader’s decryption path. - Extended
tools/logic_interpreter_probe.pyso one fixture can patch multiple logic resources intoVOL.3, patch the correspondingLOGDIRentries, and filter runs with repeated--caseoptions. - A first attempt to run all 47 default logic probe cases in one snapshot disk filled the DOS image while copying the forty-fourth full SQ2 fixture directory. No interpreter behavior was observed in that run; it is a harness capacity limit caused by copying complete fixture directories.
- The requested steps were then run as five filtered QEMU batches:
- Step 1, logic call/load/resume smoke probes: 4 matches, 0 mismatches.
- Step 2, variable-backed object/resource probes: 4 matches, 0 mismatches.
- Step 3, object rectangle predicates: 4 matches, 0 mismatches.
- Step 4, string/message probes: 2 matches, 0 mismatches.
- Step 5, inventory/object-table marker probes: 6 matches, 0 mismatches.
- New QEMU evidence from these batches covers:
- actions
0x14,0x16, and0x17for logic loading and calls; - actions
0x91and0x92as executable resume-state opcodes that continue to subsequent bytecode in the smoke fixture; - variable-backed object/resource actions
0x26,0x2a,0x2c,0x30,0x52, and0x7b; - condition opcodes
0x0b,0x10,0x11, and0x12; - action
0x72, action0x75, condition0x0f, and condition0x0eusing custom messagesHELLO!,hello, andlook; - condition opcodes
0x09and0x0a, plus marker actions0x5c..0x61.
- actions
2026-07-03: object/view getter and bitfield follow-up probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_logic_interpreter_probepython3 -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_smokepython3 -B tools/logic_opcode_evidence.py
Documented result:
- Added five object/view follow-up cases to
tools/logic_interpreter_probe.py. - The QEMU batch matched with 5 matches, 0 mismatches, and 0 errors.
- Value probes now validate:
0x31..0x35reading view/object metadata after binding view 11 group 1 frame 1;0x37writing object byte+0x24from a variable, observed through getter0x39;0x45storing0xffwhen measuring distance between inactive objects;0x4dclearing object byte+0x21, observed through getter0x57.
- Added a separate
QEMU dispatch-smokeevidence level todocs/src/logic_opcode_evidence.md. The smoke case proves that selected bitfield/helper opcodes execute, consume operands, and return to subsequent bytecode under the original interpreter, but does not claim to expose every downstream state mutation. At the time of this pass, smoke rows included0x38,0x3a..0x3e,0x40..0x42,0x44,0x46..0x47,0x4c,0x4e, and0x58..0x59; later passes promoted several of those rows to behavior evidence.
2026-07-03: object state, random, and no-op runtime probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_logic_interpreter_probepython3 -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_drawpython3 -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_entrypython3 -B tools/logic_opcode_evidence.pypython3 -B tools/logic_opcode_evidence.py --check
Documented result:
- Added QEMU-visible logic probes for additional object-state and misc actions:
0x28adds positive variable-sourced deltas to object position fields, observed through getter0x27;0x82stores the bound when its low and high random bounds are equal;0x7f,0x9b, and0xafexecute and continue to following drawing bytecode in the original interpreter;0x93and0x94write object position fields, observed through getter0x27;0x24deactivates an active persistent object so only the following transient draw remains visible in the fixture.
- The first nine-case batch matched 8 cases and mismatched the initial
0x22hypothesis. The mismatch box wasx=20..39, y=76..80, exactly the footprint of the previously activated persistent object. This showed that action0x22clearing active/update bits does not immediately unlink an object that was already activated for the current draw. - Extended
tools/logic_interpreter_probe.pycomparison expectations so a case can include additional expected sprites. The corrected0x22case,clear_all_object_bits_keeps_current_draw_entry, expects both the old persistent object atx=20and the following transient object atx=50. - The corrected single-case rerun in
build/logic-interpreter-probes/batches/object_state_misc_002.jsonmatched with 1 match, 0 mismatches, and 0 errors. - Regenerated
docs/src/logic_opcode_evidence.md; actions0x22,0x24,0x28,0x7f,0x82,0x93,0x94,0x9b, and0xafare now recorded with QEMU-backed evidence.
2026-07-03: variable view load and object field +0x23 probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_logic_interpreter_probepython3 -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_flagpython3 -B tools/logic_opcode_evidence.pypython3 -B tools/logic_opcode_evidence.py --check
Documented result:
- Added a
preload_view_nooption totools/logic_interpreter_probe.pyso individual cases can start without the defaultload_view 11prelude. This was necessary for the0x1fprobe: if view 11 were preloaded, a following draw would not prove thatload_view_vardid the loading. - The five-case QEMU batch
build/logic-interpreter-probes/batches/load_view_field23_001.jsonmatched with 5 matches, 0 mismatches, and 0 errors. 0x1f(load_view_var) is now QEMU-validated: a fixture assigns variable 1 to view 11, executes0x1f, and then draws view 11 successfully without any earlier view preload.0x49(set_object_field_23_mode1) and0x4b(set_object_field_23_mode2) are QEMU-validated for the observable part of their setup contract: each clears its flag operand, and the generated logic draws only if that flag is observed clear.0x48(set_object_field_23_mode0) and0x4a(set_object_field_23_mode3) are recorded as QEMU dispatch-smoke evidence: the fixtures prove each opcode executes and returns to following draw bytecode, but they do not directly expose object byte+0x23.
2026-07-03: collision-skip clear-bit movement probe
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_qemu_fixture tests.test_object_movement_probepython3 -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_againpython3 -B tools/logic_opcode_evidence.pypython3 -B tools/logic_opcode_evidence.py --check
Documented result:
- Added
clear_object_bit_0200_action()totools/qemu_fixture.py, encoding action0x44with one fixed object operand. - Added filtered
--casesupport totools/object_movement_probe.pyso a single movement case can be rerun without rebuilding the whole movement corpus. - Added movement case
move_collision_clear_skip_bit_blocks_again: object 0 first sets collision-skip bit0x0200with0x43, then clears it with0x44, then moves toward object 1. QEMU matched the expected blocked result at(25,80), proving that0x44restores normal object-object collision testing after the bit was set. - Regenerated
docs/src/logic_opcode_evidence.md; action0x44is now recorded as QEMU-validated instead of dispatch-smoke.
2026-07-03: horizon-bit placement probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_logic_interpreter_probepython3 -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_clamppython3 -B tools/logic_opcode_evidence.pypython3 -B tools/logic_opcode_evidence.py --check
Documented result:
- Added three logic-interpreter QEMU cases around the horizon-like placement
clamp:
horizon_clamps_object_when_bit_clear:0x3fsets[0x012d] = 100; a reset object placed at baseline80clamps to baseline101;horizon_exempt_bit_keeps_object_above_horizon: after0x3dsets object bit0x0008, the same placement remains at baseline80;horizon_clear_exempt_bit_restores_clamp: after0x3dsets and0x3eclears bit0x0008, the placement clamps to baseline101again.
- The QEMU batch
build/logic-interpreter-probes/batches/horizon_bits_001.jsonmatched with 3 matches, 0 mismatches, and 0 errors. - Regenerated
docs/src/logic_opcode_evidence.md; actions0x3d,0x3e, and0x3fare now QEMU-validated instead of source-backed or dispatch-smoke.
2026-07-03: fixed-priority clear-bit probe
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/logic_interpreter_probe.py --dos-prefix LP --output build/logic-interpreter-probes/batches/fixed_priority_bit_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_fixed_priority_bit_uses_derived_priority- The first attempt failed under the restricted sandbox because QEMU could not
bind
127.0.0.1:5for VNC. The same command was rerun with elevated QEMU permission. python3 -B tools/logic_opcode_evidence.py
Documented result:
- Added logic-interpreter QEMU case
clear_fixed_priority_bit_uses_derived_priority. - The fixture draws a synthetic control-6 picture, sets object 10 to fixed
priority/control byte
5with action0x36, then clears object bit0x0004with action0x38before drawing. - QEMU matched the expected visible output: at baseline
80, placement derived priority7from Y and drew over the control-6 background. This validates the observable effect of0x38, not just its dispatch. - Regenerated
docs/src/logic_opcode_evidence.md; action0x38is now recorded as QEMU-validated instead of dispatch-smoke.
2026-07-03: clear motion mode with action 0x4e
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_object_movement_probe tests.test_qemu_fixturepython3 -B -m unittest tests.test_logic_interpreter_probepython3 -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_motionpython3 -B tools/logic_opcode_evidence.py
Documented result:
- Added
clear_object_field_22_and_global_action()totools/qemu_fixture.py, encoding action0x4ewith one fixed object operand. - Added movement case
clear_field_22_after_random_motion_stops_motion: object 0 is initialized at(60,80), random motion is started with action0x54, and action0x4eis executed immediately afterward. - QEMU matched the expected stationary result at
(60,80). This validates the visible object byte+0x22clearing effect of0x4e; the static side effect on global[0x0139]remains documented from disassembly rather than this capture. - Regenerated
docs/src/logic_opcode_evidence.md; action0x4eis now recorded as QEMU-validated instead of dispatch-smoke.
2026-07-03: control and rectangle object-bit probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_object_movement_probe tests.test_qemu_fixture- Exploratory batch:
python3 -B tools/object_movement_probe.py --dos-prefix MC --output build/object-movement-probes/batches/control_bit_0002_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_blocks --case move_control_1_set_bit_0002_reaches_target --case move_control_1_clear_bit_0002_blocks_again - Exploratory batch:
python3 -B tools/object_movement_probe.py --dos-prefix MR --output build/object-movement-probes/batches/control_bit_0002_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_reaches_target --case move_control_1_set_bit_0002_reaches_target --case move_control_1_clear_bit_0002_still_reaches_target --case move_rect_boundary_without_bit_0002_stops_at_edge --case move_rect_boundary_set_bit_0002_reaches_target --case move_rect_boundary_clear_bit_0002_stops_again - Matched rectangle-boundary batch:
python3 -B tools/object_movement_probe.py --dos-prefix MB --output build/object-movement-probes/batches/rect_bit_0002_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_rect_boundary_without_bit_0002_stops_at_edge --case move_rect_boundary_set_bit_0002_reaches_target --case move_rect_boundary_clear_bit_0002_stops_again - Exploratory full acceptance batch:
python3 -B tools/object_movement_probe.py --dos-prefix MX --output build/object-movement-probes/batches/control_bits_acceptance_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_blocks --case move_control_1_set_bit_0002_reaches_target --case move_control_1_clear_bit_0002_blocks_again --case move_rect_boundary_without_bit_0002_stops_at_edge --case move_rect_boundary_set_bit_0002_reaches_target --case move_rect_boundary_clear_bit_0002_stops_again --case move_control_2_set_bit_0100_blocks --case move_control_2_clear_bits_0900_reaches_target --case move_control_3_set_bit_0800_blocks --case move_control_3_clear_bits_0900_reaches_target - Matched control-class-1 hidden batch:
python3 -B tools/object_movement_probe.py --dos-prefix M1 --output build/object-movement-probes/batches/control_class_1_hidden_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_1_without_bit_0002_blocks --case move_control_1_set_bit_0002_still_hidden --case move_control_1_clear_bit_0002_still_hidden - Matched control-class-2/3 rejection batch:
python3 -B tools/object_movement_probe.py --dos-prefix M9 --output build/object-movement-probes/batches/control_bits_0900_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_control_2_set_bit_0100_blocks --case move_control_2_clear_bits_0900_reaches_target --case move_control_3_set_bit_0800_blocks --case move_control_3_clear_bits_0900_reaches_target python3 -B tools/logic_opcode_evidence.py
Documented result:
- Added fixture helpers for actions
0x40,0x41,0x42,0x58,0x59, and0x5a. - Added
picture_onlycomparison support totools/object_movement_probe.pyfor cases where the original engine leaves no visible object and the capture should equal the rendered picture alone. - The first control-class-1 probes used fixed priority/control
15and reached the target, exposing thatcode.object.control_acceptanceskips the scan when object byte+0x24 == 0x0f. - Repeating control-class-1 probes at fixed priority/control
14produced plain-picture captures whether bit0x0002was clear, set by0x58, or set then cleared by0x59. This is recorded as control-class/visibility evidence, not as the positive0x58movement oracle. - The positive
0x58/0x59oracle is rectangle-boundary behavior incode.motion.pre_mode_and_boundary_update: with rectangle bounds(30,70)..(60,90), countdown-gated movement from(20,80)toward(50,80)stops at(30,80)when bit0x0002is clear, reaches(50,80)after0x58, and stops at(30,80)again after0x59. - QEMU validates
0x40: setting bit0x0100leaves a priority-14 object visible at(20,80)on a full control-class-2 picture and prevents movement to(50,80). - QEMU validates
0x41: setting bit0x0800leaves a priority-14 object visible at(20,80)on a full control-class-3 picture and prevents movement to(50,80). - QEMU validates
0x42: clearing bits0x0100/0x0800after0x40or0x41restores movement to(50,80). - Added symbolic labels
code.object.control_acceptance,code.motion.pre_mode_and_boundary_update, andcode.motion.rectangle_boundary_check. - Regenerated
docs/src/logic_opcode_evidence.md; actions0x40,0x41,0x42,0x58, and0x59are now QEMU-validated instead of dispatch-smoke.
2026-07-03: static frame-timer and frame-mode analysis
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -c "import struct; data=open('build/cleanroom/AGI.decrypted.exe','rb').read(64); print(struct.unpack_from('<14H', data, 0))"python3 -B -c "import sys; sys.path.insert(0,'tools'); from disassemble_logic import AGIDATA, load_table; data=AGIDATA.read_bytes(); table=load_table(data,0x061d,0xb0); [print(f'{op:02x} handler={table[op].handler:04x} argc={table[op].argc} meta={table[op].meta:02x}') for op in [0x3a,0x3b,0x3c,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x58,0x59,0x5a]]"ndisasm -b 16 -o 0 build/cleanroom/AGI.decrypted.exe > build/cleanroom/AGI.decrypted.ndisasmdd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_6ac8.bin bs=1 skip=27848 count=570dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0400.bin bs=1 skip=1536 count=1700dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_4800.bin bs=1 skip=18944 count=1200ndisasm -b 16 -o 0x6ac8 build/cleanroom/slice_6ac8.binndisasm -b 16 -o 0x0400 build/cleanroom/slice_0400.binndisasm -b 16 -o 0x4800 build/cleanroom/slice_4800.binrg -n "call 0x48|call 0x4[0-9a-f]{3}|\\[di\\+0x20\\]|\\[di\\+0x23\\]|\\[di\\+0x1f\\]" build/cleanroom/AGI.decrypted.ndisasm
Documented result:
- Re-centered this pass on static disassembly after the user pointed out that the work had drifted too far toward trial-and-error QEMU probing. QEMU remains useful as a validation tool, but the behavior model in this section comes from the executable.
- Confirmed the MZ/header address convention for handler disassembly. The
action dispatch table in
SQ2/AGIDATA.OVLstores loaded-image offsets; the decrypted executable file stores the corresponding bytes at image offset+0x0200, because the executable header is 32 paragraphs. For example action0x5ais table/image0x7b4eand its bytes are at file offset0x7d4e. - Confirmed action table entries:
0x46handler0x6c97, one operand: clears object flag bit0x0020.0x47handler0x6cbc, one operand: sets object flag bit0x0020.0x48handler0x6b82, one operand: sets byte+0x23 = 0and bit0x0020.0x49handler0x6bae, two operands: sets byte+0x23 = 1, sets bits0x1030, stores operand 1 in byte+0x27, and clears that flag.0x4ahandler0x6beb, one operand: sets byte+0x23 = 3and bit0x0020.0x4bhandler0x6c17, two operands: sets byte+0x23 = 2, sets bits0x1030, stores operand 1 in byte+0x27, and clears that flag.0x4chandler0x6c54, two operands: readsvar[arg1]and copies the byte into both object bytes+0x1fand+0x20. This corrects the earlier wording that said+0x20was cleared.
- Labeled
code.object.frame_timer_updateat image0x0563. It scans object records from[0x096b]to[0x096d], selecting records whose flag word satisfies(flags & 0x0051) == 0x0051. If bit0x0020is set and byte+0x20is nonzero, it decrements+0x20; when the decrement reaches zero, it callscode.object.advance_frame_by_modeand reloads+0x20from+0x1f. - Labeled
code.object.advance_frame_by_modeat image0x48b3. If bit0x1000is set, the helper clears that bit and returns without changing the selected frame. Otherwise byte+0x23selects one of four frame behaviors: mode 0 increments and wraps; mode 1 increments toward the last frame and completes there; mode 2 decrements toward frame 0 and completes there, or completes immediately if already at frame 0; mode 3 decrements and wraps from frame 0 to the last frame. - Completion in frame modes 1 and 2 sets flag byte
+0x27, clears object bit0x0020, clears direction byte+0x21, and resets byte+0x23to zero. - Updated
docs/src/symbolic_labels.md,docs/src/logic_bytecode.md,docs/src/graphics_object_pipeline.md, anddocs/src/current_status.mdwith this static model.
2026-07-03: QEMU validation of frame-timer actions
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -m unittest tests.test_qemu_fixture tests.test_object_movement_probepython3 -B tools/logic_opcode_evidence.py --checkpython3 -B tools/object_movement_probe.py --dos-prefix MA --output build/object-movement-probes/batches/frame_timer_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case animation_interval_mode1_reaches_frame1 --case animation_clear_bit_0020_prevents_frame_advance --case animation_set_bit_0020_restores_frame_advance- The first QEMU attempt failed under the restricted sandbox because QEMU could
not bind
127.0.0.1:5for VNC. The same command was rerun with approved elevated permission forpython3 -B tools/object_movement_probe.py. python3 -B tools/logic_opcode_evidence.py
Documented result:
- Focused tests
tests.test_qemu_fixtureandtests.test_object_movement_probepassed, covering the newly added helper encodings and movement case registry. - QEMU batch
build/object-movement-probes/batches/frame_timer_001.jsonmatched with 3 matches, 0 mismatches, and 0 errors:animation_interval_mode1_reaches_frame1: action0x4cseeds the frame timer and action0x49starts mode 1; view 11/group 0 advances from frame 0 to frame 1.animation_clear_bit_0020_prevents_frame_advance: action0x46clears bit0x0020after setup, so the frame remains 0.animation_set_bit_0020_restores_frame_advance: action0x47sets bit0x0020after0x46, restoring the frame advance to frame 1.
- Promoted actions
0x46,0x47, and0x4cintools/logic_opcode_evidence.pyfrom QEMU dispatch-smoke to QEMU behavior evidence backed byobject_movement_probe: frame_timer_001, then regenerateddocs/src/logic_opcode_evidence.md. - Updated
docs/src/logic_bytecode.md,docs/src/graphics_object_pipeline.md, anddocs/src/compatibility_testing.mdwith the replay command and result.
2026-07-03: QEMU validation of remaining frame modes
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_48b3_49c8.bin bs=1 skip=19123 count=280dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_6b82_6ce0.bin bs=1 skip=28034 count=350ndisasm -b 16 -o 0x48b3 build/cleanroom/slice_48b3_49c8.binndisasm -b 16 -o 0x6b82 build/cleanroom/slice_6b82_6ce0.binpython3 -B -c "import sys; sys.path.insert(0,'tools'); from disassemble_logic import AGIDATA, load_table; data=AGIDATA.read_bytes(); table=load_table(data,0x061d,0xb0); [print(f'{op:02x} handler={table[op].handler:04x} argc={table[op].argc} meta={table[op].meta:02x}') for op in [0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c]]"python3 -B -m unittest tests.test_qemu_fixture tests.test_object_movement_probepython3 -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_frame1python3 -B tools/logic_opcode_evidence.py
Documented result:
- Re-read the frame-mode dispatcher and corrected the previous source note for
mode 2. The branch at
0x4934completes immediately only when the current frame is already 0; otherwise it decrements, and the shared completion path is reached only when the new frame becomes 0. - Added fixture helpers for action
0x48,0x4a,0x4b, conditionvar == immediate, and action0x32so looping frame modes can be stopped deterministically after the desired frame appears. - Added movement cases
animation_mode0_forward_loop_wraps_to_frame0,animation_mode2_backward_completion_reaches_frame0, andanimation_mode3_backward_loop_wraps_to_frame1. - Focused tests
tests.test_qemu_fixtureandtests.test_object_movement_probepassed with 37 tests. - QEMU batch
build/object-movement-probes/batches/frame_timer_modes_002.jsonmatched with 3 matches, 0 mismatches, and 0 errors:animation_mode0_forward_loop_wraps_to_frame0: action0x48mode 0 wraps view 11/group 0 from frame 1 to frame 0.animation_mode2_backward_completion_reaches_frame0: action0x4bmode 2 moves from frame 1 to frame 0 and stops.animation_mode3_backward_loop_wraps_to_frame1: action0x4amode 3 wraps backward from frame 0 to frame 1.
- Promoted actions
0x48and0x4afrom QEMU dispatch-smoke to behavior evidence, and extended0x4bbehavior evidence with the visible mode-2 completion probe. Regenerateddocs/src/logic_opcode_evidence.md.
2026-07-03: object update-list partition actions
Commands run from /Users/peter/ai/agi/reverse:
python3 -B -c "import sys; sys.path.insert(0,'tools'); from disassemble_logic import AGIDATA, load_table; data=AGIDATA.read_bytes(); table=load_table(data,0x061d,0xb0); [print(f'{op:02x} handler={table[op].handler:04x} argc={table[op].argc} meta={table[op].meta:02x}') for op in range(0x36,0x3d)]"rg -n "0x3a|0x3b|0x3c|clear_object_bit_0010|set_object_bit_0010|refresh_object_helper|0x0010|refresh" docs/src/logic_bytecode.md docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools testsndisasm -b 16 -o 0x6a30 -e 0x6c30 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x0440 -e 0x0640 build/cleanroom/AGI.decrypted.exepython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_logic_doc_coveragepython3 -B tools/logic_interpreter_probe.py --dos-prefix LR --output build/logic-interpreter-probes/batches/object_root_partition_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_0010_moves_object_behind_set_partition --case set_bit_0010_moves_object_over_clear_partitionpython3 -B tools/logic_interpreter_probe.py --dos-prefix LR --output build/logic-interpreter-probes/batches/object_root_partition_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_0010_moves_object_behind_set_partition --case set_bit_0010_moves_object_over_clear_partitionpython3 -B tools/logic_interpreter_probe.py --dos-prefix LR --output build/logic-interpreter-probes/batches/object_root_partition_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_0010_moves_object_behind_set_partition --case set_bit_0010_moves_object_over_clear_partitionpython3 -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_partitionpython3 -B tools/logic_opcode_evidence.py
Documented result:
- Re-read action handlers
0x3a..0x3cand renamed action0x3cfromrefresh_object_helpertorefresh_object_lists. The handler computes the object record address from its operand, but the called helpers do not receive that address; the observed action is a global update-list refresh. - Added symbolic labels for the update-list wrappers and the bit-
0x0010membership helpers:code.object.build_active_update_listat image0x6a26.code.object.build_inactive_partition_listat image0x6a3d.code.object.flush_update_lists_restoreat image0x6a54.code.object.rebuild_draw_update_listsat image0x6a8e.code.object.refresh_update_listsat image0x6aab.code.object.clear_root_16ff_membershipat image0x6b44.code.object.set_root_16ff_membershipat image0x6b62.
- Source model: bit
0x0010partitions active objects between root0x16ff((flags & 0x0051) == 0x0051) and root0x1703((flags & 0x0051) == 0x0041).0x3aclears the bit through helper0x6b44;0x3bsets it through helper0x6b62;0x3cflushes, rebuilds, draws, and refreshes both roots. - The first three QEMU batches were deliberately kept in the record as fixture
corrections:
_001placed two active objects at the same coordinates during activation; placement helper0x593acould adjust an object before the partition effect was isolated._002used0x93after activation; static re-read confirmed0x93calls placement helper0x593a._003used0x25after activation; the capture showed the object at both the old and new X positions because0x25rewrites both current and saved coordinates, so the restore pass no longer erases the old drawing.
- Final QEMU batch
build/logic-interpreter-probes/batches/object_root_partition_004.jsonmatched with 2 matches, 0 mismatches, and 0 errors after the expected image explicitly modeled the stale0x25drawing as setup:clear_bit_0010_moves_object_behind_set_partition: after0x3a, the frame-1 object is drawn behind the still-bit-set frame-0 object.set_bit_0010_moves_object_over_clear_partition: after0x3athen0x3b, the frame-1 object is drawn over a frame-0 object left in the clear partition.
- Promoted actions
0x3a,0x3b, and0x3cintools/logic_opcode_evidence.pyfrom QEMU dispatch-smoke to QEMU behavior evidence backed bylogic_interpreter_probe: object_root_partition_004, then regenerateddocs/src/logic_opcode_evidence.md.
2026-07-03: object bit 0x2000 and automatic direction group selection
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0563_0620.bin bs=1 skip=1891 count=190dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_497b_49d0.bin bs=1 skip=19323 count=100ndisasm -b 16 -o 0x0563 build/cleanroom/slice_0563_0620.binndisasm -b 16 -o 0x497b build/cleanroom/slice_497b_49d0.binpython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_logic_doc_coveragepython3 -B tools/logic_interpreter_probe.py --dos-prefix L2 --output build/logic-interpreter-probes/batches/object_bit_2000_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_allows_direction_group_selection --case set_bit_2000_suppresses_direction_group_selectionpython3 -B tools/logic_interpreter_probe.py --dos-prefix L2 --output build/logic-interpreter-probes/batches/object_bit_2000_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_allows_direction_group_selection --case set_bit_2000_suppresses_direction_group_selection
Documented result:
- Action handler
0x2dat image0x497bsets object bit0x2000; handler0x2eat image0x49a3clears that bit. code.object.frame_timer_updateat image0x0563tests bit0x2000at image0x0593. If the bit is set, it skips automatic direction-based group selection. If the bit is clear, it may index one of twoAGIDATA.OVLtables by object direction byte+0x21:data.object.group_for_direction_two_or_three_groupsat data0x08ddwhen object byte+0x0bis 2 or 3.data.object.group_for_direction_four_plus_groupsat data0x08e7when object byte+0x0bis at least 4.
- The helper only calls
code.object.select_group(0x3bb7) when object byte+0x01 == 1, the table target is not sentinel4, and the target differs from the current group byte+0x0a. - Initial QEMU batch
object_bit_2000_001used view 11 and a self-looping fixture. The first case mismatched because the persistent object still drew as group 0 frame 0. Direct comparison against rendered view-11 frames showed the original capture exactly matched group 0 frame 0, so the fixture was not exposing the per-cycle selection path. - The corrected fixture uses a guarded one-time initialization and a normal
0x00end action so the engine can advance later cycles without the logic script repainting or resetting the object. It also uses view 4, whose four groups exercise thedata.object.group_for_direction_four_plus_groupstable. - Final QEMU batch
object_bit_2000_002matched with 2 matches, 0 mismatches, and 0 errors:clear_bit_2000_allows_direction_group_selection: action0x2eleaves bit0x2000clear; direction6selects view 4 group 1.set_bit_2000_suppresses_direction_group_selection: action0x2dsets bit0x2000; the same direction leaves view 4 on group 0.
- Promoted actions
0x2dand0x2eintools/logic_opcode_evidence.pyto QEMU behavior evidence backed bylogic_interpreter_probe: object_bit_2000_002, then regenerateddocs/src/logic_opcode_evidence.md.
2026-07-03: expanded direction groups, scheduler order, and rectangle bounds
Commands run from /Users/peter/ai/agi/reverse:
xxd -g1 -l 32 -s 0x08dd SQ2/AGIDATA.OVLxxd -g1 -l 32 -s 0x08e7 SQ2/AGIDATA.OVLdd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0100_0270.bin bs=1 skip=768 count=368ndisasm -b 16 -o 0x0100 build/cleanroom/slice_0100_0270.bin- Local Python scan for near calls to
0x0563,0x0644,0x150a,0x293c,0x6a8e, and0x6aab. python3 -B -m unittest tests.test_logic_interpreter_probe tests.test_logic_doc_coveragepython3 -B tools/logic_interpreter_probe.py --dos-prefix L3 --output build/logic-interpreter-probes/batches/object_bit_2000_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_bit_2000_two_or_three_group_direction6_selects_group1 --case clear_bit_2000_two_or_three_group_direction5_is_sentinel --case clear_bit_2000_requires_field01_equal_onepython3 -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_forcedpython3 -B -m unittest tests.test_qemu_fixture tests.test_object_movement_probepython3 -B tools/object_movement_probe.py --dos-prefix RB --output build/object-movement-probes/batches/rect_bounds_clear_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case move_rect_boundary_clear_bounds_reaches_target
Documented result:
- Expanded the
0x2000direction/group probes:object_bit_2000_004matched with 4 matches, 0 mismatches, and 0 errors.- View 5 validates the two/three-group table at
AGIDATA.OVL:0x08dd: direction6selects group 1. - Direction
5in the same table maps to sentinel4, so the group remains unchanged. - A one-shot
+0x01 = 2does not permanently block selection. The firstcode.object.frame_timer_updatepass sees+0x01 != 1, thencode.motion.update_objectsdecrements the countdown; a later cycle sees+0x01 == 1and selects the direction group. - A per-cycle logic write that keeps
+0x01 = 2prevents the group change, confirming the exact gate.
- Re-read the top-level cycle at image
0x0150:0x0198callscode.motion.pre_mode_and_boundary_updateat image0x0644.0x01bdcallscode.logic.call_logic(0x12ae) with logic number 0.0x024bcallscode.object.frame_timer_update(0x0563) unless byte[0x1757]is nonzero.code.object.frame_timer_updatecallscode.motion.update_objects(0x150a) at image0x061e, then rebuilds/draws/refreshes the root0x16ffupdate list.
- Corrected symbolic labels for the pre-motion pass and rectangle helper:
code.motion.pre_mode_and_boundary_updateis image0x0644.code.motion.rectangle_boundary_checkis image0x06d9.
- Added QEMU movement case
move_rect_boundary_clear_bounds_reaches_target. Batchrect_bounds_clear_001matched with 1 match, 0 mismatches, and 0 errors, validating action0x5bby setting bounds with0x5a, clearing them with0x5b, and observing that the object reaches(50,80)instead of stopping at the old boundary. - Promoted actions
0x5aand0x5bintools/logic_opcode_evidence.pyto QEMU behavior evidence, then regenerateddocs/src/logic_opcode_evidence.md.
2026-07-03: resource lifecycle, text input, menu, and sound probes
Commands run from /Users/peter/ai/agi/reverse:
rg -n "overlay_picture_var_composes_extra_picture|load_logic_var_then_call_logic_draws|discard_view_allows_reload" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/srcsed -n '620,820p' tools/logic_interpreter_probe.pysed -n '1,180p' tools/qemu_snapshot.pypython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot tests.test_logic_doc_coveragepython3 -B tools/logic_interpreter_probe.py --dos-prefix RL --output build/logic-interpreter-probes/batches/resource_lifecycle_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case load_logic_var_then_call_logic_draws --case overlay_picture_var_composes_extra_picture --case discard_picture_var_allows_reload_and_overlay --case discard_view_allows_reload_and_draw --case discard_view_var_allows_reload_and_drawpython3 -B -m json.tool build/logic-interpreter-probes/batches/resource_lifecycle_002.jsonpython3 -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_drawpython3 -B tools/logic_interpreter_probe.py --dos-prefix TX --output build/logic-interpreter-probes/batches/text_input_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_message_then_ack_continues_to_draw --case display_message_var_then_ack_continues_to_draw --case display_message_configured_then_ack_continues_to_draw --case prompt_string_to_slot_accepts_typed_word --case prompt_number_to_var_accepts_digitspython3 -B -m json.tool build/logic-interpreter-probes/batches/text_input_001.jsonmagick build/logic-interpreter-probes/fixtures/prompt_string_to_slot_accepts_typed_word/qemu_capture.ppm build/logic-interpreter-probes/fixtures/prompt_string_to_slot_accepts_typed_word/qemu_capture.pngpython3 -B tools/logic_interpreter_probe.py --dos-prefix TI --output build/logic-interpreter-probes/batches/text_input_prompt_string_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_string_to_slot_accepts_typed_wordpython3 -B tools/logic_interpreter_probe.py --dos-prefix TN --output build/logic-interpreter-probes/batches/text_input_prompt_number_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_number_to_var_accepts_digitspython3 -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_digitspython3 -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_smokepython3 -B tools/logic_opcode_evidence.py
Documented result:
- Extended
tools/qemu_snapshot.pyso eachSnapshotFixtureCasecan requestpost_launch_keysandpost_launch_wait. Existing callers keep the default no-input behavior.tools/logic_interpreter_probe.pypasses those fields through from each logic case to the shared snapshot runner. - Extended
LogicInterpreterCaseand fixture generation to support additional synthetic picture resources and a separateexpected_picture_payloadfor comparison. This lets one fixture load/overlay picture 1 while rendering the expected final picture state as picture 0 plus the overlay payload. - Added resource lifecycle cases:
load_logic_var_then_call_logic_drawsvalidates0x15followed by0x16.overlay_picture_var_composes_extra_picturevalidates that0x1ccan overlay an already-loaded picture resource. The first QEMU run,resource_lifecycle_002, mismatched only on the overlay pixels because0x1cupdated logical picture state without a visible full-screen refresh. Adding0x1aafter0x1cmade the composed picture visible.discard_picture_var_allows_reload_and_overlayvalidates a discard/reload/overlay path for0x1b.discard_view_allows_reload_and_drawanddiscard_view_var_allows_reload_and_drawvalidate0x20and0x99before a reload with0x1e.
- Final lifecycle batch
resource_lifecycle_003matched with 5 matches, 0 mismatches, and 0 errors. - Added message-window/input cases:
display_message_then_ack_continues_to_drawfor0x65.display_message_var_then_ack_continues_to_drawfor0x66.display_message_configured_then_ack_continues_to_drawfor0x97.prompt_number_to_var_accepts_digitsfor0x76, typing42and checking the destination variable through a conditional draw.
- Trial case
prompt_string_to_slot_accepts_typed_wordfor0x73visibly displayedWORD?and accepted typedlook, but the editor remained active after Enter in the QEMU capture. It was removed from the default compatibility set until the exact completion/event path is isolated. - Final text/input batch
text_input_002matched with 4 matches, 0 mismatches, and 0 errors. - Added menu and sound smoke cases:
menu_setup_dispatch_smokeruns0x9c,0x9d,0x9e,0xa0, and0x9f, then draws.menu_flag_dispatch_smokesets flag0x0e, runs0xa1, then draws.sound_load_stop_dispatch_smokeruns0x62for sound 1, then0x64, then draws.
- Batch
menu_sound_001matched with 3 matches, 0 mismatches, and 0 errors. These are dispatch-smoke probes only; they do not claim full interactive menu selection, audio playback, or sound-completion flag semantics. - Regenerated
docs/src/logic_opcode_evidence.mdso the new rows are marked as QEMU-validated or QEMU dispatch-smoke as appropriate.
2026-07-03: string editor, text UI, and diagnostics probes
Commands run from /Users/peter/ai/agi/reverse:
rg -n "0x0da9|0x73|prompt_string|text_ui|diagnostics_system" docs/src toolsdd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0c20_10a0.bin bs=1 skip=3616 count=1152ndisasm -b 16 -o 0x0c20 build/cleanroom/slice_0c20_10a0.bindd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_4420_46c0.bin bs=1 skip=17952 count=672ndisasm -b 16 -o 0x4420 build/cleanroom/slice_4420_46c0.binxxd -g 1 -s 0x1060 -l 0x20 build/cleanroom/AGI.decrypted.exepython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshotpython3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/prompt_string_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_string_to_slot_returns_after_enter --case prompt_string_to_slot_stores_typed_wordpython3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/prompt_string_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case prompt_string_to_slot_returns_after_enter --case prompt_string_to_slot_stores_typed_wordpython3 -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_wordpython3 -B tools/logic_interpreter_probe.py --dos-prefix TU --output build/logic-interpreter-probes/batches/text_ui_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_formatted_message_then_ack_continues_to_draw --case display_formatted_message_var_then_ack_continues_to_draw --case display_message_configured_var_then_ack_continues_to_draw --case input_line_toggle_refresh_erase_dispatch_smoke --case text_rect_clear_dispatch_smoke --case close_text_window_state_dispatch_smokepython3 -B tools/logic_interpreter_probe.py --dos-prefix TU --output build/logic-interpreter-probes/batches/text_ui_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case display_formatted_message_then_ack_continues_to_draw --case display_formatted_message_var_then_ack_continues_to_draw --case display_message_configured_var_then_ack_continues_to_draw --case input_line_toggle_refresh_erase_dispatch_smoke --case text_rect_clear_dispatch_smoke --case close_text_window_state_dispatch_smokepython3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_clear_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_dispatch_smoke --case close_text_window_state_dispatch_smokepython3 -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_smokepython3 -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_smokepython3 -B tools/logic_opcode_evidence.py
Documented result:
- Re-read action
0x73around image0x0c44and the shared editor helper at image0x0da9. The handler clears fixed string slot0x020d + slot * 0x28, optionally positions the prompt with0x2b0d, shows the resolved message, calls the editor helper, then redraws or cleans up the prompt/status area. - Named
code.input.edit_stringat image0x0da9. The helper clamps the requested length to0x28, copies the destination string into a local edit buffer, displays it, waits throughcode.input.wait_event, and dispatches key values through the table at image/data0x0e64. - The observed key dispatch table bytes at file offset
0x1060map0x08to one-character backspace,0x03and0x18to clear-current-input,0x0dto accept by zero-terminating and copying the local buffer back to the destination, and0x1bto cancel without copying. - Re-read event helpers around image
0x4482..0x467f.0x45d7blocks until the event normalizer returns neither0x0000nor0xffff;0x4634maps observed type-1 confirm/editor events0x0101/0x0301to Enter and0x0201/0x0401to Escape. - Extended the shared QEMU snapshot runner to support a post-launch key delay,
a wait between typed text and named keys, and a separate list of named QEMU
sendkeynames. This lets fixtures type literal text and then sendretas a distinct key event. - Initial
prompt_string_001andprompt_string_002runs showed the prompt text still visible in the comparison capture. The disassembly already showed Enter should accept; inspecting the output indicated that the interpreter had advanced, but the text-plane pixels remained over the later validation draw. Adding0x1abefore the validation draw removed that false mismatch. - Final batch
prompt_string_003matched with 2 matches, 0 mismatches, and 0 errors. It validates both return-after-Enter and copying typedlookinto the destination string slot for0x73. text_ui_001failed on the same visible text-overlay issue for formatted messages. Adding a full picture refresh before the validation draw fixed the first four cases intext_ui_002.- The
text_rect_clear_dispatch_smokecase initially mismatched because the fixture compared against the normal picture after intentionally clearing text rows. Adding a refresh before the validation draw made the probe test handler return instead of the permanent display-side clear. - Final batch
text_ui_003matched with 6 matches, 0 mismatches, and 0 errors. It validates0x67,0x68, and0x98as formatted/configured message paths, and dispatch-smokes0x77,0x78,0x89,0x8a,0x69,0x9a, and0xa9. - Batch
diagnostics_system_001matched with 4 matches, 0 mismatches, and 0 errors. It validates message/ack/return behavior for0x87,0x88, and0x8d, and dispatch-smokes0x83,0x84,0x8e,0xaa,0xab,0xac,0xad,0xa3, and0xa4. - Updated
tools/logic_opcode_evidence.pyand regenerateddocs/src/logic_opcode_evidence.mdwith the new behavior and dispatch-smoke evidence rows.
2026-07-03: text/status configuration source pass and smoke probes
Commands run from /Users/peter/ai/agi/reverse:
git status --shortrg -n "0x6a|0x6b|0x6c|0x6d|0x6e|0x6f|0x70|0x71|0x74|0x79|0x77d5|0x78f0|0x3547|0x4c3d|0x38b4|status line|input prompt|map_key_event" docs/src tools testsdd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_3400_3a00.bin bs=1 skip=13824 count=1536dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_7600_7b00.bin bs=1 skip=30720 count=1280dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_4c00_4d40.bin bs=1 skip=19968 count=320dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0d60_0df0.bin bs=1 skip=3936 count=144ndisasm -b 16 -o 0x3400 build/cleanroom/slice_3400_3a00.binndisasm -b 16 -o 0x7600 build/cleanroom/slice_7600_7b00.binndisasm -b 16 -o 0x4c00 build/cleanroom/slice_4c00_4d40.binndisasm -b 16 -o 0x0d60 build/cleanroom/slice_0d60_0df0.binpython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshotpython3 -B tools/logic_interpreter_probe.py --dos-prefix TS --output build/logic-interpreter-probes/batches/text_status_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_attribute_mode_dispatch_smoke --case screen_shake_dispatch_smoke --case input_prompt_config_dispatch_smoke --case status_line_show_hide_dispatch_smoke --case key_event_mapping_dispatch_smokepython3 -B -m json.tool build/logic-interpreter-probes/batches/text_status_001.jsonpython3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/input_prompt_config_dispatch_smoke/qemu_capture.ppmpython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshotpython3 -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_smokepython3 -B tools/logic_opcode_evidence.py
Documented result:
- Re-read status/input helpers around image
0x34bd..0x38d7:code.text.redraw_status_lineat image0x34bdwraps a status redraw in text setup/cleanup helpers, tests word[0x05d9], clears the configured status row through0x2ba6, positions output with0x2b0d, displays text through0x2390, and restores saved text attributes through0x79c3.- Action
0x70at image0x3547sets word[0x05d9] = 1and calls the redraw helper. - Action
0x71at image0x355cclears word[0x05d9]and clears the row from[0x05db]. code.input.show_prompt_markerat image0x37f7andcode.input.erase_prompt_markerat image0x382egate on prompt marker byte[0x05d7]and marker-visible word[0x0fa2].- Action
0x6cat image0x38b4resolves a message and stores its first byte in[0x05d7]. code.input.redraw_input_lineat image0x38d7redraws the input-line area when word[0x05d3]is nonzero and display mode is not the special mode-2 path.
- Re-read text-attribute and status configuration handlers around image
0x76ca..0x7a7f:- Action
0x6asets byte[0x1757] = 1, derives attributes through0x77d5, calls overlay entry0x9803, then clears a text rectangle. - Action
0x6bcalls helper0x78cb, which clears[0x1757], recomputes attributes, calls overlay entry0x9806, redraws the status line, and redraws the input line. - Action
0x6dcallscode.text.set_attribute_pair(0x77d5), which stores derived values in[0x05d1],[0x05cd], and[0x05cf]. - Action
0x6ereads a count byte and performs display-shake work through display-mode-specific helpers or direct CRT-controller writes. - Action
0x6fstores operand 0 in[0x05dd], operand 0 plus0x15in[0x05df], operand 1 in[0x05d5], operand 2 in[0x05db], and derives display offset[0x1379]from operand 0. - Helpers
0x7989and0x79c3save and restore up to five triples of text attribute globals in the table rooted at0x1759, with count word[0x1777].
- Action
- Re-read action
0x79at image0x4c3d: it combines operand 0 and operand 1 into a little-endian key/event word, stores operand 2 as the mapped value, and inserts the pair into the first free four-byte slot in the table rooted at0x0145, scanning up to 39 slots. - Re-read action
0x74at image0x0d70: it copies up to0x28bytes from a pointer read fromDS:0x0c8f + operand1 * 2into fixed string slot0x020d + operand0 * 0x28. The local SQ2 sampled table remains zero-filled, so this action was not promoted dynamically in this pass. - Added five QEMU dispatch-smoke cases:
text_attribute_mode_dispatch_smokefor0x6d,0x6a, and0x6b.screen_shake_dispatch_smokefor a one-count0x6e.input_prompt_config_dispatch_smokefor0x6cand0x6f.status_line_show_hide_dispatch_smokefor0x70and0x71.key_event_mapping_dispatch_smokefor0x79.
- Batch
text_status_001matched the first two cases, then mismatchedinput_prompt_config_dispatch_smoke. The mismatch bbox covered the validation sprite area, and the capture showed the interpreter had returned; the first0x6foperand value1changed display offset state enough that the local expected renderer no longer aligned with the captured sprite. This is useful behavior evidence for a later dedicated0x6foffset probe, but it was too broad for a dispatch-smoke fixture. - Changed the smoke fixture to use first operand
0for0x6f, preserving the handler dispatch while avoiding the non-default display offset. - Final batch
text_status_002matched with 5 matches, 0 mismatches, and 0 errors. - Updated symbolic labels for the status/input/text-attribute helpers and
globals, promoted
0x6a..0x71and0x79to QEMU dispatch-smoke evidence, and regenerateddocs/src/logic_opcode_evidence.md.
2026-07-03: input offset, mapped-key, and string-table behavior probes
Commands run from /Users/peter/ai/agi/reverse:
rg -n "def compare_capture|expected_baseline_y|mismatch_bbox|0x1379|1379|set_input_line_config|key_event|0x79|map_key_event|input_prompt_config" tools docs/src testspython3 -B -m json.tool build/logic-interpreter-probes/batches/input_prompt_config_operand1_shift_demo.json- Local Python comparison of the recreated operand-1 capture against expected
baselines
70..90. dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0900_0a40.bin bs=1 skip=2816 count=320dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_4520_45c0.bin bs=1 skip=18208 count=160ndisasm -b 16 -o 0x0900 build/cleanroom/slice_0900_0a40.binndisasm -b 16 -o 0x4520 build/cleanroom/slice_4520_45c0.binpython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshotpython3 -B tools/logic_interpreter_probe.py --dos-prefix IK --output build/logic-interpreter-probes/batches/input_key_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_config_operand1_offsets_display_by_8 --case mapped_key_sets_status_bytexxd -g 1 -s 0x0c80 -l 0x80 SQ2/AGIDATA.OVLxxd -g 1 -s 0x0c80 -l 0x80 build/logic-interpreter-probes/fixtures/input_line_config_operand1_offsets_display_by_8/AGIDATA.OVLpython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshotpython3 -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_pointerpython3 -B tools/logic_opcode_evidence.py
Documented result:
- Quantified the earlier
0x6f(1, 0, 22)mismatch by comparing the recreated QEMU capture against expected baselines. The capture matched exactly at baseline88, while the script draw used baseline80. This confirms that in the observed display mode the first0x6foperand contributes an eight-logical-row visible offset, consistent with the static assignment[0x1379] = arg0 << 3. - Added behavior case
input_line_config_operand1_offsets_display_by_8, which runs0x6f(1, 0, 22), refreshes with0x1a, draws at script baseline80, and expects the capture at baseline88. Batchinput_key_behaviour_001matched this case. - Re-read condition
0x0dat image0x09beand event mapping helper0x4566. Condition0x0dcalls0x459edirectly and does not use the script mapping table. The top-level input helper path calls0x4566, and when a type-1 event value matches a slot rooted at0x0145, helper0x4566changes the record type to3and replaces the value with the mapped value. - The type-3 event path in the input helper writes byte
[0x1218 + mapped_value] = 1. Condition0x0creads exactly this byte range, making it a clean observation point for a mapped-key behavior probe. - Added behavior case
mapped_key_sets_status_byte: one-time logic installs0x79('x', 0, 7), QEMU sends keyx, and per-cycle logic draws only when condition0x0c 7is true. Batchinput_key_behaviour_001matched this case, validating both action0x79and condition0x0cdynamically. - Inspected the original
SQ2/AGIDATA.OVLbytes at0x0c80..0x0cff; the pointer-table area around0x0c8fis zero-filled through0x0cd2, followed by static text. The same layout appears in generated fixtures. - Added fixture-local
AGIDATA.OVLpatch support totools/logic_interpreter_probe.py. - Added behavior case
set_string_from_table_copies_patched_pointer, which patches only the generated fixture: table entry 0 at0x0c8fpoints to0x0cc0, and0x0cc0containslook\0. The logic runs0x74into string slot 0, fills slot 1 from normal message textlook, then draws only if condition0x0ffinds the two slots equal. Batchinput_key_string_behaviour_001matched this case. - Promoted action
0x6f, action0x74, action0x79, and condition0x0cto QEMU behavior evidence intools/logic_opcode_evidence.py, then regenerateddocs/src/logic_opcode_evidence.md.
2026-07-03: inventory selection source pass and QEMU probes
Commands run from /Users/peter/ai/agi/reverse:
rg -n "inventory|show.obj|0x7c|post_launch|post_launch_key|dos_key" tools/logic_interpreter_probe.py tools/qemu_snapshot.py tests/test_logic_interpreter_probe.py docs/src/logic_bytecode.md docs/src/symbolic_labels.mdsed -n '1,220p' tools/qemu_snapshot.pysed -n '960,1085p' tools/logic_interpreter_probe.pysed -n '970,1030p' docs/src/logic_bytecode.mdndisasm -b 16 -o 0x3180 build/cleanroom/slice_3180_33c0.binndisasm -b 16 -o 0x9000 build/cleanroom/slice_9000_9480.binpython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot- Initial failed run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix IN --output build/logic-interpreter-probes/batches/inventory_selection_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case inventory_selection_enter_sets_var22 --case inventory_selection_escape_sets_ff --case inventory_selection_noninteractive_ack_returns python3 -B -m json.tool build/logic-interpreter-probes/batches/inventory_selection_001.jsonmagick build/logic-interpreter-probes/fixtures/inventory_selection_enter_sets_var22/qemu_capture.ppm build/logic-interpreter-probes/fixtures/inventory_selection_enter_sets_var22/qemu_capture.png- Corrected run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix IN --output build/logic-interpreter-probes/batches/inventory_selection_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case inventory_selection_enter_sets_var19 --case inventory_selection_escape_sets_var19_ff --case inventory_selection_noninteractive_ack_returns python3 -B tools/logic_opcode_evidence.py
Documented result:
- The inventory handler at
0x31d8enters a text/list mode, calls a helper now labeledcode.inventory.build_selection_list, restores text state, and returns. The list-building helper scans 3-byte entries rooted atdata.inventory.table_rootand includes only entries whose marker byte is0xff. - Each displayed carried item row is an 8-byte stack-local record containing
the original table index, item-name pointer, row, and column. The first item
is drawn in the left column; the next item is drawn in the right column after
computing
0x27 - strlen(name). - If no entries are carried, the helper inserts one fallback row pointing at the fixed “nothing” text.
- Flag 13 controls interactivity. When flag 13 is clear, the handler displays the noninteractive prompt, waits through the blocking input helper, and returns without storing a selection result. When flag 13 is set, it waits for events, uses the normalizer, handles type-1 Enter/Escape, and handles type-2 movement events through a selection-move/redraw helper.
- Enter stores the selected row’s original table index to absolute byte
DS:0x0022; Escape stores0xffto the same byte. Because the byte-variable array starts atDS:0x0009, this storage is exposed to logic bytecode as variable0x19. - The first QEMU probe incorrectly checked variable
0x22. It returned after Enter but did not draw the validation sprite, producing a mismatch over the sprite area. This was retained as evidence that the source address needed to be translated through the byte-variable base. - Corrected QEMU batch
inventory_selection_001matched all three cases:inventory_selection_enter_sets_var19,inventory_selection_escape_sets_var19_ff, andinventory_selection_noninteractive_ack_returns. - The menu source pass assigned stable labels for heading allocation, item allocation, setup finalization, enable/disable-by-id, and the interactive menu path. The observed source path enqueues type-3 events with selected menu item ids for enabled items, but deterministic menu interaction probes are still pending.
2026-07-03: menu, view-resource, system, file/log, and sound follow-up probes
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_93d0_9900.bin bs=1 skip=38352 count=1328dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_5e80_60e0.bin bs=1 skip=24704 count=608dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_2470_28f0.bin bs=1 skip=9840 count=1152dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_8280_8400.bin bs=1 skip=33920 count=384dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_51d0_5280.bin bs=1 skip=21456 count=176dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_8c80_8df0.bin bs=1 skip=36480 count=368dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_0e70_0ed0.bin bs=1 skip=4208 count=96dd if=build/cleanroom/AGI.decrypted.exe of=build/cleanroom/slice_6130_61d0.bin bs=1 skip=25392 count=160ndisasm -b 16 -o 0x93d0 build/cleanroom/slice_93d0_9900.binndisasm -b 16 -o 0x5e80 build/cleanroom/slice_5e80_60e0.binndisasm -b 16 -o 0x2470 build/cleanroom/slice_2470_28f0.binndisasm -b 16 -o 0x8280 build/cleanroom/slice_8280_8400.binndisasm -b 16 -o 0x51d0 build/cleanroom/slice_51d0_5280.binndisasm -b 16 -o 0x8c80 build/cleanroom/slice_8c80_8df0.binndisasm -b 16 -o 0x0e70 build/cleanroom/slice_0e70_0ed0.binndisasm -b 16 -o 0x6130 build/cleanroom/slice_6130_61d0.binrizin -q -c "/x 0x221d" -c q build/cleanroom/AGI.decrypted.exerizin -q -c "/x 0x833e221d" -c q build/cleanroom/AGI.decrypted.exepython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot- Initial menu run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix MN --output build/logic-interpreter-probes/batches/menu_interaction_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_interactive_enter_sets_status_byte - Final menu run: same command after adding a picture refresh before the validation draw.
python3 -B tools/logic_interpreter_probe.py --dos-prefix VW --output build/logic-interpreter-probes/batches/view_resource_display_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case view_resource_display_immediate_returns --case view_resource_display_var_returns- Initial system run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix SY --output build/logic-interpreter-probes/batches/system_dialog_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case signature_check_matching_message_returns --case restart_confirm_escape_continues_to_draw --case confirm_restart_like_escape_continues_to_draw --case joystick_calibration_no_joystick_returns --case display_mode_toggle_guarded_noop_continues --case trace_window_config_enable_dispatch_smoke - Final system run: same command after narrowing the trace case to the flag-clear gated path.
python3 -B tools/logic_interpreter_probe.py --dos-prefix FL --output build/logic-interpreter-probes/batches/file_log_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case log_file_append_dispatch_smoke --case save_game_escape_continues_to_draw --case restore_game_escape_continues_to_draw- Initial sound runs for
sound_start_clears_completion_flagand thensound_start_stop_dispatch_smoke, both without a preceding0x62load. - Final sound run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix SN --output build/logic-interpreter-probes/batches/sound_completion_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case sound_start_stop_dispatch_smoke python3 -B tools/logic_opcode_evidence.py
Documented result:
- Corrected the slice workflow reminder: focused slices use the documented image
offset plus the executable file-header adjustment (
+0x200) when reading bytes frombuild/cleanroom/AGI.decrypted.exe. code.menu.interact(0x93d1) draws the menu, waits throughcode.input.wait_event, normalizes the event, and calls0x46e8. For a type-1 Enter event on an enabled item, it calls0x44a9(3, item_id), cleans up the menu display, clears word[0x1d22], and returns.- The only observed direct references to menu request word
[0x1d22]are the setter in0xa1, the input/event caller check around image0x338b, and the cleanup clear insidecode.menu.interact. - Added
menu_interactive_enter_sets_status_byte: it creates a one-item menu with item id 7, sets flag0x0e, runs0xa1, sends Enter, and draws only when condition0x0c 7observes the enqueued type-3 menu event. The first run mismatched only because the menu text strip remained visible; adding0x1abefore the validation draw produced a 1/1 QEMU match. - Re-read the shared view-resource display helper
0x5edb. It loads the view resource with temporary[0x0f18] = 1, builds a temporary object-like record, optionally renders/caches a preview if memory allows, displays text derived from the view resource, restores any preview rectangle, and discards the resource when it was not already cached. - Added
view_resource_display_immediate_returnsandview_resource_display_var_returns; QEMU batchview_resource_display_001matched 2/2. - Added system/dialog cases for signature acceptance (
0x8f), restart confirmation Escape cancellation (0x80),0x86(0)confirmation Escape cancellation, no-joystick calibration return (0x8b), display-mode guarded no-op (0x8cwith variable 0 clear), and trace configuration/flag-clear gated0x95. QEMU batchsystem_dialog_001matched 6/6 after narrowing the trace case. - The initial enabled trace case drew a visible trace box and mismatched the normal graphics comparison. This confirms the source-backed enabled drawing path but is not a stable sprite-comparison fixture.
- Re-read
0x90and helper0x833f: the handler opens or createslogfile, seeks to the end, appends the room/input/message text, closes the handle, and returns. QEMU batchfile_log_001matchedlog_file_append_dispatch_smoke,save_game_escape_continues_to_draw, andrestore_game_escape_continues_to_draw. - Re-read
0x63: it stops prior sound, stores completion flag word[0x126a], clears that flag, locates the sound resource, then starts playback. Probes that ran0x63without first loading sound 1 did not reach the validation draw. Adding0x62(1)before0x63(1,77)and then0x64produced a 1/1 match insound_completion_001. - Promoted the newly matched opcodes in
tools/logic_opcode_evidence.pyand regenerateddocs/src/logic_opcode_evidence.md. Trace/log/sound are marked according to their current evidence scope rather than overclaiming deeper side effects.
2026-07-03: priority, diagnostics, menu edges, sound flag, and log file follow-up
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x731b -e 0x751b build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x72b5 -e 0x74b5 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x828f -e 0x848f build/cleanroom/AGI.decrypted.exepython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_qemu_snapshot- Attempted room-switch batch:
python3 -B tools/logic_interpreter_probe.py --dos-prefix RS --output build/logic-interpreter-probes/batches/room_priority_diag_sound_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_sets_room_and_previous_room --case switch_room_var_sets_room_and_previous_room --case priority_screen_enter_returns --case object_diagnostics_var_enter_returns --case sound_stop_sets_completion_flag - Corrected but still-failing room attempts using target-room-only and
target-logic-draw assertions, also under output
build/logic-interpreter-probes/batches/room_priority_diag_sound_001.json. - Stable priority/diagnostics/sound run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix PS --output build/logic-interpreter-probes/batches/priority_diag_sound_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case priority_screen_enter_returns --case object_diagnostics_var_enter_returns --case sound_stop_sets_completion_flag - Initial menu edge run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix ME --output build/logic-interpreter-probes/batches/menu_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_escape_exits_without_status_byte --case menu_disabled_item_enter_does_not_set_status_byte --case menu_enable_after_disable_allows_enter_status_byte --case menu_down_arrow_selects_second_item_status_byte - Focused down-arrow retry:
python3 -B tools/logic_interpreter_probe.py --dos-prefix MD --output build/logic-interpreter-probes/batches/menu_down_arrow_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_down_arrow_selects_second_item_status_byte - Stable menu edge run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix ME --output build/logic-interpreter-probes/batches/menu_edges_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case menu_escape_exits_without_status_byte --case menu_disabled_item_enter_does_not_set_status_byte --case menu_enable_after_disable_allows_enter_status_byte - Log file run and extraction:
python3 -B tools/logic_interpreter_probe.py --dos-prefix LF --output build/logic-interpreter-probes/batches/log_file_contents_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case log_file_append_dispatch_smoke qemu-img convert -f qcow2 -O raw build/logic-interpreter-probes/snapshot/logic_interpreter.qcow2 build/logic-interpreter-probes/snapshot/logic_interpreter_after_log.rawmdir -i build/logic-interpreter-probes/snapshot/logic_interpreter_after_log.raw@@32256 ::/LF00000mcopy -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.txtxxd -g 1 build/logic-interpreter-probes/fixtures/log_file_append_dispatch_smoke/logfile_from_qemu.txtpython3 -B tools/logic_opcode_evidence.py
Documented result:
- Reconfirmed
0x1792room-switch helper from disassembly. It stops sound, restores heap/list state, clears active/update state for each object record, stores the target in byte variable 0, copies the previous room byte into byte variable 1, clears selected flags/bytes, loads the destination logic, handles entry-boundary placement from byte variable 2, sets flag 5, and refreshes display/input state. - Three QEMU fixture shapes for
0x12/0x13were attempted and rejected as reusable evidence: directvar1 == 0previous-room assertion, target-roomvar0assertion, and target-logic draw after making the target logic self-contained. These actions remain source-backed until a fuller synthetic room-cycle fixture models the logic-0/current-room relationship. - Reconfirmed
0x1dat image0x731b: it sets word[0x1755], calls full refresh0x5546, waits for an event, refreshes again, then clears[0x1755]. QEMU casepriority_screen_enter_returnsmatched. - Reconfirmed
0x85at image0x72b5: it reads an object index from a variable operand, gathers object fields, formats them through template0x1713, and displays the result. QEMU caseobject_diagnostics_var_enter_returnsmatched. - QEMU case
sound_stop_sets_completion_flagmatched: after0x62(1)and0x63(1,77), action0x64sets flag 77 before the validation draw. This validates the configured completion-flag effect of the stop helper, while exact audio output and asynchronous playback lifetime remain source-backed. - QEMU batch
priority_diag_sound_001matched 3/3. - QEMU batch
menu_edges_002matched 3/3. Escape exits without setting status byte 7. Enter on disabled item 7 does not set status byte 7 before Escape exits. Disabling and then re-enabling item 7 restores Enter selection and status byte 7. - The attempted down-arrow menu navigation case did not reach status byte 8
even after increasing the delay between Down and Enter. It remains an
attempted-but-not-promoted QEMU fixture; arrow navigation is still
source-backed from the
code.menu.interactevent dispatch table. log_file_contents_001matched visually. Converting the post-run qcow2 image to raw and extractingLF00000\LOGFILEshowed bytes0a 0a 52 6f 6f 6d 20 30 0a 49 6e 70 75 74 20 6c 69 6e 65 3a 20 0a 4c 4f 47, which decodes as two leading newlines,Room 0,Input line:, andLOG.- Promoted
0x1d,0x64,0x85,0x90, and the tested0x9c..0xa0menu setup/toggle paths in the opcode evidence generator, regeneratedlogic_opcode_evidence.md, and updated symbolic labels for the newly touched routines/globals.
2026-07-03: room-switch re-entry and menu-direction source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x0150 -e 0x0350 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x4529 -e 0x4729 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x46e8 -e 0x48e8 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x93d1 -e 0x95d1 build/cleanroom/AGI.decrypted.exexxd -s 0x16b3 -l 0x60 -g 2 SQ2/AGIDATA.OVLxxd -s 0x0145 -l 0x80 -g 2 SQ2/AGIDATA.OVLndisasm -b 16 -o 0x10d0 -e 0x12d0 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x293c -e 0x2b3c build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x12ae -e 0x14ae build/cleanroom/AGI.decrypted.exepython3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow- Attempted room re-entry batch:
python3 -B tools/logic_interpreter_probe.py --dos-prefix RV --output build/logic-interpreter-probes/batches/room_reentry_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_sets_new_room_flag --case switch_room_var_sets_new_room_flag - Attempted corrected room re-entry batch with
0x92before0x12/0x13:python3 -B tools/logic_interpreter_probe.py --dos-prefix RV --output build/logic-interpreter-probes/batches/room_reentry_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_sets_new_room_flag --case switch_room_var_sets_new_room_flag - Non-stopping room retry:
python3 -B tools/logic_interpreter_probe.py --dos-prefix RV --output build/logic-interpreter-probes/batches/room_reentry_003.json --boot-wait 5 --draw-wait 8 --case switch_room_immediate_sets_new_room_flag --case switch_room_var_sets_new_room_flag - Attempted down-arrow key-map probe:
python3 -B tools/logic_interpreter_probe.py --dos-prefix DK --output build/logic-interpreter-probes/batches/down_key_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case mapped_down_arrow_sets_status_byte
Documented result:
- Re-read
code.engine.main_cycle(0x0150). Whencode.logic.call_logic(0)returns zero, the loop clears selected variables/flags and immediately calls logic 0 again. It does not run the frame-timer branch until the logic call returns nonzero. - Re-read
code.logic.interpret_main(0x293c). It starts execution at the current logic record’s resume pointer field[record+0x06]. Action0x00returns the currentSI, and an action handler that returns zero stops the interpreter withAX = 0. - Re-read
code.logic.call_logic(0x12ae). It saves the old current-logic record, locates or loads the requested logic, callscode.logic.interpret_main, frees a transiently loaded record when needed, restores the old current record, and returns the interpreter result. - Re-read
0x91/0x92:0x91writes the current bytecode pointer to[current_logic+0x06];0x92restores[current_logic+0x06]from the entry pointer[current_logic+0x04]. - A new synthetic room-switch fixture attempted to use a private init flag and
flag 5 as the validation condition after
0x12/0x13. It did not reach the validation draw. Adding0x92before the room-switch action also did not reach the draw. The failing cases were removed from the reusable probe list;0x12/0x13remain source-backed. - Re-read input/event path
0x4529..0x46e8. Helper0x467fdrains BIOS key events, maps raw key words through table0x16b3via helper0x46b6, and enqueues type-2 movement events throughcode.input.enqueue_event(0x44a9). The observed table maps0x4800,0x4900,0x4d00,0x5100,0x5000,0x4f00,0x4b00, and0x4700to movement codes1..8; code0x5000 -> 5is the source-backed down-arrow candidate. - Re-read
code.menu.interact(0x93d1). Type-1 Enter/Escape are handled separately; type-2 movement values dispatch through the table at0x9517. In that table, movement value 5 enters the branch at0x94da, which advances to the next item in the current menu’s item list. - A QEMU probe that mapped raw key word
0x5000through action0x79and sent monitor keydowndid not set the requested status byte. This is treated as an input-instrumentation gap rather than a semantic result; the down-arrow source table is still source-backed, not QEMU-validated. - Added symbolic labels for
code.logic.save_resume_ip_action,code.logic.restore_entry_ip_action,code.input.enqueue_event, anddata.input.menu_direction_event_map.
2026-07-03: real SQ2 logic-0 room dispatch pass
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/disassemble_logic.py 0python3 -B tools/disassemble_logic.py 0 | rg "action (12|13|14|15|16|17|91|92)"python3 -B tools/disassemble_logic.py 1 2 3 4python3 -B tools/disassemble_logic.py 1 2 3 4 5 6 7 8 9 10 | rg -n "^logic|^0000: if|cond 07 flag_set\\(#5\\)|then_start|action 00 end|call_logic_var\\(v0\\)|switch_room_like"python3 -B tools/disassemble_logic.py 99 100 101 103 104 105 | rg -n "^logic|code_len|switch_room_like|call_logic|call_logic_var|flag_set\\(#5\\)|save_logic_resume|restore_logic_entry| action 00 end"python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow- Attempted logic-0-shaped room dispatch batch:
python3 -B tools/logic_interpreter_probe.py --dos-prefix RD --output build/logic-interpreter-probes/batches/room_dispatch_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case switch_room_immediate_then_logic0_calls_current_room --case switch_room_var_then_logic0_calls_current_room xxd -g 1 -l 12 build/logic-interpreter-probes/fixtures/switch_room_immediate_then_logic0_calls_current_room/LOGDIRxxd -g 1 -s 0 -l 120 build/logic-interpreter-probes/fixtures/switch_room_immediate_then_logic0_calls_current_room/VOL.3
Documented result:
- SQ2 logic 0 is the global per-cycle script. Its early blocks handle boot/menu
setup, global status/menu/input handling, and special global transitions. The
room dispatch point is logic bytecode offset
0x053e, where it executes action0x17(call_logic_var(v0)). This calls the logic resource selected by byte variable 0. - Room logics sampled from resources 1 through 10 begin with an
if flag 5block. Those blocks perform room-entry setup: load views/sounds/pictures, prepare the picture, configure objects and ego, show the picture, and then end or fall through into per-cycle room behavior. Later blocks in those room logics handle room-local events and may callswitch_room_like. - Script variables now have clearer room roles:
- byte variable 0 at
DS:0x0009is the current room; - byte variable 1 at
DS:0x000ais the previous room; - byte variable 2 at
DS:0x000bis the room-entry boundary selector consumed and cleared bycode.room.switch_state.
- byte variable 0 at
- Built a new synthetic fixture that more closely copied the SQ2 pattern:
logic 0 switched rooms once, then used
call_logic_var(v0)to invoke a self-contained target room logic. The QEMU batch still mismatched with the same blank-screen signature as earlier room probes. The fixture’sLOGDIRandVOL.3bytes were inspected and showed the extra logic resource was patched at the expected directory offset, so this failure is not explained by a missing target logic resource. - Removed the failed synthetic room-dispatch cases from the reusable probe
registry. The source model is stronger, but
0x12/0x13remain source-backed until a fixture can reproduce the full runtime room lifecycle.
2026-07-03: room-switch re-entry QEMU validation
Commands run from /Users/peter/ai/agi/reverse:
rg -n "room|switch_room|call_logic_var|restore_logic|save_resume|logic0|case|def .*logic|class|Fixture|extra_logic" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.mdsed -n '1,260p' tools/logic_interpreter_probe.pysed -n '240,620p' tools/logic_interpreter_probe.pysed -n '620,980p' tools/logic_interpreter_probe.pysed -n '980,1360p' tools/logic_interpreter_probe.pysed -n '1360,1760p' tools/logic_interpreter_probe.pysed -n '1,260p' tests/test_logic_interpreter_probe.pypython3 -B -m unittest tests.test_logic_interpreter_probe- First attempted run, rejected because this harness does not expose a bare
--snapshotflag:python3 -B tools/logic_interpreter_probe.py --snapshot --case switch_room_reentry_dispatches_current_room --case switch_room_v_reentry_dispatches_current_room --dos-prefix RS --output build/logic-interpreter-probes/batches/room_switch_reentry_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure - Successful QEMU run:
python3 -B tools/logic_interpreter_probe.py --case switch_room_reentry_dispatches_current_room --case switch_room_v_reentry_dispatches_current_room --dos-prefix RS --output build/logic-interpreter-probes/batches/room_switch_reentry_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure python3 -B -m unittest discover -s testspython3 -B tools/logic_opcode_evidence.py
Documented result:
- Added two reusable logic-interpreter probe cases:
switch_room_reentry_dispatches_current_roomfor action0x12, andswitch_room_v_reentry_dispatches_current_roomfor action0x13. - The fixture shape is deliberately source-like. Logic 0 sets a private init
flag before the switch action. The switch action returns zero, so the current
interpreter invocation aborts and
code.engine.main_cycleimmediately calls logic 0 again. On the second pass, logic 0 skips the switch and callscall_logic_var(v0). The destination room logic checks flag 5 and performs its own picture/view load and validation draw. - QEMU
room_switch_reentry_001matched 2/2 with 0 mismatches. This promotes the visible room-switch re-entry/current-room dispatch shape from source-backed to QEMU-validated for both immediate and variable-selected room operands. - The earlier failed room-switch fixtures remain useful negative evidence about
fixture shape. A validation draw after
0x12/0x13, or a destination logic that relies on pre-switch picture/view state, does not model the original runtime lifecycle. - Full local compatibility suite passed after adding the cases:
Ran 99 tests in 18.114s, OK. - Regenerated
docs/src/logic_opcode_evidence.md; action rows0x12and0x13now cite the matched room-switch re-entry probes. Broader internal effects of helper0x1792, including object/resource reset, previous-room update, entry-boundary placement, and resource-event recording, remain source-backed unless separately probed.
2026-07-03: room entry-boundary selector QEMU validation
Commands run from /Users/peter/ai/agi/reverse:
- Oversized first disassembly attempt, useful for confirming the helper but too
broad for citation:
ndisasm -b 16 -o 0x1792 -e 0x1992 build/cleanroom/AGI.decrypted.exe - Focused corrected dump of room-switch helper
0x1792:ndisasm -b 16 -o 0x1792 -e 0x1992 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p' python3 -B -m unittest tests.test_logic_interpreter_probe- First attempted boundary batch:
python3 -B tools/logic_interpreter_probe.py --case switch_room_boundary_1_sets_object0_bottom_y --case switch_room_boundary_2_sets_object0_left_x --case switch_room_boundary_3_sets_object0_top_y --case switch_room_boundary_4_sets_object0_right_x --dos-prefix RB --output build/logic-interpreter-probes/batches/room_boundary_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure sed -n '1,220p' build/logic-interpreter-probes/batches/room_boundary_001.jsonpython3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/switch_room_boundary_1_sets_object0_bottom_y/qemu_capture.ppm- Generated diagnostic object-0 getter case under
build/logic-interpreter-probes/diagnostics/object0_getter.json. - Diagnostic getter run:
python3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/diagnostics/object0_getter.json --dos-prefix DG --output build/logic-interpreter-probes/batches/diagnostic_object0_getter_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure - Generated diagnostic marker-map case under
build/logic-interpreter-probes/diagnostics/boundary1_marker_map.json. - Diagnostic marker-map run:
python3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/diagnostics/boundary1_marker_map.json --dos-prefix DM --output build/logic-interpreter-probes/batches/diagnostic_boundary1_marker_map_001.json --boot-wait 5 --draw-wait 8 - Corrected boundary batch:
python3 -B tools/logic_interpreter_probe.py --case switch_room_boundary_1_sets_object0_bottom_y --case switch_room_boundary_2_sets_object0_left_x --case switch_room_boundary_3_sets_object0_top_y --case switch_room_boundary_4_sets_object0_right_x --dos-prefix RB --output build/logic-interpreter-probes/batches/room_boundary_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure python3 -B tools/logic_opcode_evidence.py
Documented result:
- Re-read helper
0x1792: after loading the destination logic, it reads byte variable 2 atDS:0x000b, dispatches selector values 1 through 4 through a small jump table, writes object 0 fields, clearsDS:0x000b, sets flag 5, and refreshes display/input state. - Added four reusable logic-interpreter probe cases:
switch_room_boundary_1_sets_object0_bottom_y,switch_room_boundary_2_sets_object0_left_x,switch_room_boundary_3_sets_object0_top_y, andswitch_room_boundary_4_sets_object0_right_x. - The first QEMU batch
room_boundary_001mismatched on the first case with an all-white capture. A diagnostic case proved action0x27can read object 0 fields after ordinary setup. The actual fixture issue was pre-switch setup: object 0 was being bound to view 11 without first loading view 11, so the fixture did not reach the intended room-switch path. - After changing the pre-switch setup to load view 11 before binding object 0,
QEMU batch
room_boundary_002matched 4/4 with 0 mismatches. - The matched cases validate bytecode-visible entry-boundary behavior for
action
0x12: selector 1 sets object 0 Y to0xa7; selector 2 sets object 0 X to0; selector 3 sets object 0 Y to0x25; selector 4 sets object 0 X to0xa0 - object_width. In the fixture, view 11 frame 0 has width 20, so selector 4 yields X140. All four selectors clear byte variable 2. - Regenerated
docs/src/logic_opcode_evidence.md; action row0x12now cites both the re-entry fixture and the boundary selector cases.
2026-07-03: room current/previous variable QEMU validation
Commands run from /Users/peter/ai/agi/reverse:
rg -n "room_reentry_logic0_code|room_boundary_logic0_code|room_switch_reentry_case|room_boundary_case|switch_room_boundary|switch_room_reentry" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src/current_status.md docs/src/logic_bytecode.md docs/src/compatibility_testing.mdsed -n '240,380p' tools/logic_interpreter_probe.pysed -n '780,850p' tools/logic_interpreter_probe.pypython3 -B -m unittest tests.test_logic_interpreter_probepython3 -B -m py_compile tools/logic_interpreter_probe.py- QEMU validation:
python3 -B tools/logic_interpreter_probe.py --case switch_room_sets_current_previous_and_clears_boundary --case switch_room_v_sets_current_previous_and_clears_boundary --dos-prefix RP --output build/logic-interpreter-probes/batches/room_previous_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure python3 -B tools/logic_opcode_evidence.py
Documented result:
- Added two reusable logic-interpreter probe cases:
switch_room_sets_current_previous_and_clears_boundaryfor action0x12, andswitch_room_v_sets_current_previous_and_clears_boundaryfor action0x13. - Each fixture writes a synthetic old room number into byte variable 0 before
switching to room 1 and writes invalid boundary selector 7 into byte variable
2. The switch helper should copy old
v0intov1, write destination room 1 intov0, and clearv2even though selector 7 is not a placement case. - The destination room logic validates these bytes with normal logic conditions before drawing a validation sprite.
- QEMU batch
room_previous_001matched 2/2 with 0 mismatches. This validates the byte-variable current/previous-room update for both immediate and variable-selected room-switch actions. - Regenerated
docs/src/logic_opcode_evidence.md; action rows0x12and0x13now cite the previous-room variable probes in addition to the existing room re-entry evidence.
2026-07-03: variable-room entry-boundary selector QEMU validation
Commands run from /Users/peter/ai/agi/reverse:
sed -n '240,390p' tools/logic_interpreter_probe.pysed -n '780,865p' tools/logic_interpreter_probe.pypython3 -B -m unittest tests.test_logic_interpreter_probepython3 -B -m py_compile tools/logic_interpreter_probe.py- QEMU validation:
python3 -B tools/logic_interpreter_probe.py --case switch_room_v_boundary_1_sets_object0_bottom_y --case switch_room_v_boundary_2_sets_object0_left_x --case switch_room_v_boundary_3_sets_object0_top_y --case switch_room_v_boundary_4_sets_object0_right_x --dos-prefix VB --output build/logic-interpreter-probes/batches/room_boundary_var_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure python3 -B tools/logic_opcode_evidence.py
Documented result:
- Parameterized
room_boundary_caseso it can use either the immediate0x12action or a caller-supplied variable-selected0x13action. - Added four reusable logic-interpreter probe cases:
switch_room_v_boundary_1_sets_object0_bottom_y,switch_room_v_boundary_2_sets_object0_left_x,switch_room_v_boundary_3_sets_object0_top_y, andswitch_room_v_boundary_4_sets_object0_right_x. - The variable-selected fixtures set variable 10 to destination room 1, set
byte variable 2 to selector 1 through 4, then execute
0x13(v10). The destination room logic reads object 0 with action0x27and validates the expected position plus cleared byte variable 2 before drawing. - QEMU batch
room_boundary_var_001matched 4/4 with 0 mismatches. This confirms that the variable-selected room-switch action shares the same bytecode-visible entry-boundary side effects as the immediate action: selector 1 sets object 0 Y to0xa7, selector 2 sets object 0 X to0, selector 3 sets object 0 Y to0x25, selector 4 sets object 0 X to0xa0 - object_width, and all four clearv2. - Regenerated
docs/src/logic_opcode_evidence.md; action row0x13now cites the variable-room boundary selector cases.
2026-07-03: room-switch persistent-object reset QEMU validation
Commands run from /Users/peter/ai/agi/reverse:
rg -n "expected_extra_sprites|setup_object_for_view11|activate|clear_all_object_bits|compose_frame_on_picture|extra_sprites|room_boundary_case|previous_room" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src/logic_bytecode.mdsed -n '390,470p' tools/logic_interpreter_probe.pysed -n '1810,1828p' tools/logic_interpreter_probe.pypython3 -B -m unittest tests.test_logic_interpreter_probepython3 -B -m py_compile tools/logic_interpreter_probe.py- QEMU validation:
python3 -B tools/logic_interpreter_probe.py --case switch_room_removes_preexisting_persistent_object --case switch_room_v_removes_preexisting_persistent_object --dos-prefix RO --output build/logic-interpreter-probes/batches/room_object_reset_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure python3 -B tools/logic_opcode_evidence.py
Documented result:
- Added reusable helper
room_pre_switch_logic0_codefor room-switch probes that need setup work before the switch action runs. - Added
room_pre_switch_object_reset_case, which loads and shows picture 0, loads view 11, binds object 10 to that view, places it at X 20 / baseline Y 80, activates it as a persistent object with action0x23, and then changes rooms. - Added two QEMU-backed cases:
switch_room_removes_preexisting_persistent_objectfor immediate action0x12, andswitch_room_v_removes_preexisting_persistent_objectfor variable-selected action0x13. - The destination room logic draws only a validation sprite. The local expected renderer therefore fails if the object activated before the switch survives into the destination room as an extra drawn sprite.
- QEMU batch
room_object_reset_001matched 2/2 with 0 mismatches. This validates that pre-switch active persistent-object draw state is not visibly carried into the destination room for either0x12or0x13. - This does not yet prove the contents of every object table field after the switch. It is an observable rendering compatibility fact; broader field reset details remain to be mapped from the disassembly or narrower bytecode probes.
2026-07-03: room-switch object and cache reset source correction
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,240p' docs/src/current_status.mdsed -n '1,220p' docs/src/symbolic_labels.mdrg -n "000017[0-9A-Fa-f]|000014[0-9A-Fa-f]|1792|1485|code\\.room\\.switch_state|reset_dynamic_state|room switch|room-switch" build/cleanroom/AGI.decrypted.ndisasm docs/src/*.md tools/*.py- Oversized exploratory disassembly reads, corrected by the focused reads
below:
ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x1485 -e 0x1685 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x7060 -e 0x7260 build/cleanroom/AGI.decrypted.exe
ndisasm -b 16 -o 0x10d0 -e 0x12d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x30c0 -e 0x32c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'ndisasm -b 16 -o 0x4470 -e 0x4670 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x4c00 -e 0x4e00 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x3920 -e 0x3b20 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x49c0 -e 0x4bc0 build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'ndisasm -b 16 -o 0x50a0 -e 0x52a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'sed -n '300,380p' docs/src/graphics_object_pipeline.mdrg -n "\\+0x1e|\\+0x1f|\\+0x20|0x0010|0x0040|0x0001|0xffbe|0x17b6|17B6|room-switch object|object reset" docs/src tools tests- Compact confirmation reads:
ndisasm -b 16 -o 0x175c -e 0x195c build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'ndisasm -b 16 -o 0x10d0 -e 0x12d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,55p'ndisasm -b 16 -o 0x3920 -e 0x3b20 build/cleanroom/AGI.decrypted.exe | sed -n '30,65p'ndisasm -b 16 -o 0x49c0 -e 0x4bc0 build/cleanroom/AGI.decrypted.exe | sed -n '1,35p'ndisasm -b 16 -o 0x50a0 -e 0x52a0 build/cleanroom/AGI.decrypted.exe | sed -n '15,42p'ndisasm -b 16 -o 0x4470 -e 0x4670 build/cleanroom/AGI.decrypted.exe | sed -n '8,28p'
Documented result:
- Corrected the room-switch object reset description. At
0x17b6..0x17e5,code.room.switch_stateiterates object records in0x2b-byte steps, clears bits0x0001and0x0040withAND 0xffbe, sets bit0x0010, clears pointer fields+0x10,+0x08, and+0x14, then stores byte1into+0x00,+0x01,+0x20,+0x1f, and+0x1e. - The previous prose saying
+0x1e,+0x1f, and+0x20are cleared was wrong. The instruction stream keepsAL = 1; the repeatedsub ah,ahclears only the high byte and does not changeAL. Those bytes are therefore seeded to1. In the object field map they are step size, frame-timer reload, and frame-timer current countdown. - Refined the room-switch cache reset model. Helper
0x10d0calls0x10f7,0x396d,0x50cc, and0x49dc. The logic helper0x10f7does not clear the root word[0x0977]; if the root is nonzero, it writes zero to the first logic cache record’s+0x00next-link field. This preserves the first logic cache record and unlinks later records. - The remaining cache helpers clear their roots directly: view-like cache root
[0x0ffa] = 0through0x396d, sound-like cache root[0x125a] = 0through0x50cc, and picture-like cache root/static record word[0x120e] = 0through0x49dc. - Added symbolic labels for
code.logic.truncate_cache_to_head,code.resource.reset_room_caches,code.view.clear_cache_root,code.picture.clear_cache_root,code.sound.clear_cache_root, andcode.input.reset_event_state, plus data labels for the view/picture/sound cache roots. - Updated
docs/src/logic_bytecode.md,docs/src/logic_resources.md,docs/src/current_status.md, anddocs/src/symbolic_labels.mdso the high-level room-switch model distinguishes QEMU-validated visible behavior from source-backed exact memory-field effects.
2026-07-03: resource-event recording and restore replay source pass
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' docs/src/current_status.mdrg -n "0x70b1|0x707c|0x706d|0x681c|resource.event|resource-event|1707|1709|0141|0143|code.restore.replay_resource_events|data.resource" docs/src tools testsgit status --shortndisasm -b 16 -o 0x7058 -e 0x7258 build/cleanroom/AGI.decrypted.exe | sed -n '1,130p'ndisasm -b 16 -o 0x705e -e 0x725e build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'ndisasm -b 16 -o 0x681c -e 0x6a1c build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'ndisasm -b 16 -o 0x716a -e 0x736a build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'ndisasm -b 16 -o 0x0000 -e 0x0200 build/cleanroom/AGI.decrypted.exe | rg "call 0x70b1|call 0x7060|call 0x706d|call 0x707c|call 0x712f|call 0x714c|call 0x681c"ndisasm -b 16 -o 0x113d -e 0x133d build/cleanroom/AGI.decrypted.exe | sed -n '1,75p'ndisasm -b 16 -o 0x39b1 -e 0x3bb1 build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'ndisasm -b 16 -o 0x39f7 -e 0x3bf7 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x4a16 -e 0x4c16 build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'ndisasm -b 16 -o 0x4a3b -e 0x4c3b build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'ndisasm -b 16 -o 0x3f0d -e 0x410d build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'ndisasm -b 16 -o 0x5126 -e 0x5326 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x2d52 -e 0x2f52 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x5ebf -e 0x60bf build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x1720 -e 0x1920 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'ndisasm -b 16 -o 0x78f0 -e 0x7af0 build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'rg -n "0x8e|0xab|0xac|load_view|prepare_picture|overlay_picture|discard_view|load_sound|resource event|event buffer|code.event|data.event" docs/src/logic_bytecode.md docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md docs/src/current_status.md docs/src/progress_log.md- Follow-up check for re-enable paths:
ndisasm -b 16 -o 0x2512 -e 0x2712 build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'ndisasm -b 16 -o 0x2753 -e 0x2953 build/cleanroom/AGI.decrypted.exe | sed -n '1,230p'ndisasm -b 16 -o 0x0000 -e 0x0200 build/cleanroom/AGI.decrypted.exe | rg "call 0x705e|call 0x706d|call 0x681c"
Documented result:
- Assigned stable labels for the resource-event helpers:
code.event.disable_recording,code.event.enable_recording,code.event.reset_pair_buffer,code.event.record_pair,code.event.prepare_replay_cursor,code.event.next_replay_pair, and the action handlers for opcodes0x8e,0xab, and0xac. - Refined the data labels for the replay log:
data.event.pair_capacity([0x0141]),data.event.pair_count([0x0143]),data.event.saved_pair_count([0x05e1]),data.event.pair_buffer_base([0x1707]),data.event.pair_buffer_write([0x1709]),data.event.pair_buffer_read([0x170b]),data.event.recording_enabled([0x170d]), anddata.event.pair_high_water([0x170f]). - The event log is a sequence of two-byte pairs
(kind, value). Capacity is stored as a pair count, while the allocated byte size iscapacity * 2.code.event.record_pairappends only if flag 7 is clear anddata.event.recording_enabledis nonzero. It reports error code0x0bwhen the write pointer reachesbase + capacity * 2. - Room switching calls
code.event.reset_pair_bufferand thencode.event.enable_recording, so each new room starts with a fresh event log. Restore replay callscode.event.disable_recordingbefore replaying saved events so the replayed operations do not append duplicate pairs. - Mapped restore event kinds from the dispatch table at
0x6915:0: load logic, then restore logic resume metadata through0x13a5;1: load/refresh view throughcode.view.load_resource;2: load picture throughcode.picture.load_resource;3: load sound throughcode.sound.load_resource;4: prepare/decode picture throughcode.picture.prepare;5: replay the transient-display-object packet;6: discard picture throughcode.picture.discard;7: discard view through0x3f0d;8: overlay picture throughcode.picture.overlay_prepare.
- Kind
5is a four-pair packet. Helper0x2d52records(5, 0), then records byte pairs from0x0eae..0x0eb3; replay reads those next three pairs back into the same globals before calling0x2d52. - Mapped event-producing resource paths:
0x14/0x15record kind0after loading logic through0x117d;0x1e/0x1frecord kind1only when creating a new cached view entry;0x18records kind2only when creating a new cached picture entry;0x62records kind3only when creating a new cached sound entry;0x19,0x1c,0x1b,0x20, and0x99record kinds4,8,6, and7through their shared helpers.
- The temporary view-resource display helper
0x5edb, used by actions0x81and0xa2, disables recording before its internal load/display sequence and re-enables recording before returning. If it loaded the view only for the display, it discards that view while recording is still disabled. This keeps temporary preview work out of the persistent restore model. - The restore action at
0x2512calls replay at0x681cand then continues through display/menu refresh helpers, but the checked caller slice does not callcode.event.enable_recording. A full call-site scan found only twocode.event.enable_recordingcalls: room switching at0x17a3and the temporary view-display cleanup at0x6024. Display-mode toggle action0x8calso calls replay at0x797fwithout an observed re-enable in that immediate path. Therefore the post-replay event-recording lifecycle remains an explicit open question; the docs no longer assume automatic re-enable after restore replay. - Updated
docs/src/logic_bytecode.mdwith the higher-level event-log model, and updateddocs/src/symbolic_labels.mdwith the new code/data labels.
2026-07-03: replay save-block correction and display-mode QEMU probe
Commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -nreads ofdocs/src/progress_log.md,docs/src/clean_room_executable_notes.md,docs/src/symbolic_labels.md,docs/src/logic_bytecode.md,docs/src/current_status.md,docs/src/compatibility_testing.md, anddocs/src/graphics_object_pipeline.mdndisasmslices around image offsets0x2512,0x2753,0x681c,0x794c,0x00c4, and0x821c- Pattern scans of
build/cleanroom/AGI.decrypted.exeandbuild/cleanroom/AGI.decrypted.ndisasmfor stores to[0x170d]and calls tocode.event.disable_recording,code.event.enable_recording, andcode.restore.replay_resource_events python3 -B -m unittest discover -s tests- QEMU monitor-driven display-mode replay probes using
build/logic-interpreter-probes/snapshot/logic_interpreter.qcow2, followed byinfo registers, memory reads, andscreendump python3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/display_mode_replay_skips_flag7_unrecorded_picture/manual_memory_probe.ppm
Important correction:
- The saved whole-file disassembly uses file offsets, while MZ code image
addresses are two hundredh bytes lower. For this executable,
file offset = image offset + 0x200. Earlier helper slices that did not account for that relationship were plausible-looking but pointed at the wrong bytes.
Save/restore dependency map:
- In the save action (
0x2753, file offset0x2953), helper0x28c6writes length-prefixed blocks. The first large state block is length0x05e1bytes starting atDS:0x0002, not a small block rooted at[0x05e1]. That range includesdata.event.pair_capacity([0x0141]) anddata.event.pair_count([0x0143]). - The active replay pair bytes are a later block whose length is
[0x0141] << 1and whose pointer isdata.event.pair_buffer_base([0x1707]). data.event.recording_enabled([0x170d]) is not part of those save blocks.- Restore action
0x2512reads the same block families through helper0x26b0, then callscode.restore.replay_resource_events. - Helper
0x1364serializes logic-cache resume metadata into[0x0985]as four-byte entries containing a logic resource byte and a resume offset, terminated by word0xffff. Helper0x13a5restores a loaded logic record’s resume pointer by matching the resource number and adding the saved offset to the loaded entry pointer.
Static recording-gate scan:
- Direct stores to
data.event.recording_enabledwere found only in the helper bodies:- file
0x7263/ image0x705e: clear to zero; - file
0x7272/ image0x706d: set to one.
- file
- Direct calls to the enable helper were found at file
0x19a3/ image0x17a3(room switch) and file0x6224/ image0x6024(temporary view-resource display helper). - Direct calls to the disable helper were found at file
0x60e3/ image0x5ee3(temporary view-resource display helper) and file0x6a2a/ image0x682a(restore/display-mode replay). - Direct calls to
code.restore.replay_resource_eventswere found at file0x287a/ image0x267a(restore success path) and file0x7b7f/ image0x797f(display-mode toggle action0x8c). - This scan did not show a direct re-enable inside replay or its immediate
restore/display-mode callers. The dynamic probe below proved recording was
enabled again by the time the following script action recorded a transient
object packet. A later source pass corrected this apparent open question:
the replay dispatch table hid the post-loop
call 0x706dat image0x6927.
Display-mode replay QEMU probe:
-
The fixture patched
AGIDATA.OVLwords0x112eand0x1130to zero and launched the game withSIERRA -p -c, so action0x8ccould pass its source guard and call replay. -
Runtime
info registersshowedDS = 0x16a5, so the data segment physical base was0x16a50. -
Memory reads after the fixture stopped:
[0x112e]at physical0x17b7e:00 00;[0x1130]at physical0x17b80:01 00, proving0x8ctoggled bit 0;- around
[0x0141]/[0x0143]: bytes00 32 00 08, meaning capacity0x32and active pair count8; [0x1707] = 0x4f33,[0x1709] = 0x4f43,[0x170b] = 0x4f3b,[0x170d] = 0x0001, and[0x170f] = 0x0008.
-
Pair buffer at physical
DS*16 + 0x4f33 = 0x1b983:00 01 02 00 04 00 01 0b 05 00 0b 00 00 32 50 ffDecoded as pairs:
(0,1),(2,0),(4,0),(1,11),(5,0),(11,0),(0,50),(80,255). -
The pair buffer proves the replay log includes the room-switch logic load, picture 0 load/prepare, view 11 load, and final transient object packet. It does not include picture 1 when that picture was drawn with flag 7 set or after
0xab/0xacrolled the pair count back. -
A fresh screenshot from the same paused VM still matched the earlier automated capture and visibly showed an alternating-row background:
sha256_rgb e0f5d9669c5d1ecc326a42b28c0b517d4cdc3d1770f53ce38b49a887e1ed5123. Comparing it against the picture-0-only expectation produced 13,473 mismatches with bbox(0,1,159,167), while comparing it against the picture-1-only expectation produced 13,466 mismatches with bbox(0,0,159,166). The downsampled rows alternate: even rows are nibble6, odd rows are nibble4.
Documentation and harness result:
- Added per-case launch-command support to the QEMU snapshot harness and mapped
the DOS monitor key name for
-, allowing logic fixtures to launch asSIERRA -p -c. - Added two display-mode replay fixtures:
display_mode_replay_skips_flag7_unrecorded_pictureanddisplay_mode_replay_uses_rolled_back_event_count. - The automated screenshot expectations now reflect the original engine’s observable behavior in this fixture: the background alternates rows from the recorded and unrecorded/rolled-back pictures. The replay-log semantics are documented from source plus memory inspection rather than inferred from the screenshot.
- The corrected QEMU batch
build/logic-interpreter-probes/batches/replay_visible_001.jsonmatched with 2 matches, 0 mismatches, and 0 errors.
2026-07-04: display-mode replay classified as CGA remapping artifact
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,280p' docs/src/current_status.mdrg -n "0x8c|display-mode|row-interleav|CGA|EGA|\\[0x1130\\]|0x1130|0x112e|0x2b28|0x5528|0x2b4f|0x681c|0x5685|0x9899" docs/src/logic_bytecode.md docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md docs/src/current_status.md tools testsrg -n "1130|112e|1365|1379|5685|9899|99b8|9be3|9916|794c|2b28|5528|2b4f|681c" build/cleanroom/AGI.decrypted.ndisasmndisasm -b 16 -o 0x00c4 -e 0x02c4 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x40a0 -e 0x42a0 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x794c -e 0x7b4c build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x2b20 -e 0x2d20 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x5520 -e 0x5720 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x9800 SQ2/EGA_GRAF.OVLndisasm -b 16 -o 0x9800 SQ2/CGA_GRAF.OVLndisasm -b 16 -o 0x9800 SQ2/VG_GRAF.OVLndisasm -b 16 -o 0x9800 SQ2/JR_GRAF.OVLxxd -g 1 -s 0x1d30 -l 0x90 SQ2/AGIDATA.OVL- local Python table parse of
SQ2/AGIDATA.OVLbytes at0x1d36 ndisasm -b 16 -o 0x4a80 -e 0x4c80 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x6440 -e 0x6640 build/cleanroom/AGI.decrypted.exe
Corrected interpretation:
- The row-interleaved display observed after the
0x8cQEMU replay probe is a CGA-style display remapping artifact. It is not evidence that the unrecorded or rolled-back picture survives the replay. - The command-line parser at image
0x00c4sets display mode word[0x1130]directly from single-letter switches:-cstores0,-rstores1,-estores3,-hstores2, and-vstores4. The same parser stores hardware selector[0x112e] = 0for-p,2for-t, and8for-s. - Action
0x8cat image0x794conly enters its rebuild path when[0x112e] == 0, byte variable 0 is nonzero, and[0x1130]is not2or3. Therefore the fixture that launchesSIERRA -p -cis intentionally forcing the hardware-0 CGA-style path; the full 16-color EGA target path is outside this handler’s active branch. - Picture command
0xf0callscode.display.map_visual_color_for_adapter(0x5685) before storing the visual draw value and masks. That mapper returns the input color unchanged for[0x112e] != 0, and also returns unchanged for modes2and3. It delegates to graphics-overlay entry0x9815only when[0x112e] == 0and the mode is not2or3. - In
SQ2/CGA_GRAF.OVL, entry0x9815is a color mapper, not a mode setup entry. It indexes a three-byte-per-color table atAGIDATA.OVL:0x1d36. When[0x1130] != 1, it returns table byte 0 duplicated intoALandAH; when[0x1130] == 1, it returns the following two-byte word. For AGI color6, the observed table bytes are08 0b 0e. - In
SQ2/EGA_GRAF.OVL, entry0x9815is instead a graphics-mode setup routine that sets BIOS mode0x0d, configures palette/register state, and stores video segment0xa000in[0x1371]. The EGA target path does not use the CGA color-mapping table for picture command0xf0. - Replay kind
4callscode.picture.prepare(0x4acf), which callscode.picture.decode_with_clear(0x6445). That entry fills the logical buffer with0x4f4fthroughcode.display.fill_buffer_word(0x5257) before decoding the picture command stream. Combined with the QEMU memory proof that the event log excludes picture 1, this rules out the old picture as the source of the row-interleaved background.
Documentation result:
- Updated the bytecode, graphics pipeline, compatibility, status, and symbolic label docs so the row-interleaved replay fixture is described as CGA-only adapter evidence rather than a full EGA compatibility requirement.
- Added symbolic labels for
code.display.map_visual_color_for_adapter,overlay.cga.map_visual_color_for_mode, anddata.display.cga_color_map.
2026-07-04: replay recording re-enable source correction
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,240p' docs/src/current_status.mdtail -80 docs/src/clean_room_executable_notes.mdgit status --short- An exploratory
rgcommand containing unescaped backticks was rejected by the shell before producing useful evidence; no interpretation was derived from that failed command. sed -n '4728,4905p' docs/src/clean_room_executable_notes.mdsed -n '60,95p' docs/src/symbolic_labels.mdsed -n '185,200p' docs/src/symbolic_labels.mdsed -n '236,270p' docs/src/runtime_model.mdndisasm -b 16 -o 0x681c -e 0x6a1c build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x705e -e 0x725e build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x2512 -e 0x2712 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x794c -e 0x7b4c build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x1364 -e 0x1564 build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'ndisasm -b 16 -o 0x1720 -e 0x1920 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x5ebf -e 0x60bf build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'rg -n "call 0x705e|call 0x706d|call 0x681c|mov word \\[0x170d\\]|\\[0x170d\\]" build/cleanroom/AGI.decrypted.ndisasmndisasm -b 16 -o 0x6927 -e 0x6b27 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'xxd -g 1 -s 0x6b10 -l 0x30 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x6904 -e 0x6b04 build/cleanroom/AGI.decrypted.exe | sed -n '1,45p'
Correction:
- The earlier static scan that failed to find a replay-time re-enable was a
disassembly-boundary false negative, not an engine behavior. In the linear
slice that starts before the event-kind dispatch table,
ndisasmtreats the bytes immediately after the table as data-like instructions. - The replay loop at
code.restore.replay_resource_events(0x681c) branches to image0x6927whencode.event.next_replay_pair(0x714c) returns zero. Disassembling at0x6927decodes bytese8 43 07ascall 0x706d, which iscode.event.enable_recording. - The event-kind dispatch table is at image
0x6915; the raw bytes around file offset0x6b10show the table words for handlers0x688e,0x689e,0x68ab,0x68b1,0x68b7,0x68bd,0x68f2,0x68f8, and0x68fe, followed bye8 43 07at file offset0x6b27/ image0x6927. - Therefore restore/display-mode replay disables recording only while replaying the saved pairs. After the pair stream ends, replay re-enables recording, then scans object records to restore saved flags, rebind view payloads, and refresh display/input state.
- The save-block correction remains unchanged:
data.event.recording_enabledis not saved as part of the length-prefixed state blocks. Restore establishes the runtime gate by replay control flow, not by reading a saved word.
Documentation result:
- Updated
logic_bytecode.md,graphics_object_pipeline.md,compatibility_testing.md,current_status.md, andsymbolic_labels.mdto remove the stale unresolved re-enable note. - Added symbolic labels for
table.restore.replay_event_dispatchandcode.restore.finish_replay_and_reenable_recording.
2026-07-04: raw-key predicate and focused edge-render probes
Commands run from /Users/peter/ai/agi/reverse:
sed -nreads ofPROGRESS.md,tools/logic_interpreter_probe.py,tests/test_logic_interpreter_probe.py,tools/logic_opcode_evidence.py,tools/object_overlay_probe.py,tests/test_object_overlay_probe.py,docs/src/logic_bytecode.md,docs/src/compatibility_testing.md,docs/src/graphics_object_pipeline.md, anddocs/src/runtime_model.mdrg -n "raw_key|0x0d|0D|key event|keyboard|status byte|condition 0x0d|key_event|last key" docs/src/logic_bytecode.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools/logic_interpreter_probe.pyrg -n "def load_cases|--case|args.case|case_id" tools/logic_interpreter_probe.pypython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_object_overlay_probepython3 -B tools/logic_interpreter_probe.py --dos-prefix RK --output build/logic-interpreter-probes/batches/raw_key_condition_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case raw_key_event_available_draws_after_typed_key- Sandboxed attempt:
python3 -B tools/object_overlay_probe.py --dos-prefix OC --output build/object-overlay-probes/batches/clip_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case left_clip_view11_priority15 --case top_clip_view11_priority15 - Escalated rerun of the same object-overlay command, after QEMU reported
Failed to bind socket: Operation not permittedfor its local VNC display. python3 -B tools/logic_opcode_evidence.py
Results:
- Added logic probe case
raw_key_event_available_draws_after_typed_key. It installs no0x79key mapping, sends plain keyx, and draws only when condition0x0dsucceeds. Batchraw_key_condition_001matched QEMU with 1 match, 0 mismatches, and 0 errors, promoting condition0x0d(raw_key_event_available) from source-backed to QEMU-validated. - Promoted actions
0x62(load_sound) and0x63(start_sound_with_flag) from dispatch-smoke to QEMU-validated opcode-level evidence because existing QEMU casesound_stop_sets_completion_flagloads sound 1, starts it with completion flag 77, stops it with0x64, and reaches the validation draw only after flag 77 is set. Actual audio playback and asynchronous timing remain partial. - Promoted actions
0xab(save_event_buffer_count) and0xac(restore_event_buffer_count) from dispatch-smoke to QEMU-validated replay-log evidence throughdisplay_mode_replay_uses_rolled_back_event_count. The automated capture and paired memory notes show the rolled-back picture is excluded from the active pair buffer used by replay. - Added
--case CASE_IDfiltering totools/object_overlay_probe.py, matching the existing logic-probe workflow for focused QEMU runs. - Added object overlay cases
left_clip_view11_priority15andtop_clip_view11_priority15. The first validates view 11 flush with the left edge at left0, baseline80; the second revalidates the top-edge placement adjustment where requested left20, baseline2matches local output at left18, baseline4. - Focused object overlay batch
clip_edges_001matched QEMU with 2 matches, 0 mismatches, and 0 errors after rerunning with permission for QEMU’s VNC bind. - Regenerated
docs/src/logic_opcode_evidence.mdfrom the local evidence generator.PROGRESS.mdnow counts 153 covered action opcodes (152QEMU-validated plus structural0x00), 23 partial action opcodes, and all 19 condition opcodes QEMU-validated. - Added implementation-facing state-machine summaries for resource lifecycle,
object drawing lifecycle, and motion/animation lifecycle to
docs/src/runtime_model.md.
2026-07-04: action 0x84 movement effect and text/input lifecycle model
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,380p' PROGRESS.mdrg -n "QEMU dispatch-smoke|source-backed|0x69|0x6a|0x6b|0x6c|0x6d|0x6e|0x70|0x71|0x77|0x78|0x83|0x84|0x89|0x8a|0x8e|0x95|0x96|0x9a|0xa3|0xa4|0xa9|0xaa|0xad" docs/src/logic_opcode_evidence.md PROGRESS.md docs/src/logic_bytecode.mdsed -nreads oftools/qemu_snapshot.py,tools/logic_interpreter_probe.py,tools/object_movement_probe.py,tools/qemu_fixture.py,tests/test_logic_interpreter_probe.py,tests/test_object_movement_probe.py, and the relevant docs sections.- Attempted
0xaaprobe:python3 -B tools/logic_interpreter_probe.py --dos-prefix SD --output build/logic-interpreter-probes/batches/save_description_copy_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case copy_save_description_to_string_slot_copies_buffer cat build/logic-interpreter-probes/batches/save_description_copy_001.jsonxxd -g 1 -s 0x0e60 -l 0x60 build/logic-interpreter-probes/fixtures/copy_save_description_to_string_slot_copies_buffer/AGIDATA.OVLndisasm -b 16 -o 0x2720 -e 0x2920 build/cleanroom/AGI.decrypted.exepython3 -B -m unittest tests.test_object_movement_probe tests.test_logic_interpreter_probe tests.test_qemu_fixturepython3 -B tools/logic_opcode_evidence.pypython3 -B tools/object_movement_probe.py --dos-prefix G84 --output build/object-movement-probes/batches/action_84_motion_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case action_84_after_random_motion_stops_motion
Results:
- Attempted to promote action
0xaa(copy_save_description_to_string_slot) with a fixture-local patch that putlookatAGIDATA.OVL:0x0e72, the source pointer used by the handler at image0x2726. The QEMU run did not reach the validation draw: the capture mismatched exactly where the expected view would have been, indicating the string comparison failed. The fixture file did containlookat offset0x0e72, so this attempt suggests runtime initialization or save-selector state controls that buffer. The case was removed from the reusable base-case registry and0xaaremains dispatch-smoke only. - Added helper
set_global_0139_and_clear_object0_field_22_action()for action0x84and object movement caseaction_84_after_random_motion_stops_motion. The fixture starts random motion on object 0, immediately executes0x84, and expects the object to remain at(60,80). - QEMU batch
action_84_motion_001matched with 1 match, 0 mismatches, and 0 errors. This promotes the object-0 motion-byte effect of action0x84to QEMU-validated; the global[0x0139] = 1side effect remains documented from source. - Updated
tools/logic_opcode_evidence.pyand regenerateddocs/src/logic_opcode_evidence.md, promoting0x84out of dispatch-smoke. - Added an implementation-facing text/input UI lifecycle state machine to
docs/src/runtime_model.md, tying input-line enable/disable, prompt/status configuration, modal text windows, alternate text mode, and event/edit loops to the current opcode evidence. - Updated
PROGRESS.md: logic action opcode coverage is now 154 of 176 at[x]level (153QEMU-validated plus structural0x00), with 22 partial action opcodes remaining.
2026-07-04: text rectangle clear behavior probes
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' PROGRESS.mdsed -n '240,380p' PROGRESS.mdrg -n "0x69|0x6a|0x6b|0x6c|0x6d|0x70|0x71|0x77|0x78|0x83|0x89|0x8a|0x8e|0x95|0x96|0x9a|0xa3|0xa4|0xa9|0xaa|0xad" docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md tools testssed -nreads oftools/logic_interpreter_probe.py,tools/qemu_fixture.py,tests/test_logic_interpreter_probe.py,docs/src/logic_bytecode.md, anddocs/src/runtime_model.md.ndisasm -b 16 -o 0x34bd -e 0x36bd build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x3726 -e 0x3926 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x76ca -e 0x78ca build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x78cb -e 0x7acb build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x2bc4 -e 0x2dc4 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'ndisasm -b 16 -o 0x2b78 -e 0x2d78 build/cleanroom/AGI.decrypted.exe | sed -n '1,80p'python3 -B -m unittest tests.test_logic_interpreter_probepython3 -B -m py_compile tools/logic_interpreter_probe.py- First attempted QEMU run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_rect_clear_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_rows_removes_formatted_text --case text_rect_clear_bounds_removes_formatted_text - Corrected QEMU runs:
python3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_rect_clear_behaviour_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_rows_removes_formatted_text --case text_rect_clear_bounds_removes_formatted_textandpython3 -B tools/logic_interpreter_probe.py --dos-prefix TC --output build/logic-interpreter-probes/batches/text_rect_clear_behaviour_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case text_rect_clear_rows_removes_formatted_text --case text_rect_clear_bounds_removes_formatted_text cat build/logic-interpreter-probes/batches/text_rect_clear_behaviour_001.jsoncat build/logic-interpreter-probes/batches/text_rect_clear_behaviour_002.jsonpython3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/text_rect_clear_bounds_removes_formatted_text/qemu_capture.ppm- Local one-off PPM measurement of black pixel ranges in rows 60..75 of
build/logic-interpreter-probes/fixtures/text_rect_clear_bounds_removes_formatted_text/qemu_capture.ppm. python3 -B tools/logic_opcode_evidence.py
Results:
- Added
LogicInterpreterCase.expected_visual_rectsto model display-surface effects that are not picture-resource mutations. The comparator applies these rectangles to the low visual nibble before composing any expected view cels, preserving the priority/control nibble. - Added QEMU cases
text_rect_clear_rows_removes_formatted_textandtext_rect_clear_bounds_removes_formatted_text. Each displays formatted message text, accepts Enter, runs the clear action, and compares the capture without using0x1ato repaint the picture. - The first row-clear attempt mismatched because the expected screen assumed
the original white picture remained. The actual capture had a black band at
logical Y 40..55, proving that
0x69(5, 6, 0)clears the visible display surface rather than restoring picture pixels. - After adding an expected black rectangle,
text_rect_clear_behaviour_002matched the0x69case but mismatched the bounded0x9acase. Measuring the capture showed that0x9a(8, 5, 8, 20, 0)clears logical X 20..83/Y 64..71. This validates the EGA target’s text grid as four logical pixels per text column and eight logical pixels per text row. - Final QEMU batch
text_rect_clear_behaviour_003matched with 2 matches, 0 mismatches, and 0 errors. Actions0x69(clear_text_rect) and0x9a(clear_text_rect_bounds) were promoted from dispatch-smoke to QEMU-validated behavior coverage. - Added symbolic labels
code.text.clear_rows(0x2b78) andcode.text.clear_bounds(0x2bc4), updated the opcode/runtime docs, and regenerateddocs/src/logic_opcode_evidence.md. - Updated
PROGRESS.md: logic action opcode coverage is now 156 of 176 at[x]level (155QEMU-validated plus structural0x00), with 20 partial action opcodes remaining.
2026-07-04: status/input single-row clear behavior probes
Commands run from /Users/peter/ai/agi/reverse:
sed -nreads ofPROGRESS.md,docs/src/progress_log.md,docs/src/logic_bytecode.md,docs/src/runtime_model.md,docs/src/logic_opcode_evidence.md,docs/src/compatibility_testing.md,docs/src/symbolic_labels.md,tools/logic_interpreter_probe.py,tools/logic_opcode_evidence.py, andtests/test_logic_interpreter_probe.py.rg -n "0x70|0x71|0x77|disable_input|show_status|hide_status|status_line|input_line" docs/src/clean_room_executable_notes.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/logic_opcode_evidence.mdndisasm -b 16 -o 0x2ba6 -e 0x2da6 build/cleanroom/AGI.decrypted.exe | sed -n '1,60p'python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectanglespython3 -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_rowpython3 -B tools/logic_opcode_evidence.py
Results:
- Re-read
code.text.clear_rowat image0x2ba6. The helper pushes[bp+0xa],[bp+0x8], and[bp+0x8], then callscode.text.clear_rows(0x2b78). This makes it a single-row wrapper: top row and bottom row are identical, and the second argument is the clear attribute. - Added QEMU case
input_line_disable_clears_configured_row. The fixture displays formatted text on row 5, acknowledges it, runs0x6f(0, 5, 22)to set the input-row global[0x05d5], then runs0x77. The original-engine capture matches only when logical Y 40..47 is modeled as cleared to visual color 0 before the final object draw. - Added QEMU case
status_line_hide_clears_configured_row. The fixture displays formatted text on row 5, acknowledges it, runs0x6f(0, 0, 5)to set the status-row global[0x05db], then runs0x71. The capture likewise matches with logical Y 40..47 cleared to visual color 0. - QEMU batch
text_hide_clear_behaviour_001matched with 2 matches, 0 mismatches, and 0 errors. Actions0x71(hide_status_line_like) and0x77(disable_input_line_like) are now behavior-level QEMU-validated for the normal EGA display path. - Added symbolic label
code.text.clear_row(0x2ba6), updated the opcode/runtime/compatibility docs, and regenerateddocs/src/logic_opcode_evidence.md. - Updated
PROGRESS.md: logic action opcode coverage is now 158 of 176 at[x]level (157QEMU-validated plus structural0x00), with 18 partial action opcodes remaining.
2026-07-04: input-line enable and alternate text-attribute surface probes
Commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -nreads ofPROGRESS.md,docs/src/logic_bytecode.md,docs/src/runtime_model.md,docs/src/compatibility_testing.md,docs/src/symbolic_labels.md,docs/src/clean_room_executable_notes.md,tools/logic_interpreter_probe.py,tools/logic_opcode_evidence.py, andtests/test_logic_interpreter_probe.py.rg -n "0x70|0x78|0x89|0x8a|0x6c|0x6d|0xa3|0xa4|0x0d0f|0x05d3|0x05d7|0x05d9|0x0ff8|0x0fa4|0x0fce|0x38d7|0x37a5|0x3652|0x34bd" docs/src/clean_room_executable_notes.md docs/src/logic_bytecode.md docs/src/runtime_model.md tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.pyndisasm -b 16 -o 0x34bd -e 0x36bd build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'ndisasm -b 16 -o 0x3726 -e 0x3926 build/cleanroom/AGI.decrypted.exe | sed -n '1,170p'ndisasm -b 16 -o 0x38b4 -e 0x3ab4 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'ndisasm -b 16 -o 0x76ca -e 0x78ca build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'ndisasm -b 16 -o 0x78cb -e 0x7acb build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x77d5 -e 0x79d5 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectangles- First attempted QEMU run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix TE --output build/logic-interpreter-probes/batches/text_enable_attr_behaviour_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_enable_clears_configured_row --case text_attribute_enable_clears_visible_surface - Corrected QEMU run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix TE --output build/logic-interpreter-probes/batches/text_enable_attr_behaviour_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_enable_clears_configured_row --case text_attribute_enable_clears_visible_surface cat build/logic-interpreter-probes/batches/text_enable_attr_behaviour_001.jsonpython3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/text_attribute_enable_clears_visible_surface/qemu_capture.ppmpython3 -B tools/logic_opcode_evidence.py
Results:
- Re-read action
0x78at image0x3898and helpercode.input.redraw_input_lineat image0x38d7. The action sets word[0x05d3] = 1; in the normal non-display-mode-2 path, the helper erases any visible prompt marker, callscode.text.clear_row(0x2ba6) for row[0x05d5]with attribute[0x05cf], positions the cursor at that row, and writes fixed string slot 0, the visible input buffer, and the prompt marker. - Added QEMU case
input_line_enable_clears_configured_row. The fixture sets the prompt marker to an empty message, displays formatted text on row 5, acknowledges it, runs0x6f(0, 5, 22), and then runs0x78. The capture matches only when logical Y 40..47 is cleared to visual color 0 before the final object draw. - Re-read action
0x6aat image0x76ca. The handler erases the prompt marker, sets byte[0x1757] = 1, derives text attributes throughcode.text.set_attribute_pair(0x77d5), calls overlay entry0x9803, then callscode.text.clear_rows(0x2b78) for rows 0..24. - Added QEMU case
text_attribute_enable_clears_visible_surface. The first expected model composed the usual transient-object validation draw after0x6a, but QEMU batchtext_enable_attr_behaviour_001mismatched only in the object area. Inspecting the capture showed a single black color across the screen, so the visible surface clear was correct and the object composition expectation was wrong for this active alternate text mode. - Updated the probe helper with an explicit
compare_viewswitch and changed the0x6acase to compare only the visible surface. QEMU batchtext_enable_attr_behaviour_002then matched with 2 matches, 0 mismatches, and 0 errors. - Actions
0x78(enable_input_line_like) and0x6a(enable_text_attr_mode_1757) are now behavior-level QEMU-validated for the observed EGA paths. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, and symbolic-label docs. - Updated
PROGRESS.md: logic action opcode coverage is now 160 of 176 at[x]level (159QEMU-validated plus structural0x00), with 16 partial action opcodes remaining.
2026-07-04: prompt-marker suppression and text-attribute exit probes
Commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -nreads ofPROGRESS.md,tools/logic_interpreter_probe.py,tests/test_logic_interpreter_probe.py,tools/logic_opcode_evidence.py,docs/src/logic_bytecode.md,docs/src/compatibility_testing.md,docs/src/runtime_model.md,docs/src/symbolic_labels.md, and this notes file.rg -n "0xfce|0FA4|0ff8|parse_string|set_string|input buffer|prompt_marker|0x6C|0x89|0x8A" tools/logic_interpreter_probe.py docs/src/logic_bytecode.md docs/src/clean_room_executable_notes.md docs/src/runtime_model.md tests/test_logic_interpreter_probe.pyndisasm -b 16 -o 0x38b4 -e 0x39b4 build/cleanroom/AGI.decrypted.exe | sed -n '1,80p'ndisasm -b 16 -o 0x37f7 -e 0x38b4 build/cleanroom/AGI.decrypted.exe | sed -n '1,95p'python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectanglespython3 -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_markerpython3 -B tools/logic_opcode_evidence.py
Results:
- Added QEMU case
text_attribute_disable_restores_picture_draw. The fixture runs0x6a, then0x6b, then refreshes the picture and draws the validation object. The original-engine capture matched the normal composed object view, validating that0x6bleaves the alternate text-attribute mode and restores ordinary picture/object drawing. - Added QEMU case
input_prompt_empty_message_suppresses_marker. The fixture first runs0x6cwith a nonempty message, displays and acknowledges text on row 5, then runs0x6cwith an empty message before0x6f(0, 5, 22)and0x78. The capture matches only when the input row is black with no prompt marker glyph, validating the source-backed behavior that0x6cstores the first byte of the resolved message and that byte zero suppresses marker drawing. - QEMU batch
text_prompt_attr_behaviour_001matched with 2 matches, 0 mismatches, and 0 errors. - Actions
0x6b(disable_text_attr_mode_1757) and0x6c(set_input_prompt_char) are now behavior-level QEMU-validated for the focused visible effects above. Nonempty prompt-marker glyph shape remains a text-rendering detail not yet modeled. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, and symbolic-label docs. - Updated
PROGRESS.md: logic action opcode coverage is now 162 of 176 at[x]level (161QEMU-validated plus structural0x00), with 14 partial action opcodes remaining.
2026-07-04: text-attribute pair behavior probe
Commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -nreads ofPROGRESS.md,tools/logic_interpreter_probe.py,tests/test_logic_interpreter_probe.py,tools/logic_opcode_evidence.py,docs/src/logic_bytecode.md,docs/src/runtime_model.md,docs/src/compatibility_testing.md,docs/src/symbolic_labels.md, and this notes file.rg -n "0x77d5|0x7803|0x78a1|0x78ad|set_attribute_pair|0x6d|0x6D" docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md docs/src/logic_bytecode.md docs/src/runtime_model.mdndisasm -b 16 -o 0x77d5 -e 0x79d5 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x76ca -e 0x78ca build/cleanroom/AGI.decrypted.exepython3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectanglespython3 -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_colorpython3 -B tools/logic_opcode_evidence.py
Results:
- Re-read action
0x6dat image0x77af. It reads two immediate operands and callscode.text.set_attribute_pair(0x77d5). - Re-read
code.text.set_attribute_pairat image0x77d5. It stores[0x05d1] = helper_0x7803(arg0, arg1),[0x05cd] = helper_0x78a1(arg0), and[0x05cf] = helper_0x78ad(arg1). In normal text mode,0x78adreturns0xfffor a nonzero argument; when byte[0x1757]is set,0x7803packs the pair asarg0 | (arg1 << 4). - Added QEMU case
text_attribute_pair_changes_attr_mode_clear_color. The fixture runs0x6d(0, 1), then0x6a, and compares the visible surface without expecting the normal validation sprite while alternate text mode is active. - QEMU batch
text_attr_pair_behaviour_001matched with 1 match, 0 mismatches, and 0 errors. This validates that the stored pair is reused by0x6aand produces a full-screen visual color 15 clear, matching packed text attribute low byte0xf0for the observed EGA path. - Promoted action
0x6d(set_text_window_pair) to behavior-level QEMU-validated evidence. Updatedtools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, and symbolic-label docs. - Updated
PROGRESS.md: logic action opcode coverage is now 163 of 176 at[x]level (162QEMU-validated plus structural0x00), with 13 partial action opcodes remaining.
2026-07-04: input-line refresh/erase and status-line show probes
Commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -nreads ofPROGRESS.md,tools/logic_interpreter_probe.py,tests/test_logic_interpreter_probe.py,tools/logic_opcode_evidence.py,docs/src/logic_bytecode.md,docs/src/runtime_model.md,docs/src/compatibility_testing.md,docs/src/symbolic_labels.md, and this notes file.rg -n "\\[~\\]|Highest-Value|Remaining|0x70|0x89|0x8a|0xa3|0xa4|0xa9|0xaa|0xad|0x83|0x8e|0x95|0x96|0x6e" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/clean_room_executable_notes.md tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py tools/logic_opcode_evidence.pyndisasm -b 16 -o 0x3652 -e 0x3852 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x3726 -e 0x3926 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x34bd -e 0x36bd build/cleanroom/AGI.decrypted.exepython3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_text_rect_clear_cases_expect_display_surface_rectangles tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_can_match_without_glyph_model tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_report_mismatch- First QEMU attempt:
python3 -B tools/logic_interpreter_probe.py --dos-prefix IR --output build/logic-interpreter-probes/batches/input_refresh_status_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case status_line_show_draws_configured_row --case input_line_typed_text_visible_baseline --case input_line_erase_clears_typed_buffer --case input_line_erase_then_refresh_restores_typed_buffer - Capture checks:
python3 -B -m json.tool build/logic-interpreter-probes/batches/input_refresh_status_001.jsonandpython3 -B tools/inspect_ppm.pyover the three input-line captures. - Local row-count script over the downsampled captures for
input_line_typed_text_visible_baseline,input_line_erase_clears_typed_buffer, and the failed refresh case. - Focused unit rerun:
python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_can_match_without_glyph_model tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_report_mismatch - Corrected QEMU input batch:
python3 -B tools/logic_interpreter_probe.py --dos-prefix IR --output build/logic-interpreter-probes/batches/input_refresh_status_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_line_typed_text_visible_baseline --case input_line_erase_clears_typed_buffer --case input_line_refresh_repaints_entered_buffer - QEMU status batch:
python3 -B tools/logic_interpreter_probe.py --dos-prefix ST --output build/logic-interpreter-probes/batches/status_show_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case status_line_show_draws_configured_row python3 -B tools/logic_opcode_evidence.py
Results:
- Added a narrow rectangle-check comparison mode to
tools/logic_interpreter_probe.pyfor UI text rows whose exact font glyphs are not yet modeled by the local renderer. Existing exact full-frame comparisons remain the default. - Re-read helper
code.input.handle_input_char(0x3652), action0x8a(0x3726), action0x89(0x3753), helpercode.input.append_source_to_visible(0x37a5), and status-line helper0x34bd. - Added QEMU baseline
input_line_typed_text_visible_baseline, which validates that typed live-edit characters produce visible color-15 pixels on the configured input row. - Added QEMU case
input_line_erase_clears_typed_buffer. The fixture configures input row 5, sendslook, then runs0x8aevery cycle. It matched only when logical Y 40..47 was black before the final object draw, validating the visible erase path for action0x8a. - The first attempted refresh case assumed
0x89would repaint unaccepted live-edit characters after0x8a. QEMU batchinput_refresh_status_001matched the baseline and erase cases but mismatched the refresh case. Row counts showed the baseline row had color-15 glyph pixels while both the erase and failed refresh captures had zero color-15 pixels in logical Y 40..47. - Corrected the refresh case to type
lookplus Enter before checking0x89. This matches the disassembly: Enter copies visible buffer0x0fa4into source buffer0x0fce, clears visible length[0x0ff8], and redraws; the normal EGA0x89path then copies from0x0fceback into0x0fa4. - QEMU batch
input_refresh_status_002matched with 3 matches, 0 mismatches, and 0 errors. This promotes actions0x89(refresh_input_line) and0x8a(erase_input_line) to behavior-level QEMU-validated evidence for the observed EGA path. - Added QEMU case
status_line_show_draws_configured_row. The fixture configures status row 5 through0x6f(0, 0, 5), runs0x70, and checks for visible color-15 pixels in logical Y 40..47. QEMU batchstatus_show_001matched with 1 match, 0 mismatches, and 0 errors, promoting action0x70(show_status_line_like) to behavior-level QEMU-validated evidence. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs. - Updated
PROGRESS.md: logic action opcode coverage is now 166 of 176 at[x]level (165QEMU-validated plus structural0x00), with 10 partial action opcodes remaining.
2026-07-04: enabled trace-window validation
Commands run from /Users/peter/ai/agi/reverse:
git status --shortrg -n "trace_window|0x95|0x96|1d10|1d08|1d0a" tools/logic_interpreter_probe.py docs/src/logic_bytecode.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md PROGRESS.mdsed -nreads oftools/logic_interpreter_probe.py,tests/test_logic_interpreter_probe.py,docs/src/logic_bytecode.md,docs/src/symbolic_labels.md,docs/src/runtime_model.md,docs/src/compatibility_testing.md,PROGRESS.md, and this notes file.ndisasm -b 16 -o 0x8c91 -e 0x8e91 build/cleanroom/AGI.decrypted.exeduring the preceding trace inspection pass.- Focused unit tests:
python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_trace_window_rect_check_tracks_source_bounds tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_can_match_without_glyph_model tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_report_mismatch - First enabled trace QEMU run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix TR --output build/logic-interpreter-probes/batches/trace_window_enable_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case trace_window_enable_draws_box_when_flag10_set - Capture inspection:
python3 -B tools/inspect_ppm.py build/logic-interpreter-probes/fixtures/trace_window_enable_draws_box_when_flag10_set/qemu_capture.ppm - Local downsample/color-count script over the same PPM capture.
magick build/logic-interpreter-probes/fixtures/trace_window_enable_draws_box_when_flag10_set/qemu_capture.ppm build/logic-interpreter-probes/fixtures/trace_window_enable_draws_box_when_flag10_set/qemu_capture.png- Stricter focused unit rerun, same unittest command as above.
- Final enabled trace QEMU run:
python3 -B tools/logic_interpreter_probe.py --dos-prefix TR --output build/logic-interpreter-probes/batches/trace_window_enable_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case trace_window_enable_draws_box_when_flag10_set python3 -B tools/logic_opcode_evidence.py
Results:
- Re-read the trace action pair from disassembly. Handler
0x95at image0x8c91either consumes an extra byte when trace state[0x1d10]is already nonzero, or calls helper0x8caeto enable the trace window only if flag 10 is set. Handler0x96at image0x8d3dstores trace logic/resource, row-offset, and height globals in[0x1d12],[0x1d08], and[0x1d0a], clamping height upward to at least 2. - Added QEMU case
trace_window_enable_draws_box_when_flag10_set. The fixture configures the base text row with0x6f(0, 0, 5), configures the trace window with0x96(0, 1, 2), sets flag 10, and runs0x95as a one-shot path so the repeated-activeSI + 1behavior cannot consume fixture bytes by accident. - The first enabled trace run matched a broad white-window check. Inspection of
the capture showed the original engine draws a red-bordered, white-filled
trace box with black text such as
0: 12(94). The downsampled capture has red border pixels around logical row 5, large white fill through the trace window, and black glyph pixels in rows 18..30. - Tightened the case to require all three visible signals: red border, white
fill, and black trace text. QEMU batch
trace_window_enable_002matched with 1 match, 0 mismatches, and 0 errors. - Promoted actions
0x95(enable_action_trace_window) and0x96(configure_action_trace_window) from QEMU dispatch-smoke to behavior-level QEMU-validated evidence. The older flag-clear case remains useful as gated no-draw coverage. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs. - Updated
PROGRESS.md: logic action opcode coverage is now 168 of 176 at[x]level (167QEMU-validated plus structural0x00), with 8 partial action opcodes remaining.
2026-07-04: input-width flag and inactive close-window cleanup probes
Commands run from /Users/peter/ai/agi/reverse:
rg -n "Highest-Value|Remaining|0x6e|0x83|0x8e|0xa3|0xa4|0xa9|0xaa|0xad" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md tools/logic_interpreter_probe.py tools/logic_opcode_evidence.pysed -nreads ofPROGRESS.md,docs/src/logic_bytecode.md,docs/src/runtime_model.md,docs/src/symbolic_labels.md,docs/src/compatibility_testing.md,tools/logic_interpreter_probe.py,tests/test_logic_interpreter_probe.py,tools/qemu_snapshot.py, and this notes file.ndisasm -b 16 -o 0x3652 -e 0x3852 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x3939 -e 0x3b39 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x1f2b -e 0x212b build/cleanroom/AGI.decrypted.exe- Focused unit tests:
python3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flow tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_input_width_flag_cases_have_distinct_row_checks tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_can_match_without_glyph_model tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_rect_checks_report_mismatch - First QEMU attempt with a visible long slot-0 prefix:
python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag - Capture/report inspections using
python3 -B tools/inspect_ppm.pyand local downsample/color-count scripts over the generatedinput_width_flag_*fixture captures. - Second QEMU attempt with a long blank slot-0 prefix:
python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag - Third QEMU attempt with the check moved to the wrapped row:
python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag - Final QEMU batch:
python3 -B tools/logic_interpreter_probe.py --dos-prefix IW --output build/logic-interpreter-probes/batches/input_width_flag_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_width_flag_a3_allows_long_live_input --case input_width_flag_a4_restores_long_slot_limit --case close_text_window_state_clears_input_width_flag python3 -B tools/logic_opcode_evidence.py
Results:
- Re-read
code.input.handle_input_char(0x3652). At entry it computes a live-input cap inDI: if word[0x0d0f]is nonzero it uses0x24; otherwise it computes0x28 - strlen(0x020d), where0x020dis fixed string slot 0. The prompt marker byte can reduce this by one, and byte[0x21]applies another cap. The printable-character path appends to visible buffer0x0fa4only while current visible length[0x0ff8]is below this cap. - Re-read action
0xa3at0x3939: it sets word[0x0d0f] = 1and returns the current bytecode pointer. Action0xa4at0x394bclears the same word. - Re-read action
0xa9at0x1f2b: if word[0x0d1d]is nonzero, it restores saved display rectangle[0x0d23]/[0x0d25]through helper0x560c, then clears both[0x0d0f]and[0x0d1d]. The QEMU case below validates the unconditional[0x0d0f]clear with no active saved window; the active rectangle restore remains source-backed from this disassembly. - Added cases
input_width_flag_a3_allows_long_live_input,input_width_flag_a4_restores_long_slot_limit, andclose_text_window_state_clears_input_width_flag. - First attempt used a visible 38-character string slot 0.
0xa3matched, but0xa4mismatched because the long prefix itself painted many white pixels on the input row, masking the accepted-input distinction. - Second attempt changed slot 0 to 38 spaces. This removed the visible prefix glyphs, but QEMU showed the typed characters wrapping into logical rows 48..55, not rows 40..47.
- Third attempt checked for white pixels on the wrapped row.
0xa3matched, but0xa4still mismatched because the wrapped row is blank white fill even without accepted typed glyphs. - Final attempt checked for black glyph pixels inside logical rows 48..55.
With
0xa3, accepted typed characters create black glyph pixels in that white-filled wrapped row. With0xa4, and with0xa9after0xa3, the same row remains blank white fill with no black glyph signal. - QEMU batch
input_width_flag_004matched 3/3 with 0 mismatches and 0 errors. This promotes0xa3and0xa4to behavior-level QEMU evidence for the input-width flag and promotes0xa9for the inactive-window unconditional flag-clear side. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs. - Updated
PROGRESS.md: logic action opcode coverage is now 171 of 176 at[x]level (170QEMU-validated plus structural0x00), with 5 partial action opcodes remaining.
2026-07-04: action 0x83 direction-mirror timing
Commands:
rg -n "0139|0x0139|clear_global_0139|set_global_0139|field_22|object0|first object" ...ndisasm -b 16 -o 0x702f -e 0x722f build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x0150 -e 0x0350 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'python3 -B tools/logic_interpreter_probe.py --dos-prefix L83 --output build/logic-interpreter-probes/batches/object0_direction_mirror_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_global_0139_allows_object0_direction_to_seed_globalpython3 -B tools/logic_interpreter_probe.py --dos-prefix L83 --output build/logic-interpreter-probes/batches/object0_direction_mirror_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_global_0139_allows_object0_direction_to_seed_globalpython3 -B tools/logic_interpreter_probe.py --dos-prefix L83 --output build/logic-interpreter-probes/batches/object0_direction_mirror_003.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_global_0139_allows_object0_direction_to_seed_globalpython3 -B tools/logic_interpreter_probe.py --dos-prefix L83 --output build/logic-interpreter-probes/batches/object0_direction_mirror_004.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case clear_global_0139_allows_object0_direction_to_seed_globalpython3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/manual_direction_value_cases.json --dos-prefix L8D --output build/logic-interpreter-probes/batches/object0_direction_value_diag_001.json --boot-wait 5 --draw-wait 8 --case diagnostic_object0_direction_value_after_0139_mirrorpython3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/manual_direction_phase_cases.json --dos-prefix L8P --output build/logic-interpreter-probes/batches/object0_direction_phase_diag_001.json --boot-wait 5 --draw-wait 8 --case diagnostic_object0_direction_phase2_reachedpython3 -B tools/logic_interpreter_probe.py --cases build/logic-interpreter-probes/manual_direction_active_cases.json --dos-prefix L8A --output build/logic-interpreter-probes/batches/object0_direction_active_diag_001.json --boot-wait 5 --draw-wait 8 --case diagnostic_object0_active_direction_phase2
Observations:
- Action
0x83at image0x702fis a tiny handler: it stores word[0x0139] = 0and returns the current bytecode pointer. - Action
0x84at image0x7041stores word[0x0139] = 1and clears byte+0x22on the first object entry. code.engine.main_cycleuses[0x0139]before logic execution. When the word is zero, it copies first-object direction byte+0x21to global byte[0x000f]. When the word is nonzero, it copies[0x000f]back to first object byte+0x21.- The same main-cycle source later writes
[0x000f]back to first-object+0x21after the logic-0 call returns. This means a logic script that sets object0 byte+0x21after the pre-logic mirror is too late to seed[0x000f]for the next cycle by itself. - Several attempted permanent QEMU fixtures tried to set object0 byte
+0x21to6, execute0x83, wait a cycle, execute0x84, and then read byte+0x21through0x57. These did not validate the intended model:object0_direction_mirror_001..004mismatched or produced no validation marker. - Disposable diagnostics confirmed the phase scaffolding reached phase 2, but
the observed object0 direction after the sequence was
0, not6. Activating object0 during the seed phase did not change that result. - Conclusion:
0x83should be specified from source as the selector clear for the pre-logic object0/global direction mirror. A script-level QEMU fixture is not a clean validation shape because the relevant branch point happens before logic bytecode runs, and cycle-end restoration can clobber script-written object0 direction bytes. - Removed the attempted reusable
clear_global_0139_allows_object0_direction_to_seed_globalfixture fromtools/logic_interpreter_probe.pyafter the diagnostics showed it was testing the wrong timing point. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs. - Updated
PROGRESS.md: logic action opcode coverage is now 172 of 176 at[x]level (170QEMU-validated, structural0x00, and source-backed0x83), with 4 partial action opcodes remaining.
2026-07-04: action 0xad key-release enqueue gate
Commands:
rg -n "0xad|increment_global_1530|1530|0x1530|Highest-Value|\\[~\\]" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md tools/logic_opcode_evidence.py tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.pyndisasm -b 16 -o 0x6000 -e 0x6200 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'rizin -q -c "/x 3015" -c q build/cleanroom/AGI.decrypted.exerizin -q -c "/x 1915" -c "/x 3015" -c "/x 2f15" -c q build/cleanroom/AGI.decrypted.exerg -n "44a9|code\\.event|enqueue|event queue|keyboard|raw event|type-2|type 2|0x44a9" docs/src/symbolic_labels.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/clean_room_executable_notes.md
Observations:
- Action
0xadat image0x602fconsists ofinc byte [0x1530], then returns the current bytecode pointer. - Local byte-pattern searches found only the action increment and the keyboard
IRQ hook test of
[0x1530]. - The keyboard IRQ hook at image
0x6036stores the raw scan byte in[0x152f], maps scan codes0x47..0x51to an index by subtracting0x47, checks enable table[0x1519 + index], and tracks press/release state in[0x1524 + index]. - On release (
scan & 0x80set), if the latch was set, the hook clears the latch and tests byte[0x1530]. If the gate is nonzero, it callscode.input.enqueue_event(0x44a9) with(type=2, value=0). - On press, if no latch is set for that index, the hook clears the latch table
range and increments the selected
[0x1524 + index]latch. - Conclusion:
0xadis a source-backed input/keyboard action. It increments a nonzero gate that allows selected tracked-key releases to enqueue a type-2 zero event from the interrupt hook. A QEMU fixture for this would depend on raw scan-code press/release timing and is less appropriate than direct source evidence for the current spec target. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs. - Updated
PROGRESS.md: logic action opcode coverage is now 173 of 176 at[x]level (170QEMU-validated, structural0x00, and source-backed0x83/0xad), with 3 partial action opcodes remaining.
2026-07-04: action 0x8e event-pair capacity reset
Commands:
rg -n "0x8e|set_global_0141|pair_capacity|0x0141|data.event.pair_capacity|reset_pair_buffer|event buffer|resource-event|replay" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md tools/logic_opcode_evidence.py tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.pyndisasm -b 16 -o 0x7140 -e 0x7220 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x7060 -e 0x7130 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'
Observations:
- Action
0x8eat image0x716areads one immediate byte, writes it todata.event.pair_capacity([0x0141]), calls an update-list flush helper, callscode.event.reset_pair_buffer, calls the matching update-list rebuild helper, and returns the advanced bytecode pointer. code.event.reset_pair_bufferchecksdata.event.pair_capacity. If the capacity is positive and no pair buffer exists, it allocatescapacity * 2bytes, stores the resulting pointer indata.event.pair_buffer_base([0x1707]), and initializes allocator state. It then setsdata.event.pair_buffer_write([0x1709]) to the base pointer and clearsdata.event.pair_count([0x0143]).- Existing replay-source and QEMU work already validates downstream pair-log
semantics for
0xab/0xacrollback and display-mode/restore replay. The0x8eaction itself is compact enough to cover from source as the capacity and reset entry point for that same log. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs. - Updated
PROGRESS.md: logic action opcode coverage is now 174 of 176 at[x]level (170QEMU-validated, structural0x00, and source-backed0x83/0x8e/0xad), with 2 partial action opcodes remaining.
2026-07-04: action 0xaa save-description buffer copy
Commands:
rg -n "0xaa|copy_save_description|0x0e72|0e72|save description|description buffer|select_slot|0x2726|0x4de8|0x7d|0x7e" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md tools/logic_opcode_evidence.py tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.pyndisasm -b 16 -o 0x2700 -e 0x2860 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x4d80 -e 0x4e40 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'
Observations:
- Action
0xaaat image0x2726reads one immediate string-slot index, computes destination0x020d + index * 0x28, and copies up to0x1fbytes from runtime buffer[0x0e72]through the shared bounded-copy helper. - Save and restore handlers test byte
[0x0e72]aftercode.save.select_slot_or_pathreturns, so this buffer is populated by save/restore selector state rather than by static resource data. - Earlier attempted QEMU fixture
save_description_copy_001patchedAGIDATA.OVLbytes at offset0x0e72tolook, then tried to compare the copied string slot against a message string. The validation draw did not occur even though the fixture file contained the bytes. This is now explained as a fixture-shape problem: action0xaareads the interpreter’s runtime data segment at[0x0e72], not the fixture file’s static overlay bytes. - Dynamic validation remains possible but should drive the real save/restore selector path that fills the runtime description buffer. The action itself is compact and is now covered as source-backed.
- Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs. - Updated
PROGRESS.md: logic action opcode coverage is now 175 of 176 at[x]level (170QEMU-validated, structural0x00, and source-backed0x83/0x8e/0xaa/0xad), with only0x6epartial.
2026-07-04: action 0x6e screen-shake source pass
Commands:
rg -n "0x6e|shake_screen|screen_shake|shake|0x6e\\b|screen-shake|display offset|0x1379|0x112e|0x1130" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/compatibility_testing.md docs/src/clean_room_executable_notes.md tools/logic_opcode_evidence.py tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.pyndisasm -b 16 -o 0x79c0 -e 0x7b60 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'rizin -q -c "/x 7a17" -c "/x 7913" -c "/x 6513" -c "/x 7917" -c q build/cleanroom/AGI.decrypted.exe
Observations:
- Action
0x6ereads one immediate count byte intoCLand advances the bytecode pointer before doing the display work. - Display mode
[0x1130] == 3,2, or4delegates to display/overlay helper paths observed at0x99b8,0x9be3, and0x9916. - The normal path sets byte
[0x1779]to0x70when hardware selector[0x112e] == 0, otherwise to0x38. - It writes CRT controller registers via ports
0x3d4and0x3d5: register0x02receives a byte from table0x177aplus[0x1365]; register0x07receives the following table byte plus[0x1779]. - After each register-pair write, it waits for timer word
[0x0129]to change. It advances through table pairs until the register-7 value returns to the base[0x1779], then repeats for the requested count. - Existing QEMU case
screen_shake_dispatch_smokevalidates that a one-count action returns to following bytecode. A screenshot-after-return fixture cannot capture the transient register animation reliably, so the timing/display effect is source-backed. - Updated
tools/logic_opcode_evidence.py, regenerateddocs/src/logic_opcode_evidence.md, and updated opcode, runtime, compatibility, symbolic-label, and tracker docs. - Updated
PROGRESS.md: all 176 logic action opcodes are now covered at[x]level (170QEMU-validated, structural0x00, and source-backed0x6e/0x83/0x8e/0xaa/0xad).
2026-07-04: menu navigation source-table refinement
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x93d1 -e 0x95d1 build/cleanroom/AGI.decrypted.exexxd -g 2 -s 0x16b3 -l 0x50 SQ2/AGIDATA.OVLsed -n '2140,2255p' tools/logic_interpreter_probe.pysed -n '1088,1178p' docs/src/logic_bytecode.md
Documented result:
- Re-read
code.menu.interact(0x93d1) and its type-2 movement dispatch table at image0x9526. The eight little-endian target words are0x9492,0x94a6,0x94b2,0x94cb,0x94da,0x94e5,0x94f6, and0x9509. - Confirmed from
SQ2/AGIDATA.OVLthat the raw movement table rooted at0x16b3maps BIOS-style key words0x4800,0x4900,0x4d00,0x5100,0x5000,0x4f00,0x4b00, and0x4700to movement values1..8. - Refined the menu/navigation prose: item movement branches select previous, first, last, or next item nodes directly and do not skip disabled item nodes. The item enable word is tested only by the Enter branch before enqueueing a type-3 selection event. Heading left/right movement skips disabled headings, while root/last-heading jumps select the root or root-previous heading directly.
- Added symbolic label
table.menu.navigation_dispatchand splitdata.menu.heading_root,data.menu.current_heading, anddata.menu.current_itemso future interpreter-version comparisons do not depend on the SQ2 addresses alone.
2026-07-04: menu interaction state-machine source pass
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1140,1225p' docs/src/logic_bytecode.mdsed -n '5800,5865p' docs/src/clean_room_executable_notes.mdsed -n '244,274p' docs/src/symbolic_labels.mdndisasm -b 16 -o 0x93d1 -e 0x95d1 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x911d -e 0x931d build/cleanroom/AGI.decrypted.exexxd -g 2 -s 0x16b0 -l 0x60 SQ2/AGIDATA.OVL
Documented result:
- Re-read the setup handlers at
0x911d,0x91cf, and0x92ba, the enable/disable helper at0x935f, and the modal interaction loop at0x93d1. - Converted the source observations into an implementation-facing menu data
model in
docs/src/runtime_model.md: 18-byte circular heading nodes, 14-byte circular item nodes, global root/current pointers, finalization, and the interaction request word. - Documented the modal interaction lifecycle.
0xa1only requests the menu when flag 14 is set.code.menu.interactwaits through the shared event helpers, treats event type 1 as Enter/Escape, treats event type 2 as movement values1..8, and persists the current heading/item before looping. - Confirmed the Enter semantics from source and existing QEMU probes: enabled
items enqueue a type-3 status event with the item id, disabled items continue
waiting, and Escape exits without enqueueing a selection. Dynamic arrow-key
validation remains a compatibility-suite gap, but the movement semantics are
source-backed from
table.menu.navigation_dispatchand the AGIDATA raw-key table.
2026-07-04: picture scanner command-resume fuzz expansion
Commands run from /Users/peter/ai/agi/reverse:
rg -n "base_0|corner|f4|f5|interleav|random|pattern|seed_fill|draw_corner" tools/picture_fuzz.py tests/test_picture_fuzz.py tests/test_graphics_rendering.pysed -n '1,260p' tools/picture_fuzz.pysed -n '1,430p' tests/test_graphics_rendering.pypython3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzzpython3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --cleanpython3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_030_line_pair_command_resume --case base_031_corner_command_resume --case base_032_fill_command_resume --dos-prefix FR --fixture-root build/picture-fuzz/fixtures --output build/picture-fuzz/batches/command_resume_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
Documented result:
- Added three safe curated picture fuzz cases. They exercise the common
coordinate/list-reader rule that a byte above
0xefterminates the active drawing command and remains pending for the scanner. - Added local renderer tests for an incomplete absolute-line coordinate pair
terminated by command
0xf0, a Y-first corner path terminated after one segment by command0xf0, and a seed-fill point list terminated by command0xf0. - Regenerated the corpus with 1,057 cases, of which 1,055 are safe for QEMU. The two unsafe cases remain out-of-spec guardrails for over-read behavior.
- QEMU snapshot batch
command_resume_001matched all three new cases with 0 mismatches, promoting this scanner-resume behavior to original-engine compatibility evidence.
2026-07-04: timed view/object carousel harness
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,280p' tools/view_batch.pysed -n '1,320p' tools/picture_carousel.pysed -n '1,260p' tests/test_view_batch.pysed -n '1,980p' tools/qemu_fixture.pysed -n '1,620p' tests/test_qemu_fixture.pypython3 -B -m unittest tests.test_qemu_fixture tests.test_view_batch tests.test_view_carouselpython3 -B tools/view_carousel.py --case view_011_normal_mid --case view_000_group1_mirrored_mid --fixture-root build/view-carousel/smoke-fixtures --dos-dir VCARSMK --output build/view-carousel/batches/view_carousel_smoke_001.json --boot-wait 5 --first-wait 3 --delay-cycles 120 --speed-value 1 --poll-interval 0.5 --poll-timeout 20python3 -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 20python3 -B tools/view_carousel.py --include-stress --fixture-root build/view-carousel/stress-fixtures --dos-dir VCARSTR --output build/view-carousel/batches/view_carousel_stress_001.json --boot-wait 5 --first-wait 3 --delay-cycles 120 --speed-value 1 --poll-interval 0.5 --poll-timeout 20
Documented result:
- Added
view_timed_carousel_logic_payloadandbuild_view_timed_carousel_fixturetotools/qemu_fixture.py. The fixture packs generatedLOGIC.0, selected picture resources, and selected view resources intoVOL.3and patchesPICDIR,VIEWDIR, andLOGDIR. - Added
tools/view_carousel.py, a timed polling QEMU harness for picture-plus-view cases. It keeps one original-engine process running, refreshes the picture and transient object after a cycle delay, and pollsscreendumpoutput until the expected local comparison matches. - Added local tests for the new logic payload, packed fixture layout, runner naming/report behavior, and mocked runner flow.
- The first sandboxed QEMU attempt failed because QEMU could not bind the local
VNC socket; rerunning with the approved
python3 -B tools/view_carousel.pycommand prefix allowed the local socket bind. - QEMU
view_carousel_smoke_001matched two cases,view_carousel_base_001matched all 8 current base view cases, andview_carousel_stress_001matched all 19 current base-plus-stress cases with 0 mismatches and 0 errors from one original-engine process.
2026-07-04: picture/view runtime contract synthesis
Commands run from /Users/peter/ai/agi/reverse:
sed -n '230,295p' docs/src/compatibility_testing.mdsed -n '1008,1024p' docs/src/compatibility_testing.mdsed -n '1216,1230p' docs/src/compatibility_testing.mdsed -n '383,398p' PROGRESS.md
Documented result:
- Added implementation-facing picture decoder lifecycle text to
docs/src/runtime_model.md, covering cache selection, fresh versus overlay decode, scanner command/data behavior, draw-state channels, seed-fill contract, and display finalization. - Added implementation-facing view/cel drawing contract text to
docs/src/runtime_model.md, covering payload layout, row runs, bit-0x80orientation rewrite, baseline placement, priority/control gating, pixel writes, and transient versus persistent object use. - Updated
PROGRESS.mdso picture/view implementation text is no longer listed as the main renderer gap; remaining renderer work is now broader priority/control, animation, future edge probes, and cross-version/resource parity.
2026-07-04: parser wildcard and terminator probes
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/inspect_words.py --prefix look --limit 20python3 -B tools/inspect_words.py --prefix around --limit 20python3 -B tools/inspect_words.py --prefix get --limit 20sed -n '1240,1310p' tools/logic_interpreter_probe.pypython3 -B -m unittest tests.test_logic_interpreter_probe.LogicInterpreterProbeTests.test_base_cases_cover_core_control_flowpython3 -B tools/logic_interpreter_probe.py --dos-prefix PW --output build/logic-interpreter-probes/batches/parser_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case input_word_sequence_matches_two_words --case input_word_sequence_wildcard_matches_word --case input_word_sequence_terminator_accepts_prefix
Documented result:
- Confirmed from the local
WORDS.TOKdecoder thatlookmaps to word ID0x0002andgetmaps to word ID0x0005. The wordaroundmaps to0x0000, so it was not used as the positive edge probe. - Added three
tools/logic_interpreter_probe.pycases. They parse message stringlook getwith action0x75, then test condition0x0efor exact two-word matching, wildcard word ID0x0001, and terminator word ID0x270f. - QEMU batch
parser_edges_001matched all three cases with 0 mismatches. The runtime model now treats the wildcard and terminator behavior as QEMU-backed, not merely source-backed.
2026-07-04: object placement spiral source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x593a -e 0x5b3a build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x56b8 -e 0x58b8 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'rg -n '0x593a|0x56b8|placement|right-edge|right edge|baseline67|baseline_y=67|expected_baseline_y=67|place' docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools/object_overlay_probe.py tests/test_object_overlay_probe.py- Local simulation of the
0x593amovement sequence for view 11/group 0/frame 0 at requested placements(20, 2),(0, 80), and(154, 80). python3 -B -m unittest tests.test_graphics_rendering tests.test_object_overlay_probepython3 -B -m unittest tests.test_view_batch- Compared existing QEMU captures for
top_clip_view11_priority15andright_clip_view11_priority15against the updated local model.
Documented result:
- Re-read placement helper
code.object.place(0x593a). It tests the initial object position with bounds helper0x5a14, object collision helper0x4719, and control/priority acceptance helper0x56b8. If the position fails, it searches in a widening spiral. - The source movement sequence is
left 1,down 1,right 2,up 2,left 3,down 3,right 4,up 4, and so on. The candidate is tested before each move. - Added
search_object_placement()andplacement_bounds_ok()totools/agi_graphics.pyfor the bounds-only portion of this source model. The helper reproduces the previously QEMU-observed top-edge placement(18, 4)and right-edge placement(140, 67)for view 11/group 0/frame 0, and reproduces horizon clamping to[0x012d] + 1when bit0x0008is not modeled as set. - Updated
tools/object_overlay_probe.pyso ordinary top/right expected positions are derived from the placement search instead of hard-coded in the case registry. Existing QEMU captures for both edge cases still compare with 0 mismatches. - Updated object-pipeline, compatibility, symbolic-label, progress-tracker, and unit-test coverage. Collision/control rejection can extend the search beyond the first bounds-acceptable candidate, so those cases remain future work.
2026-07-04: picture seed-fill span-stack source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x533b -e 0x533b build/cleanroom/AGI.decrypted.exesed -n '185,220p' docs/src/graphics_object_pipeline.mdsed -n '770,790p' docs/src/compatibility_testing.mdsed -n '968,984p' docs/src/compatibility_testing.mdsed -n '88,118p' docs/src/symbolic_labels.mdsed -n '2415,2490p' docs/src/clean_room_executable_notes.md
Rejected or limited evidence:
- The broad disassembly command above printed far past the seed-fill helper,
so only the known helper body around
code.picture.seed_filland the pre-existing focused seed-fill notes were used as evidence for this pass.
Documented result:
- Re-read the seed-fill helper enough to make the implementation-facing contract sharper. The helper chooses exactly one expansion test channel per seed: visual low nibble first when visual drawing is active, otherwise control high nibble when control drawing is active.
- Confirmed the early exits: no active drawing channel, selected visual value equal to the visual default target, selected control value equal to the control default target, or a seed cell that does not match the selected default target.
- Confirmed that accepted pixels still use the normal active draw byte and odd/even masks, so both logical nibbles can change even though only one channel controls expansion.
- Documented the SQ2 traversal class as a stack-backed horizontal span fill.
It fills the current row left/right, records span state in the scratch block
around
0x126c..0x1279, scans adjacent rows in a current vertical direction, pushes deferred branch spans on the CPU stack, reverses direction when needed, and terminates after popping the sentinel row state. - Added conservative symbolic label
data.picture.seed_fill_span_scratchinstead of naming each byte in the scratch block prematurely. - Updated compatibility notes to say the remaining seed-fill work is broadened parity coverage for barriers, odd/even masks, and multi-seed cases, not unknown traversal class.
2026-07-04: seed-fill fuzz case expansion
Commands run from /Users/peter/ai/agi/reverse:
rg -n "base_0|visual_fill|seed|fill|cases|safe_for_qemu" tools/picture_fuzz.py tests/test_picture_fuzz.py tests/test_graphics_rendering.pysed -n '1,260p' tests/test_picture_fuzz.pysed -n '1,260p' tools/picture_fuzz.pysed -n '1,240p' tools/agi_graphics.pysed -n '115,160p' tests/test_graphics_rendering.pypython3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzzpython3 -B tools/picture_fuzz.py generate --count 8 --seed 4097 --output build/picture-fuzz/seed-fill-cases --cleanpython3 -B tools/picture_fuzz.py batch-qemu --snapshot --corpus build/picture-fuzz/seed-fill-cases --fixture-root build/picture-fuzz/seed-fill-fixtures --case base_021_visual_fill_full_height_barrier --case base_022_visual_fill_multi_seed_boxes --case base_023_control_fill_ignores_visual_barrier --dos-prefix SF --output build/picture-fuzz/batches/seed_fill_edges_001.json --boot-wait 5 --draw-wait 8 --stop-on-failurecat build/picture-fuzz/batches/seed_fill_edges_001.jsonpython3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean
Rejected or limited evidence:
- The first QEMU batch attempt failed before any interpreter behavior was
observed because sandboxed QEMU could not bind
127.0.0.1:5for VNC. The same command was rerun with approved escalation and produced the evidence result below.
Documented result:
- Added safe curated fuzz cases:
base_021_visual_fill_full_height_barrier: a full-height one-pixel visual barrier blocks a visual seed fill.base_022_visual_fill_multi_seed_boxes: one0xf8command contains two seed pairs and fills two isolated boxed regions.base_023_control_fill_ignores_visual_barrier: a control-channel fill crosses a visual-only barrier because the selected expansion channel is control, while the visible barrier remains undisturbed.
- Added local renderer assertions for all three cases. The tests check final
cell values rather than only screenshot hashes, including the control-channel
crossing in
base_023. - Regenerated a small corpus; it reported 32 cases total, with 30 marked safe for QEMU. The standard 1,024-random corpus command reports 1,048 cases total and 1,046 safe cases.
- The QEMU snapshot batch
seed_fill_edges_001matched the local renderer: 3 matches, 0 mismatches, 0 errors, with each comparison covering 26,880 logical pixels.
2026-07-04: optional view stress batch expansion
Commands run from /Users/peter/ai/agi/reverse:
rg -n "mirror|mirroring|transparent|cel|view" docs/src/graphics_object_pipeline.md docs/src/compatibility_testing.md PROGRESS.md tests/test_graphics_rendering.py tools/agi_graphics.py tools/view_batch.pysed -n '390,620p' tools/agi_graphics.pysed -n '520,620p' docs/src/graphics_object_pipeline.md- Local Python corpus scan over
VIEWDIRframes to count frames, mirror-bit frames, transparent-color representatives, and largest cels. - Local Python validation that selected stress cases fit within the screen at their chosen placements.
python3 -B -m unittest tests.test_view_batch tests.test_graphics_renderingpython3 -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-failurecat build/view-batch/batches/view_stress_001.json
Documented result:
- Added
stress_cases()totools/view_batch.pyand exposed it through optional CLI flag--include-stress. The default six-case view batch remains unchanged for quick smoke runs. - The stress suite adds eleven cases selected from the local SQ2 view corpus:
large cels, the 129-row tall cel, transparent colors
0,1,2,5,6,7,8,10,13,14, and15, and a bit-0x80transparent-10 frame. - The local unit tests confirm that stress cases are optional and that the stress placements fit on screen.
- The QEMU snapshot run
view_stress_001covered 17 cases total: the six existing base cases plus the eleven stress cases. All 17 matched with 0 mismatches and 0 errors, each over 26,880 logical pixels.
2026-07-04: pattern and interleaved picture fuzz expansion
Commands run from /Users/peter/ai/agi/reverse:
rg -n "pattern|0x10|0x20|pattern_mode|base_02" tests/test_graphics_rendering.py tools/picture_fuzz.py docs/src/graphics_object_pipeline.md docs/src/compatibility_testing.mdsed -n '245,270p' docs/src/graphics_object_pipeline.mdsed -n '250,278p' tests/test_graphics_rendering.pypython3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzzpython3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --cleanpython3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_024_pattern_bypass_mask --case base_025_interleaved_line_fill_pattern --case base_026_pattern_random_bypass_sequence --dos-prefix PF --output build/picture-fuzz/batches/pattern_interleaved_001.json --boot-wait 5 --draw-wait 8 --stop-on-failurecat build/picture-fuzz/batches/pattern_interleaved_001.json
Documented result:
- Added safe curated picture fuzz cases:
base_024_pattern_bypass_mask: isolates pattern mode bit0x10bypassing the row/column mask test.base_025_interleaved_line_fill_pattern: draws a rectangle outline, fills it, draws a line through it, and overlays a pattern plot in one valid picture stream.base_026_pattern_random_bypass_sequence: uses both mode bits0x10and0x20across two pseudo-random pattern plots.
- Added local renderer assertions for the first two cases. The mask-bypass test checks the expected 4-by-7 filled footprint for radius 3, and the interleaved test checks that later line and pattern commands overwrite earlier fill results sequentially.
- Regenerated the standard fuzz corpus. It now reports 1,051 cases total and 1,049 safe for QEMU.
- The QEMU snapshot batch
pattern_interleaved_001matched the local renderer: 3 matches, 0 mismatches, 0 errors, with each comparison covering 26,880 logical pixels.
2026-07-04: placement-search predicate hook clarification
Commands run from /Users/peter/ai/agi/reverse:
rg -n "0x4719|0x56b8|collision|control rejection|placement search|spiral|code.object.place|code.object.*collision|control-buffer" docs/src/graphics_object_pipeline.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md tools/agi_graphics.py tests/test_graphics_rendering.py tools/object_overlay_probe.py tests/test_object_overlay_probe.pyndisasm -b 16 -o 0x4719 -e 0x4919 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x56b8 -e 0x58b8 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'sed -n '700,866p' docs/src/graphics_object_pipeline.md
Rejected or limited evidence:
- The first disassembly rerun used image addresses as file offsets and landed in the wrong window. The corrected commands above add the MZ header offset when skipping into the file and show the expected helper bodies.
Documented result:
- Reconfirmed from
code.object.placeand its callees that every placement candidate is tested in this order: bounds/horizon helper0x5a14, collision helper0x4719, and control-buffer acceptance helper0x56b8. - Added a docstring to
search_object_placement()explaining that its optionalacceptpredicate models the two non-bounds predicates. - Added a local regression test that rejects the first four otherwise-valid
candidates
(20,80),(19,80),(19,81), and(20,81). The helper then returns(21,81), matching the source spiral order and showing how collision/control rejection extends the search without changing movement order. - Added symbolic label
code.object.collision_testfor helper0x4719so both placement predicates have stable cross-version names.
2026-07-04: text/input tracker audit
Commands run from /Users/peter/ai/agi/reverse:
rg -n "dispatch-smoke|text window|input line|prompt|status line|close_text|clear_text|trace window|0x69|0x70|0xa3|0xa4|0xa9|text/input|Text windows" PROGRESS.md docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/compatibility_testing.md tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.pyndisasm -b 16 -o 0x1f2b -e 0x212b build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'rg -n "0x0d1d|0x0d23|0x0d25|0x560c|close_text_window|saved rectangle|text window" docs/src/clean_room_executable_notes.md docs/src/logic_bytecode.md docs/src/runtime_model.md tools/logic_interpreter_probe.pyrg -n "0x79|set_input|input row|key_event_mapping|status_line_show_hide|input_line_erase|0x8a|0x6f" docs/src/logic_bytecode.md docs/src/compatibility_testing.md docs/src/runtime_model.md tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py
Documented result:
- Audited the current text/input coverage against
PROGRESS.md. The broad “promote dispatch-smoke rows” wording was stale: focused QEMU cases already cover prompt marker behavior, status/input row show-hide/clear, input refresh/erase, mapped-key and raw-key paths, text attribute mode entry/exit, and the input-width flag effects of0xa3,0xa4, and inactive0xa9. - Re-read
0xa9at0x1f2b: it conditionally calls0x560c([0x0d23], [0x0d25])only when[0x0d1d]is nonzero, then clears[0x0d0f]and[0x0d1d]. - Updated
PROGRESS.mdto name the remaining text/input gap more precisely: active saved-window restore for0xa9, plus non-EGA text paths only if they become necessary for SQ2 behavior.
2026-07-04: sound resource format source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x50d8 -e 0x52d8 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x7f96 -e 0x8196 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'- Local Python scans over
SQ2/SNDDIRand the referencedVOL.*payloads. python3 -B -m unittest tests.test_sound_resources
Documented result:
code.sound.find_loaded_resourcestarts at0x50d8.- Action
0x62enterscode.sound.load_resourceat0x5126. On cache miss, it records resource event(3, resource), resolves the sound directory entry through0x440d, reads the payload through the generic volume reader0x2e32, stores the raw payload pointer at cache-record+0x04, then reads four little-endian words from the payload and stores derived payload-relative channel pointers at record offsets+0x06,+0x08,+0x0a, and+0x0c. - Action
0x63stores/clears the completion flag, locates the already loaded sound record through0x50d8, and callscode.sound.driver_startat0x7f96. code.sound.driver_startcopies the four cached channel pointers into data0x1788..0x178f, initializes the four countdown words at0x1790..0x1797to 1, initializes per-channel state words, and sets active-state word[0x1258] = 1.- The playback tick reads channel records as
duration u16;0xffffterminates a channel. Otherwise it reads a 16-bit tone/control word followed by one control byte and uses the low nibble as the observed attenuation/control value. - Added
tools/agi_sound.pywith a deterministic parser for the observed sound payload shape andtests/test_sound_resources.pyto scan all present SQ2 sound resources. - The targeted sound test passed: 49 present sound resources; every present
payload has four sorted in-bounds channel offsets with first offset 8; every
channel parses to an in-payload terminator. Sound 1 has offsets
(8, 15, 22, 29), channel 0 first event(duration=0x0027, tone_word=0x8037, control_byte=0x9f), and channel 0 terminator offset 13. - This is source-backed resource-format evidence. Audible pitch, timing, and hardware-driver output remain provisional.
2026-07-04: sound playback tick scheduling source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x7f60 -e 0x8160 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x8160 -e 0x8360 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x74c0 -e 0x76c0 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x83a0 -e 0x85a0 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x84f0 -e 0x86f0 build/cleanroom/AGI.decrypted.exe- Local Python scans over parsed SQ2 sound events and completion ticks.
python3 -B -m unittest tests.test_sound_resources
Documented result:
code.sound.driver_startchooses the active channel set from hardware selector[0x112e]: selector values0and8setdata.sound.active_channel_byte_limit = 2anddata.sound.remaining_active_channels = 1, so only channel 0 is advanced. Other observed selector values set the byte limit to 8 and the remaining count to 4, advancing channel offsets0,2,4, and6.code.sound.driver_tickstarts by testing flag 9 throughcode.flags.testat0x7502. If flag 9 is clear, it calls the low-level stop/completion path immediately.- The timer interrupt hook at
0x8521callscode.sound.driver_tickonly whendata.sound.active_stateis nonzero, then either acknowledges the interrupt or chains to the original timer interrupt every third hook call through byte0x184f. - Every channel countdown is initialized to 1, so the first event or terminator is consumed on the first active tick. After an event is consumed, its duration word is stored as the next 16-bit countdown. A duration of zero would wrap and delay the next channel record read for 65,536 ticks.
- The local SQ2 corpus contains 3,619 parsed sound events. The minimum duration is 1, the maximum is 688, and no present event uses duration zero.
- Added source-backed scheduling helpers to
tools/agi_sound.py:active_sound_channel_indices,schedule_sound_channel, andsound_completion_tick. - Expanded
tests/test_sound_resources.pyfrom four to nine tests. New checks validate the one-channel/four-channel selector rule, sound 1’s tick-40 natural termination, sound 60’s differing one-channel and four-channel completion ticks (3403and3404), the synthetic zero-duration wrap, and immediate first-tick completion when flag 9 is clear. - The targeted sound-resource tests passed: 9 tests in
tests.test_sound_resources. Hardware pitch, attenuation envelopes, and port-level output remain provisional.
2026-07-04: active text-window restore source pass
Commands run from /Users/peter/ai/agi/reverse:
rg -n "0d1d|0D1D|0d23|0D23|0d25|0D25|560c|5590|text window|saved-window|saved window|close_text" build/cleanroom docs/src tools testsndisasm -b 16 -o 0x5500 -e 0x5700 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x1cc0 -e 0x1ec0 build/cleanroom/AGI.decrypted.exe
Documented result:
code.text.display_stringat0x1ce8calls the modal message-window setup helper at0x1d96, waits for the relevant acknowledgement/event path, and later callscode.text.close_window_stateat0x1f2bwith argument zero on the normal close path.code.text.display_message_windowat0x1d96first checks word[0x0d1d]. If a saved window is already active, it callscode.text.close_window_statebefore building the next window. This prevents stacking multiple saved rectangles in the observed modal-message path.- The same opener formats/copies the current message text through helper
0x1f54, derives text-window row/column and size words from the text metrics and configuration globals, computes packed rectangle words[0x0d23]and[0x0d25], then calls helper0x5590with those words and attribute0x040f. - Helper
0x5590is the boxed-window draw/save helper. It delegates the actual surface save/fill/draw operations to overlay/helper calls around0x9812. After that call returns, the opener sets[0x0d1d] = 1, prints the formatted text, refreshes text/input areas, and sets[0x0d0f] = 1. code.text.close_window_stateat0x1f2btests[0x0d1d]; when nonzero it calls helper0x560c([0x0d23], [0x0d25]). Helper0x560cloads those packed rectangle words and delegates to overlay restore helper0x980c.- After the conditional restore,
code.text.close_window_statealways clears[0x0d0f]and[0x0d1d]. The existing QEMU probe validates the inactive unconditional[0x0d0f]clear; the active saved-rectangle lifecycle is now source-backed by both the producer at0x1d96and consumer at0x1f2b.
2026-07-04: save-file selector and block-envelope source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x2500 -e 0x2700 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x2700 -e 0x2900 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x85e5 -e 0x87e5 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x8814 -e 0x8a14 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x8a80 -e 0x8c80 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x8794 -e 0x8994 build/cleanroom/AGI.decrypted.exe | sed -n '1,110p'ndisasm -b 16 -o 0x8b9f -e 0x8d9f build/cleanroom/AGI.decrypted.exe | sed -n '1,190p'xxd -g 1 -s 0x1860 -l 0x400 SQ2/AGIDATA.OVL- Local Python scan over
SQ2/SQ2SG.*save files. python3 -B -m unittest tests.test_save_resources
Documented result:
- Rechecked the save and restore handlers at
0x2753and0x2512. The save block table in earlier notes had the first write reversed. Source argument order and the local save files show that the first length-prefixed block is0x05e1bytes from data address0x0002, not two bytes from0x05e1. - The five save-file blocks written by
code.save.write_length_prefixed_blockand read bycode.save.read_length_prefixed_blockare:0x05e1bytes from/to0x0002.[0x096f]bytes from/to[0x096b].[0x0975]bytes from/to[0x0971].[0x0141] * 2bytes from/to[0x1707].0x1364()bytes from/to0x0985.
- The checked-in local SQ2 saves
SQ2/SQ2SG.1throughSQ2/SQ2SG.11all parse as a 31-byte description/header followed by five little-endian length-prefixed blocks. The first four block lengths are fixed in this local corpus:1505,903,328, and200. The fifth block is present and variable-sized (16..28bytes observed). - Added
tools/agi_save.py, a narrow parser for the source-backed save-file envelope. Addedtests/test_save_resources.py; the targeted test run passed four tests and checks all local SQ2 save files, description extraction, truncated-block rejection, and trailing-byte rejection. - Refined
code.save.select_slot_or_pathat0x85e5. It saves prompt-marker visibility, erases the marker, saves/restores text state, stops active sound, sets a text attribute pair, delegates path prompting, scans selectable slots, formats the selected filename into0x1c8c, and returns zero for cancel/no selection. - Labeled selector subhelpers:
code.save.check_drive_or_path_availableat0x86a3.code.save.prompt_path_if_neededat0x8705.code.save.edit_modal_text_fieldat0x8794.code.save.select_numbered_slotat0x8814.code.save.read_slot_summaryat0x8b9f.
code.save.prompt_path_if_neededdisplays the save or restore path prompt when[0x0e72]is empty, edits path buffer0x1962, and validates it through the generic path validator at0x5bdd.code.save.select_numbered_slotscans up to 12 numbered save files, displays descriptions, marks the current row with glyph0x1a, clears the old row with a space, accepts Enter, cancels Escape, and handles movement events1and5as up/down with wrap.- In save mode, accepting an empty-description slot calls
code.save.edit_modal_text_fieldwith prompt text at0x1baaand fills the 31-byte header/description buffer at0x1c6cbefore the save handler creates the file.
2026-07-04: heap allocation and mark/rewind source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x1300 -e 0x1500 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x1480 -e 0x1680 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x0f80 -e 0x1180 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'- Source/local searches over heap globals
0x0a55,0x0a57,0x0a59,0x0a5b,0x0a5d, and0x0a5fin the disassembly notes, docs, tools, and tests.
Documented result:
- The interpreter heap is source-backed as a bump allocator with mark/rewind cleanup. No general free-list behavior has been observed in the allocator helpers.
code.heap.allocateat0x13d6computes available bytes as[0x0a5b] - [0x0a55]. If the requested size is larger, it formats the out-of-memory message at0x09fd, displays it through the text helper, and calls restart/exit helper0x02ae. No recoverable allocation-failure return was observed.- On successful allocation,
0x13d6returns the old current heap pointer, advances[0x0a55]by the requested size, callscode.heap.update_free_memory_varat0x14a0, and updates high-water pointer[0x0a5f]when the new current top exceeds the prior high-water value. code.heap.current_topat0x1430returns[0x0a55].code.heap.rewind_toat0x143cstores a caller-provided pointer in[0x0a55]without refreshing the free-memory byte.code.heap.save_temporary_markat0x144bstores[0x0a55]in[0x0a5d].code.heap.restore_temporary_markat0x145arewinds to[0x0a5d]only when that mark is nonzero, then clears the mark.- Startup calls
code.heap.save_room_reset_markat0x1476after initial object/inventory setup and logic 0 load.code.heap.reset_dynamic_stateat0x1485, used by room switch, restart, and restore paths, frees update-list nodes, clears the temporary mark, restores[0x0a55]from[0x0a59], and refreshes the free-memory byte through0x14a0. code.heap.update_free_memory_varcomputes[0x0a5b] - [0x0a55], stores the high byte in byte variable[0x0011], and returns the full free-byte count.code.heap.show_status_actionat0x14bdformats heap diagnostics from the same globals: heap size[0x0a5b] - [0x0a57], current use[0x0a55] - [0x0a57], maximum use[0x0a5f] - [0x0a57], room/reset mark[0x0a59] - [0x0a57], and resource-event high-water[0x170f].
2026-07-04: restart, restore-failure, and shutdown cleanup source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x02ae -e 0x04ae build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'ndisasm -b 16 -o 0x0240 -e 0x0440 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'ndisasm -b 16 -o 0x2460 -e 0x2660 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x2500 -e 0x2700 build/cleanroom/AGI.decrypted.exe | sed -n '1,240p'ndisasm -b 16 -o 0x2700 -e 0x2900 build/cleanroom/AGI.decrypted.exe | sed -n '80,260p'ndisasm -b 16 -o 0x8240 -e 0x8440 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x8380 -e 0x8580 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x5a20 -e 0x5c20 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'ndisasm -b 16 -o 0x0f80 -e 0x1180 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'- Source/document searches for
0x80,0x86,0x02ae,0x8275, restore-success/failure paths, interrupt-vector cleanup, and log-file handle cleanup.
Addressing note:
- A first exploratory
ndisasmcommand used-etoo broadly.ndisasm -eis a file skip, not an end address. The documented slices above use the project’s existing convention: display image offset with-o IMAGE, and skip to the corresponding EXE file offset with-e IMAGE+0x200.
Documented result:
code.restart.confirm_restart_actionat image0x2472is an in-engine restart, not DOS process termination. It stops sound, clears the prompt/input line, tests flag 16 to optionally skip the confirmation prompt, and only enters the reset block when the confirmation result is nonzero.- On accepted restart,
0x2472erases visible input, saves the current flag 9 state, callscode.heap.reset_dynamic_state(0x1485), callscode.restart.initialize_game_tables(0x0fa5), refreshes display/list state through0x30d6, sets flag 6, restores flag 9 if it was previously set, clears timer/event words[0x0129]and[0x012b], reloads trace logic[0x1d12]if configured, calls menu/list refresh helper0x930e, redraws the prompt marker, and returns zero to the dispatcher. - On canceled restart, the reset block is skipped and
0x2472returns the following bytecode pointer after redrawing the prompt marker. code.system.confirm_exit_actionat image0x027fis the smaller confirmation-gated exit path. It stops sound, exits immediately when operand bytearg0 == 1, or displays message0x05e3and exits only if the display helper returns one.code.system.exit_with_cleanupat image0x02aecallscode.system.shutdown_cleanup(0x8275) and then callscode.system.dos_terminate(0)at0x00ae. The DOS terminate wrapper usesint 21hwithAH=0x4c.code.system.shutdown_cleanupcloses the log file if open throughcode.log.close_if_open(0x838c), restores saved interrupt vectors/timer state through0x849f, then calls the BIOS video-mode wrapper0x5a5ewith mode byte[0x1807].code.system.install_interrupt_hooksat0x83acsaves original vectors and installs interpreter keyboard/timer/critical-error style hooks. The restore helper at0x849frestores saved vectors for interrupts0x1f,0x05,0x08,0x1c,0x09,0x23,0x24, and conditionally0x10, and resets the PIT timer divisor before returning.- Restore action
0x7euses the fatal exit helper for read failure: after any length-prefixed state block read fails, it closes the save file, displays message0x0d87, and callscode.system.exit_with_cleanup. This is not modeled as a recoverable restore error. - Restore success has a different continuation: after all five blocks are read,
it restores display adapter/mode bytes, sets hardware flag 11 for nonzero
adapter kinds, calls
code.restore.replay_resource_events, refreshes display and list state, clears the saved caller return pointer on the stack, and returns zero so execution resumes through the restored state.
2026-07-04: save envelope round-trip and DOS wrapper correction
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,220p' tools/agi_save.pysed -n '1,240p' tests/test_save_resources.pysed -n '680,760p' docs/src/compatibility_testing.mdsed -n '70,190p' docs/src/agi_executable.mdsed -n '290,370p' PROGRESS.mdrg -n "code\\.dos\\.|0x5cef|0x5d12|0x5d35|0x5d6b|0x5db2|0x5e01|0x5e3e|0x5e73" docs/src PROGRESS.md tools testsndisasm -b 16 -o 0x5c80 -e 0x5e80 build/cleanroom/AGI.decrypted.exendisasm -b 16 -o 0x5ca8 -e 0x5ea8 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'python3 -B -m unittest tests.test_save_resources
Documented result:
tools/agi_save.pynow serializes the parsed save-file envelope back to bytes. The serializer requires the same source-backed structure used by the parser: a 31-byte header, exactly five blocks, matching block order, and each block’s stored length matching the number of data bytes. This preserves the interpreter’s envelope instead of inventing a higher-level save format.tests/test_save_resources.pynow checks that all 11 checked-inSQ2/SQ2SG.*files parse and serialize back to identical bytes. The focused save test module ran 6 tests successfully.- The DOS wrapper symbol rows in
docs/src/symbolic_labels.mdwere corrected against disassembly. The previous table had several post-open wrappers mapped to later helper addresses; the corrected source-backed map is:
| Label | Image offset | DOS function / behavior |
|---|---|---|
code.dos.create_file | 0x5cad | AH=0x3c; returns 0xffff on carry/error. |
code.dos.open_file | 0x5cce | AH=0x3d; returns 0xffff on carry/error. |
code.dos.read_file | 0x5cef | AH=0x3f; returns zero on carry/error, so callers check the returned byte count. |
code.dos.write_file | 0x5d12 | AH=0x40; returns zero on carry/error, so callers check the returned byte count. |
code.dos.delete_file | 0x5d35 | AH=0x41; returns zero on carry/error. |
code.dos.close_file | 0x5d52 | AH=0x3e; callers observed so far ignore a close error. |
code.dos.seek_file | 0x5d6b | AH=0x42; returns 0xffff:0xffff in DX:AX on carry/error. |
code.dos.duplicate_handle | 0x5d94 | AH=0x45; returns 0xffff on carry/error. |
code.dos.get_current_directory | 0x5db2 | Writes a leading separator and calls AH=0x47 for the default drive. |
code.dos.get_current_drive_letter | 0x5dea | AH=0x19; returns lowercase a plus the zero-based current-drive number. |
code.dos.find_first | 0x5e01 | Sets DTA with AH=0x1a, then calls AH=0x4e; returns 0xffff on carry/error. |
code.dos.find_next | 0x5e26 | AH=0x4f; returns 0xffff on carry/error. |
code.dos.probe_drive_selectable | 0x5e3e | Tries selecting a lowercase drive letter, checks whether DOS reports it as current, then restores the original drive. |
code.dos.get_file_time | 0x5e73 | AH=0x57, AL=0; selector code uses the returned CX time word. |
code.dos.prepare_call | 0x5e8d | Temporarily switches DS to segment 0x0a01 and clears word [0x184d]. |
- This pass strengthens save-file fixture generation but does not yet prove a dynamic original-engine save/restore round trip from a generated save file.
2026-07-04: save/restore file-error source pass
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x2500 -e 0x2700 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x26a0 -e 0x28a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x2750 -e 0x2950 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'rg -n "0x28c6|0x26b0|0x2753|0x2512|0x0e46|0x0d87|write_length|read_length|save_game_state|restore_game_state|About to save|Error in" docs/src tools tests PROGRESS.mdstrings -a -t x SQ2/AGIDATA.OVL | rg "Can't|Error|About|save|restore|file"xxd -g 1 -s 0xd20 -l 0x160 SQ2/AGIDATA.OVLsed -n '270,300p' docs/src/runtime_model.mdsed -n '1350,1360p' docs/src/logic_bytecode.md
Documented result:
code.save.write_length_prefixed_blockat image0x28c6writes the low length byte, high length byte, and payload separately. It returns 1 only when the two one-byte writes each return 1 and the payload write returns the full requested length.- Save action
0x7dtreats create failure and write failure differently.code.dos.create_filereturning0xffffformats message0x0df0with path buffer0x1962, displays it, restores modal/text state, and returns to the following bytecode. A short header write or failed length-prefixed block closes the handle, deletes filename0x1c8c, displays message0x0e46, restores modal/text state, and also returns to the following bytecode. code.save.read_length_prefixed_blockat image0x26b0reads the low length byte, high length byte, and payload separately. It returns 1 only when the one-byte reads each return 1 and the payload read returns the decoded length.- Restore action
0x7ealso splits open and read failures. Open failure formats/displays message0x0d73, restores modal/text state, and returns to the following bytecode. Any failure after the file has opened and block reads have begun closes the file, displays message0x0d87, and callscode.system.exit_with_cleanup; this path terminates the DOS process. - Local strings confirm the visible prompts:
0x0d73: can’t-open-file message;0x0d87: restoring error followed by an enter-to-quit prompt;0x0df0: directory full or disk write-protected;0x0e46: disk full.
2026-07-04: view-batch right/bottom placement validation
Commands run from /Users/peter/ai/agi/reverse:
rg -n "Picture|picture|view|renderer|Remaining|Highest-Value|fuzz|line|pattern|fill|priority|cel|mirror|clip" PROGRESS.md docs/src/graphics_object_pipeline.md docs/src/picture_resources.md docs/src/view_resources.md docs/src/compatibility_testing.md tests tools | head -n 240ls docs/srcsed -n '1,260p' tests/test_picture_fuzz.pysed -n '1,260p' tests/test_view_batch.pysed -n '1,260p' tools/view_batch.pysed -n '260,620p' tools/view_batch.pyrg -n "right_clip|bottom_clip|left_clip|top_clip|clip" tools/view_batch.py tools/object_overlay_probe.py tests/test_view_batch.py tests/test_object_overlay_probe.py docs/src/compatibility_testing.md docs/src/graphics_object_pipeline.mdsed -n '640,710p' tools/agi_graphics.pypython3 -B -m unittest tests.test_view_batch- Local Python probes using
tools/agi_graphics.render_view_frameandtools/agi_graphics.search_object_placementto compute view 11 frame size and placement-search results. python3 -B tools/view_batch.py --snapshot --dos-prefix VC --output build/view-batch/batches/clip_right_bottom_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case view_011_right_clip --case view_011_bottom_clipsed -n '1,220p' build/view-batch/batches/clip_right_bottom_001.jsonpython3 -B tools/inspect_ppm.py build/view-batch/fixtures/view_011_right_clip/qemu_capture.ppmsed -n '180,235p' tools/object_overlay_probe.pysed -n '340,380p' tools/object_overlay_probe.pypython3 -B tools/view_batch.py --snapshot --dos-prefix VC --output build/view-batch/batches/clip_right_bottom_002.json --boot-wait 5 --draw-wait 8 --stop-on-failure --case view_011_right_clip --case view_011_bottom_clip
Documented result:
- The simple view-batch registry now includes right and bottom edge-placement cases for view 11/group 0/frame 0, in addition to the previous normal, cached, mirrored, left-edge, top-edge, and low-priority cases.
tools/view_batch.pynow supports repeated--case CASE_IDfilters, matching the focused-run pattern used by the other QEMU probe harnesses.- The first focused QEMU run,
clip_right_bottom_001, mismatched the right-edge case. The mismatch was not an original-engine failure; it showed that direct view composition expected a simple right clamp while the original engine routed the transient object throughcode.object.place(0x593a) and its source-backed spiral placement search. tools/view_batch.pynow computes the expected comparison placement withsearch_object_placementby default, with optional expected-position and expected-priority overrides available for future cases.- Local placement probes for view 11/group 0/frame 0 showed:
- request
(150, 80)resolves to placement(140, 71); - request
(20, 170)resolves to placement(23, 167).
- request
- The corrected focused QEMU run,
clip_right_bottom_002, matched both cases with 2 matches, 0 mismatches, and 0 errors.
2026-07-04: real-picture batch harness and base QEMU parity
Commands run from /Users/peter/ai/agi/reverse:
rg -n "compare_picture_capture.py|qemu_fixture.py picture|picture [0-9]|real-resource|real resource|picture_N|PICDIR|all 74|74 valid|render_picture" docs/src tools testssed -n '620,730p' tools/qemu_fixture.pysed -n '1,180p' tools/qemu_fixture.py- Local Python scan of
SQ2/PICDIRto find present picture resources, picture streams that use pattern commands, and the largest valid picture payload. sed -n '1,140p' tests/test_graphics_rendering.pysed -n '40,110p' docs/src/compatibility_testing.md- Added
tools/picture_batch.pyandtests/test_picture_batch.py. python3 -B -m unittest tests.test_picture_batch tests.test_view_batchpython3 -B tools/picture_batch.py --snapshot --dos-prefix PB --output build/picture-batch/batches/picture_base_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure- Escalated rerun of the same
tools/picture_batch.pycommand after the first unprivileged QEMU attempt could not bindvnc=127.0.0.1:5from the sandbox.
Documented result:
tools/picture_batch.pyprovides a reusable QEMU validation batch for real local SQ2 picture resources. It builds picture fixtures withbuild_picture_fixture, can run them serially or from one QEMU snapshot, and writes a JSON report with match/mismatch/error counts.- The base case registry intentionally starts small: picture 1 is the first present local picture resource and includes pattern plotting, while picture 45 is the largest valid local picture payload observed in this corpus.
- The first unprivileged batch attempt failed before the interpreter ran because QEMU could not bind its VNC socket from the sandbox. That is an execution environment failure, not compatibility evidence.
- The escalated
picture_base_001snapshot run matched both base cases:picture_001_first_presentandpicture_045_largest_payloadeach had 0 mismatches over 26,880 logical pixels, for 2 matches, 0 mismatches, and 0 errors overall.
2026-07-04: pattern channel-mask source pass and QEMU visible parity
Commands run from /Users/peter/ai/agi/reverse:
sed -n '230,285p' docs/src/graphics_object_pipeline.mdsed -n '380,430p' tools/agi_graphics.pyndisasm -b 16 -o 0x64e0 -e 0x64e0 build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'ndisasm -b 16 -o 0x5200 -e 0x5200 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'ndisasm -b 16 -o 0x6470 -e 0x6470 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x66a0 -e 0x66a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x54e0 -e 0x54e0 build/cleanroom/AGI.decrypted.exe | sed -n '1,110p'ndisasm -b 16 -o 0x6670 -e 0x6670 build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'ndisasm -b 16 -o 0x66f0 -e 0x66f0 build/cleanroom/AGI.decrypted.exe | sed -n '1,150p'ndisasm -b 16 -o 0x68b0 -e 0x68b0 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'ndisasm -b 16 -o 0x5860 -e 0x5860 build/cleanroom/AGI.decrypted.exe | sed -n '1,90p'sed -n '90,150p' tools/picture_fuzz.pysed -n '270,330p' tests/test_graphics_rendering.pypython3 -B -m unittest tests.test_graphics_rendering tests.test_picture_fuzzpython3 -B tools/picture_fuzz.py generate --count 8 --seed 4097 --output build/picture-fuzz/pattern-channel-cases --cleanpython3 -B tools/picture_fuzz.py batch-qemu --snapshot --corpus build/picture-fuzz/pattern-channel-cases --fixture-root build/picture-fuzz/pattern-channel-fixtures --case base_027_pattern_visual_control_channels --case base_028_pattern_visual_disabled_control_only --case base_029_pattern_control_disabled_visual_only --dos-prefix PC --output build/picture-fuzz/batches/pattern_channel_masks_001.json --boot-wait 5 --draw-wait 8 --stop-on-failurepython3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --clean
Documented result:
- The first pattern-source disassembly window intentionally proved a label
convention detail: existing picture symbols are loaded-image offsets, while
ndisasm -econsumes file offsets. In this file-offset pass, the relevant windows are shifted by0x200; prose continues using the established symbolic loaded-image labels. - Source inspection confirms that pattern helper
code.picture.cmd_pattern_plotand its draw helper do not implement separate channel rules. For every accepted candidate pixel, the helper writes the current X/Y pair into[0x150b]and calls the common pixel writer. The pixel writer selects[0x136d]for odd Y rows or[0x136e]for even Y rows, ORs active draw bits from[0x1369], ANDs with the selected mask, and stores the resulting byte. - In the full 16-color EGA target path,
code.display.map_visual_color_for_adapterreturns withAH == AL, so visual odd/even masks are identical. The parity branch remains part of the implementation model, but visual parity divergence is a non-EGA concern unless it is needed to explain observed SQ2 behavior. - Added curated safe fuzz cases:
base_027_pattern_visual_control_channels: both channels active; local tests assert visual and nondefault control nibbles change together.base_028_pattern_visual_disabled_control_only: visual disabled, control active; local tests assert only the control nibble changes.base_029_pattern_control_disabled_visual_only: control disabled, visual active; local tests assert the default control nibble is preserved.
- A test correction during this pass clarified that the default control nibble
is already
4, so the both-active case uses control class5to make the control-channel change observable in local cell tests. - The QEMU snapshot batch
pattern_channel_masks_001matched the visible EGA surface for all three cases: 3 matches, 0 mismatches, and 0 errors. Screenshots do not expose the control buffer directly, so the control-channel assertions remain source-backed plus local renderer evidence rather than screenshot evidence. - Regenerating the standard deterministic corpus after adding the three cases reports 1,054 total cases and 1,052 safe-for-QEMU cases.
2026-07-04: broad real-picture preset parity
Commands run from /Users/peter/ai/agi/reverse:
- Local Python scan of all valid
PICDIRpayloads, counting local command bytes0xf0..0xfaby picture and listing largest payloads, pattern-heavy pictures, fill-heavy pictures, and broad command-family mixes. - Added
broad_cases,all_present_cases, and preset selection totools/picture_batch.py. - Added preset and discovery tests to
tests/test_picture_batch.py. python3 -B -m unittest tests.test_picture_batchpython3 -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-failuresed -n '1,220p' build/picture-batch/batches/picture_broad_001.json
Documented result:
- The local corpus scan found 74 present valid picture resources. The broad
preset is intentionally representative rather than exhaustive:
- picture 1: first present local picture resource;
- picture 6: early resource with many fills and pattern plots;
- picture 17: all observed picture command families, including multiple pattern-mode changes;
- picture 43: dense large picture with many fills, lines, and pattern plots;
- picture 44: large fill-heavy picture with many control toggles;
- picture 45: largest valid picture payload in the local corpus;
- picture 46: pattern-heavy large picture with broad command-family mix;
- picture 76: high pattern-count resource outside the largest-payload cluster.
tools/picture_batch.py --preset allcan now discover all 74 present valid local picture resources for a future full-corpus QEMU run. The default preset remains the two-case base set so quick checks stay cheap.- The QEMU snapshot batch
picture_broad_001matched all eight broad cases: 8 matches, 0 mismatches, and 0 errors, with each comparison covering 26,880 logical pixels.
2026-07-04: packed picture fixtures and full SQ2 picture parity
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' tools/qemu_snapshot.py- Local Python check of
picture_batch.all_present_cases(), confirming 74 valid present picture cases from the localPICDIR. du -sh SQ2 build/picture-batch/fixturesls -lh build/dos622/dos622.img build/picture-batch/snapshot/picture_batch.raw build/picture-batch/snapshot/picture_batch.qcow2 2>/dev/nullfind build/picture-batch/fixtures/picture_001_first_present -maxdepth 1 -type f -print0 | xargs -0 ls -lhdu -sh build/picture-batch/fixtures/picture_001_first_present build/picture-batch/fixtures/picture_046_pattern_heavymdir -i build/picture-batch/snapshot/picture_batch.raw@@32256 :: | tail -n 20- Added
copy_minimal_picture_treeandbuild_packed_picture_fixturetotools/qemu_fixture.py. - Updated
tools/picture_batch.pyto use packed picture fixtures. - Added packed-fixture structural coverage to
tests/test_qemu_fixture.py. python3 -B -m unittest tests.test_qemu_fixture tests.test_picture_batch- Local Python packed-fixture size probe for pictures 1 and 45.
python3 -B tools/picture_batch.py --preset base --snapshot --fixture-root build/picture-batch/packed-fixtures --dos-prefix PP --output build/picture-batch/batches/picture_base_packed_001.json --boot-wait 5 --draw-wait 8 --stop-on-failurepython3 -B tools/picture_batch.py --preset all --snapshot --fixture-root build/picture-batch/all-fixtures --dos-prefix PA --output build/picture-batch/batches/picture_all_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
Documented result:
- A direct all-picture batch using full copied SQ2 fixture directories would put too much pressure on the 64 MB DOS snapshot image. One full picture fixture directory was about 1.7 MB because it included the original volumes and saved games, so 74 copies would exceed the image before filesystem overhead.
- Packed picture fixtures solve that for picture-only parity checks. They copy
the minimal engine/support files, write generated
LOGIC.0intoVOL.3, append the tested local picture payload as the nextVOL.3record, patchLOGDIR[0]to the generated logic, and patch the selectedPICDIRentry to the appended picture record. A packed picture fixture for pictures 1 or 45 is roughly 72 KB of input files. - The packed fixture preserves the tested original picture payload while
avoiding duplicate copies of unrelated resource volumes. The original one-off
qemu_fixture.py picturecommand still uses the historical full-tree fixture path;picture_batch.pynow uses packed fixtures for batch throughput. - Packed base QEMU batch
picture_base_packed_001matched pictures 1 and 45 with 2 matches, 0 mismatches, and 0 errors, validating that the original engine accepts the trimmed picture fixture layout. - Full present-picture QEMU batch
picture_all_001matched all 74 valid local SQ2 picture resources with 74 matches, 0 mismatches, and 0 errors. - User feedback during this long run identified an important future throughput
direction: for resource sweeps, generate a single in-engine carousel fixture
that displays the next resource after input or a timed event, then drive it
from QEMU with
screendumpplus key sends. The isolated one-process-per-case snapshot harness remains the simplest reference oracle, but carousel-style sweeps should become part of the infrastructure before comparing many games and interpreter versions. - If future fixture sets genuinely need more DOS disk space, the right answer
is a larger formatted/bootable DOS test image or a purpose-built large fixture
image. Appending bytes to
build/dos622/dos622.imgwould not by itself help, because the FAT partition geometry inside the image would still describe the old volume.
2026-07-04: picture carousel prototype
Commands run from /Users/peter/ai/agi/reverse:
- Added carousel bytecode helpers and
build_picture_carousel_fixturetotools/qemu_fixture.py. - Added
tools/picture_carousel.py. - Added
tests/test_picture_carousel.pyand carousel structural coverage intests/test_qemu_fixture.py. python3 -B -m unittest tests.test_qemu_fixture tests.test_picture_carouselpython3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_001.json --boot-wait 5 --first-wait 8 --advance-wait 2python3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_002.json --boot-wait 5 --first-wait 8 --advance-wait 2python3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_003.json --boot-wait 5 --first-wait 8 --advance-wait 2python3 -B tools/picture_carousel.py --preset broad --fixture-root build/picture-carousel/broad-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_broad_001.json --boot-wait 5 --first-wait 8 --advance-wait 2python3 -B tools/picture_carousel.py --preset broad --fixture-root build/picture-carousel/broad-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_broad_002.json --boot-wait 5 --first-wait 8 --advance-wait 8- Manual four-picture case file
build/picture-carousel/manual_four_cases.json. python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_001.json --boot-wait 5 --first-wait 8 --advance-wait 4python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_002.json --boot-wait 5 --first-wait 8 --advance-wait 4python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_keys_001.json --boot-wait 5 --first-wait 8 --advance-wait 4 --advance-key x,y,zpython3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_mapped_001.json --boot-wait 5 --first-wait 8 --advance-wait 4python3 -B tools/picture_carousel.py --cases build/picture-carousel/manual_four_cases.json --fixture-root build/picture-carousel/manual-four-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_manual_four_fkeys_001.json --boot-wait 5 --first-wait 8 --advance-wait 4python3 -B tools/picture_carousel.py --preset base --fixture-root build/picture-carousel/base-fixtures --dos-dir PICSWEEP --output build/picture-carousel/batches/picture_carousel_base_mapped_fkey_001.json --boot-wait 5 --first-wait 8 --advance-wait 4
Documented result:
tools/picture_carousel.pybuilds one packed fixture containing multiple picture payloads, launches one engine process, captures the first picture, sends an advance key, waits, captures the next picture, and compares each capture to the local renderer.- The first raw-key implementation matched the first capture but failed the
second because it cleared the wrong byte variable. The raw-key predicate
caches at absolute
[0x001c]; because script variables start at[0x0009], the matching script variable index is0x13, not0x1c. Clearing the wrong slot let a single key event advance twice and wrap back to picture 1. - Correcting that raw-key cache clear made the two-picture base carousel
picture_carousel_base_003pass with 2 matches and 0 mismatches. - Longer raw-key carousels still stalled after the third displayed picture.
Changing the persistent carousel index from high variable
v249tov32did not change that behavior. - The mapped-key/status-byte variant uses action
0x79to map advance keys to unique status bytes and tests(index == n) && status[n]. Printable keys and function-key mappings both still failed broader four/eight-picture sweeps: later captures either remained on the previous picture or showed the expected picture with a visible input/message-window artifact in logical rectangle approximatelyx=35..124,y=67..92. - The current mapped function-key base smoke
picture_carousel_base_mapped_fkey_001passed with 2 matches and 0 mismatches from one engine process. This proves the general packed-carousel fixture can work, but the advance/ack strategy is not robust enough yet for broad compatibility evidence. - Keep
tools/picture_carousel.pyas infrastructure prototype and unit-tested scaffolding. Before using it for cross-game sweeps, it needs a deterministic way to advance multiple resources without parser/UI side effects and with a reliable acknowledgement that the next picture has completed drawing.
2026-07-04: timed polling picture carousel and speed variable
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x0150 -e 0x0350 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'ndisasm -b 16 -o 0x7f60 -e 0x8160 build/cleanroom/AGI.decrypted.exe | sed -n '1,120p'- Local message scan over readable SQ2 logic resources for
speed,fast, andslow. - Added timed-carousel helpers to
tools/qemu_fixture.py. - Added timed and polling modes to
tools/picture_carousel.py. python3 -B -m unittest tests.test_qemu_fixture tests.test_picture_carouselpython3 -B tools/picture_carousel.py --preset base --mode timed --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-base-fixtures --dos-dir PICTIME --output build/picture-carousel/batches/picture_carousel_base_timed_001.json --boot-wait 5 --first-wait 5 --advance-wait 7python3 -B tools/picture_carousel.py --preset broad --mode timed --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-fixtures --dos-dir PICTIME --output build/picture-carousel/batches/picture_carousel_broad_timed_001.json --boot-wait 5 --first-wait 5 --advance-wait 7 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad.qcow2python3 -B tools/picture_carousel.py --preset broad --case picture_017_full_command_mix --case picture_043_dense_large_fill_pattern --mode timed --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-17-43-fixtures --dos-dir PT1743 --output build/picture-carousel/batches/picture_carousel_17_43_timed_001.json --boot-wait 5 --first-wait 5 --advance-wait 7 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_17_43.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_17_43.qcow2python3 -B tools/picture_carousel.py --preset broad --mode timed --delay-cycles 240 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-fixtures-v4 --dos-dir PICTIME --output build/picture-carousel/batches/picture_carousel_broad_timed_004.json --boot-wait 5 --first-wait 5 --advance-wait 7 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad_v4.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad_v4.qcow2python3 -B tools/picture_carousel.py --preset broad --mode timed --poll --delay-cycles 240 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-poll-fixtures --dos-dir PICPOLL --output build/picture-carousel/batches/picture_carousel_broad_timed_poll_001.json --boot-wait 5 --first-wait 3 --poll-interval 1 --poll-timeout 25 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad_poll.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad_poll.qcow2python3 -B tools/picture_carousel.py --preset broad --mode timed --poll --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-poll-fast-fixtures --dos-dir PICPOLL --output build/picture-carousel/batches/picture_carousel_broad_timed_poll_fast_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.5 --poll-timeout 15 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad_poll_fast.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad_poll_fast.qcow2python3 -B tools/picture_carousel.py --preset broad --mode timed --poll --delay-cycles 60 --speed-value 1 --fixture-root build/picture-carousel/timed-broad-poll-faster-fixtures --dos-dir PICPOLL --output build/picture-carousel/batches/picture_carousel_broad_timed_poll_faster_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.25 --poll-timeout 10 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_broad_poll_faster.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_broad_poll_faster.qcow2
Documented result:
- The local SQ2 logic message scan did not find obvious script-level speed-menu text, so the speed investigation moved to the executable.
code.engine.main_cyclecalls helper0x7f78near the start of each cycle. That helper reads byteDS:0x0013, which is script variablev10because byte variables start atDS:0x0009, spins until word[0x1784]is greater than or equal to that value, then clears[0x1784]. Settingv10lower makes the top-level cycle run faster; timed-carousel fixtures usev10 = 1as a fast but capturable pace.- The timed carousel avoids parser/key-event side effects by advancing after a generated per-cycle counter rather than a keyboard event. It also sets flag 7 before generated picture loads to suppress resource-event pair recording during the artificial sweep.
- Fixed-sleep timed captures were brittle. With
delay-cycles 120, a broad run captured pictures1,17,43,44,46,76,76,76; withdelay-cycles 240, it captured1,6,6,17,43,43,44,45. These were cadence misses: identity checks found exact matches to other broad-preset pictures. - Reordering each transition to update carousel state before discarding the old picture let the timed sequence advance through the whole broad list, but fixed sleeps still drifted as picture load/draw time varied.
- Polling mode solves the cadence problem for this use case. It repeatedly
captures a QEMU
screendumpand compares it to the expected local render until that expected picture appears, then moves to the next expected picture. - Timed polling run
picture_carousel_broad_timed_poll_001matched all eight broad pictures withdelay-cycles 240,speed-value 1, and 0 mismatches. - Faster timed polling run
picture_carousel_broad_timed_poll_fast_001also matched all eight broad pictures withdelay-cycles 120,speed-value 1, 0.5-second polling, and 0 mismatches. This is the current recommended one-engine broad picture sweep. delay-cycles 60was too short for reliable polling and missed all intermediate broad pictures except the final picture 76. Do not use that as a default for broad resource sweeps.
2026-07-04: all-picture timed polling carousel chunking
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/picture_carousel.py --preset all --mode timed --poll --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-all-poll-fixtures --dos-dir PICALL --output build/picture-carousel/batches/picture_carousel_all_timed_poll_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.5 --poll-timeout 20 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_all_poll.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_all_poll.qcow2- Local identity comparison of mismatched captures against all present local SQ2 picture renders.
magick build/picture-carousel/timed-all-poll-fixtures/carousel/qemu_picture_020.ppm build/picture-carousel/timed-all-poll-fixtures/carousel/qemu_picture_020.pngmdir -i build/picture-carousel/snapshot/picture_carousel_all_poll.raw@@32256 ::/PICALLxxd -g 1 -s 61353 -l 32 build/picture-carousel/timed-all-poll-fixtures/carousel/VOL.3xxd -g 1 -s 60 -l 9 build/picture-carousel/timed-all-poll-fixtures/carousel/PICDIR- Added
--chunk-sizesupport and per-case polling progress totools/picture_carousel.py. - Added chunking coverage to
tests/test_picture_carousel.py. python3 -B -m unittest tests.test_picture_carouselpython3 -B tools/picture_carousel.py --preset all --mode timed --poll --chunk-size 16 --delay-cycles 120 --speed-value 1 --fixture-root build/picture-carousel/timed-all-poll-chunk16-fixtures --dos-dir PICALL --output build/picture-carousel/batches/picture_carousel_all_timed_poll_chunk16_001.json --boot-wait 5 --first-wait 3 --poll-interval 0.5 --poll-timeout 20 --snapshot-raw build/picture-carousel/snapshot/picture_carousel_all_poll_chunk16.raw --snapshot-qcow build/picture-carousel/snapshot/picture_carousel_all_poll_chunk16.qcow2
Documented result:
- A single all-present-picture timed polling carousel matched the first 19 pictures, then all later expected captures were closest to picture 19.
- Inspecting the picture 20 capture showed the original engine’s disk prompt:
Please insert disk 3 and press ENTER.The prompt was drawn over picture 19. - The generated fixture disk still contained
VOL.3inC:\PICALL, and that file size was 180,430 bytes. The packed picture 20 record at offset0xefa9began with the expected record header bytes12 34 03 86 0e, and thePICDIRentry for picture 20 was30 ef a9. This rules out the simple fixture-copy/header explanation for the prompt. - Treat this as an original-engine resource lifecycle or disk-prompt boundary for oversized generated carousel fixtures, not as renderer behavior to model in the clean-room spec.
- The chunked all-present-picture timed polling run used chunks of 16 pictures and matched all 74 valid local SQ2 pictures with 74 matches, 0 mismatches, and 0 errors across five engine launches.
- Chunking is the current recommended path for larger real-resource carousel sweeps: it preserves the faster timed polling workflow while avoiding the oversized single-fixture prompt boundary.
2026-07-04: dynamic save-write probe
Commands run from /Users/peter/ai/agi/reverse:
rg -n "save|restore|file-error|file error|0x7d|0x7e|save_game|restore_game|SAVE" tools tests docs/src PROGRESS.mdsed -n '740,825p' docs/src/compatibility_testing.mdsed -n '1,260p' tools/agi_save.pysed -n '1,320p' tools/qemu_fixture.pysed -n '1,320p' tools/qemu_snapshot.py- Added
tools/save_roundtrip_probe.py. - Added
tests/test_save_roundtrip_probe.py. python3 -B -m unittest tests.test_save_roundtrip_probepython3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_001.json --boot-wait 5 --draw-wait 8 --post-launch-wait 2 --post-launch-after-text-wait 1 --key-delay 0.08python3 -B tools/save_roundtrip_probe.py --keys $'codex probe\n' --output build/save-roundtrip/save_roundtrip_002.json --capture build/save-roundtrip/qemu_capture_002.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_002.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_002.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_002.raw --save-output build/save-roundtrip/SQ2SG_002.1 --boot-wait 5 --draw-wait 8 --post-launch-wait 5 --post-launch-after-text-wait 1 --key-delay 0.08magick build/save-roundtrip/qemu_capture_002.ppm build/save-roundtrip/qemu_capture_002.pngmdir -i build/save-roundtrip/snapshot/save_roundtrip_after_002.raw@@32256 ::/SVRTpython3 -B tools/save_roundtrip_probe.py --description abc --no-submit-description --output build/save-roundtrip/save_roundtrip_debug_no_submit.json --capture build/save-roundtrip/qemu_capture_no_submit.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_no_submit.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_no_submit.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_no_submit.raw --save-output build/save-roundtrip/SQ2SG_no_submit.1 --boot-wait 5 --draw-wait 4 --path-prompt-wait 2 --description-wait 3 --key-delay 0.12python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_005.json --capture build/save-roundtrip/qemu_capture_005.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_005.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_005.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_005.raw --save-output build/save-roundtrip/SQ2SG_005.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --key-delay 0.08python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_006.json --capture build/save-roundtrip/qemu_capture_006.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_006.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_006.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_006.raw --save-output build/save-roundtrip/SQ2SG_006.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08mdir -i build/save-roundtrip/snapshot/save_roundtrip_after_006.raw@@32256 ::/SVRTmcopy -o -i build/save-roundtrip/snapshot/save_roundtrip_after_006.raw@@32256 ::/SVRT/SG.1 build/save-roundtrip/SG_006.1python3 -B -c "import sys; from pathlib import Path; sys.path.insert(0, 'tools'); from agi_save import load_save; s=load_save(Path('build/save-roundtrip/SG_006.1')); print(s.description); print([b.length for b in s.blocks]); print(Path('build/save-roundtrip/SG_006.1').stat().st_size)"python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_007.json --capture build/save-roundtrip/qemu_capture_007.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_007.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_007.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_007.raw --save-output build/save-roundtrip/SG_007.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08
Documented result:
- The new probe builds a synthetic fixture that removes existing save files,
displays a blank picture, loads view 11, calls action
0x7d, redraws the picture, draws view 11 at X 50 after the save action returns, and loops. - The first one-shot key sequence did not write a save. Its capture showed the save-description prompt with a blank input line, proving the original action had reached the save UI but the typed description had not landed in the correct editor.
- A no-leading-Enter sequence appended
codex probeto the path prompt and produced the visible messageThere is no directory named \SVRTcodex probe.This established that the first save UI stage is path acceptance. - A no-submit debug run after accepting the path showed the numbered save-slot selector. This established the second UI stage: select a slot before typing the description.
- After adding path, slot, description, and final confirmation stages, the
original engine wrote
SG.1in the syntheticC:\SVRTfixture directory. The name lacks theSQ2prefix seen in the checked-inSQ2/SQ2SG.*files; the probe therefore exposes--save-stemfor future game/interpreter runs. - The extracted dynamic save file parsed through
tools/agi_save.pywith descriptioncodex probe, block lengths1505,903,328,100, and12, and total size 2889 bytes. - The fourth block length differs from the checked-in local SQ2 saves’
observed
200byte fourth block. The source-backed writer uses[0x0141] * 2, so this dynamic fixture’s shorter object/list state implies a runtime count of 50 entries for the generated no-save scenario. - The post-save QEMU capture matched the expected validation screen with 0 visual mismatches, proving that the save action returned to following bytecode after the original engine wrote the file.
- This section establishes dynamic save-write evidence. The following section records the restore probe built from the generated save bytes.
2026-07-04: dynamic restore probe from generated save
Commands run from /Users/peter/ai/agi/reverse:
- Extended
tools/save_roundtrip_probe.pywith restore fixture generation and--mode restore. - Added restore fixture coverage to
tests/test_save_roundtrip_probe.py. python3 -B -m unittest tests.test_save_roundtrip_probepython3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SG_007.1 --output build/save-roundtrip/restore_roundtrip_001.json --fixture build/save-roundtrip/restore-fixture --dos-dir RSVT --capture build/save-roundtrip/restore_capture_001.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_001.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_001.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --confirmation-wait 1 --key-delay 0.08magick build/save-roundtrip/restore_capture_001.ppm build/save-roundtrip/restore_capture_001.pngpython3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SG_007.1 --output build/save-roundtrip/restore_roundtrip_002.json --fixture build/save-roundtrip/restore-fixture --dos-dir RSVT --capture build/save-roundtrip/restore_capture_002.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_002.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_002.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 5 --slot-wait 1 --confirmation-wait 1 --key-delay 0.08mdir -i build/save-roundtrip/snapshot/restore_roundtrip_002.raw@@32256 ::/RSVTpython3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SG_007.1 --save-stem SQ2SG --output build/save-roundtrip/restore_roundtrip_sq2stem_001.json --fixture build/save-roundtrip/restore-fixture-sq2stem --dos-dir RSV2 --capture build/save-roundtrip/restore_capture_sq2stem_001.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_001.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_001.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 5 --slot-wait 1 --confirmation-wait 1 --key-delay 0.08- Source rereads around
0x5b73,0x8b9f,0x85e5, and0x2512showed thatDS:0x0002supplies both the filename prefix in%s%s%ssg.%dand the saved-state signature checked by the slot summary reader. - Updated
tools/save_roundtrip_probe.pyso generated fixtures call0x8f verify_game_signaturewith messageSQ2before save/restore, then reran:python3 -B tools/save_roundtrip_probe.py --output build/save-roundtrip/save_roundtrip_010.json --capture build/save-roundtrip/qemu_capture_010.ppm --snapshot-raw build/save-roundtrip/snapshot/save_roundtrip_010.raw --snapshot-qcow build/save-roundtrip/snapshot/save_roundtrip_010.qcow2 --post-run-raw build/save-roundtrip/snapshot/save_roundtrip_after_010.raw --save-output build/save-roundtrip/SQ2SG_010.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08 - Updated the restore fixture oracle so success is distinguished from ordinary
continuation after
0x7e: the save fixture sets a packed flag and validation variables with X=50, while the restore fixture starts with X=90 and draws from restored variables only when the saved flag is present. Reran:python3 -B tools/save_roundtrip_probe.py --mode restore --save-input build/save-roundtrip/SQ2SG_010.1 --output build/save-roundtrip/restore_roundtrip_sq2stem_006.json --fixture build/save-roundtrip/restore-fixture-signed --dos-dir RST6 --capture build/save-roundtrip/restore_capture_sq2stem_006.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_006.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_roundtrip_sq2stem_006.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 8 --path-keys $'\n\n' --slot-wait 2 --slot-keys $'\n\n' --confirmation-wait 1 --confirmation-keys $'\n\n' --key-delay 0.12
Documented result:
- The first restore fixture logic used the same byte layout as the save fixture,
but the action byte was
0x7einstead of0x7d. That proved too weak as a restore oracle: source at0x2512..0x26afshows a successful restore returns zero and ends the current logic stream, while cancel/open-failure paths can continue after0x7e. - The first restore run copied the generated save bytes into the fixture as
SG.1, matching the filename that the synthetic save action wrote. The capture stayed at the restore path prompt and mismatched the validation screen. - Increasing the path prompt wait did not change that result. The fixture disk
did contain
SG.1, so the likely failure was filename selection rather than missing copied data. - Copying the same generated save bytes into the restore fixture as
SQ2SG.1made the old restore selector advance, but the later X=90/X=50 oracle showed that this was still just the continuation path, not a proven restore. - Source explains the filename-stem behavior: action
0x8fcopies a logic message intoDS:0x0002and verifies it against the embeddedSQ2string; formatter0x5b73then usesDS:0x0002as the third%sin%s%s%ssg.%d; slot summary reader0x8b9falso compares the first payload bytes of a candidate save against the same string. The early synthetic fixture skipped0x8f, so it saved a blank-prefixSG.1whose state block started with zeroes. The corrected fixture calls0x8f("SQ2"), writesSQ2SG.1, and the first state block starts53 51 32 00. restore_roundtrip_sq2stem_006is the first restore probe in this sequence that proves actual restored state: it matched the X=50 branch gated by the restored flag/variables with 0 visual mismatches. A failure/cancel return after0x7edraws X=90 and mismatches the X=50 oracle.
2026-07-04: dynamic restore read-error UI probe
Commands run from /Users/peter/ai/agi/reverse:
- Extended
tools/save_roundtrip_probe.pywith--mode restore-read-error. The generated fixture writesSQ2SG.1as a malformed save that is still selector-visible: a 31-byte description, declared first-block length0x05e1, and seven payload bytes53 51 32 00 00 00 00. - Added fixture-shape tests to
tests/test_save_roundtrip_probe.py. python3 -B -m unittest tests.test_save_roundtrip_probe- First broad-key run, which sent redundant Enters and therefore captured DOS
after the fatal dialog was dismissed:
python3 -B tools/save_roundtrip_probe.py --mode restore-read-error --output build/save-roundtrip/restore_read_error_001.json --fixture build/save-roundtrip/restore-read-error-fixture --dos-dir RERR --capture build/save-roundtrip/restore_read_error_001.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_read_error_001.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_read_error_001.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 8 --path-keys $'\n\n' --slot-wait 2 --slot-keys $'\n\n' --confirmation-wait 1 --confirmation-keys $'\n' --key-delay 0.12 - Prompt-timing captures:
restore_read_error_prompt_001,restore_read_error_slot_001,restore_read_error_after_slot_001, andrestore_read_error_exact_001. - Stable capture using exactly one Enter per prompt:
python3 -B tools/save_roundtrip_probe.py --mode restore-read-error --output build/save-roundtrip/restore_read_error_002.json --fixture build/save-roundtrip/restore-read-error-fixture --dos-dir RERR --capture build/save-roundtrip/restore_read_error_002.ppm --snapshot-raw build/save-roundtrip/snapshot/restore_read_error_002.raw --snapshot-qcow build/save-roundtrip/snapshot/restore_read_error_002.qcow2 --boot-wait 5 --draw-wait 8 --path-prompt-wait 8 --path-keys $'\n' --slot-wait 2 --slot-keys $'\n' --confirmation-wait 1 --confirmation-keys $'\n' --key-delay 0.12 python3 -B tools/inspect_ppm.py build/save-roundtrip/restore_read_error_002.ppmmagick build/save-roundtrip/restore_read_error_002.ppm build/save-roundtrip/restore_read_error_002.png
Documented result:
- The malformed
SQ2SG.1is 40 bytes long and intentionally cannot parse as a complete save envelope. It is still valid enough for the selector summary path: after the 31-byte description,code.save.read_slot_summaryskips the first length prefix and reads the seven available payload bytes, which match theSQ2signature prefix established by0x8f. - The timing captures show the expected UI sequence: restore directory prompt,
selector row with description
codex probe, confirmation dialog naming\rerr\sq2sg.1, then the read-error dialog. - The stable final capture remains on the read-error dialog after an 8-second
wait, proving that the dialog waits for an Enter rather than immediately
exiting. Its text is
Error in restoring game. Press ENTER to quit. restore_read_error_002.ppmhas geometry640x400, RGB SHA-256556971f26fc34deb32497a9d10c08eedeb28f6bdb0957cd7676a8ef26830849c, 3 unique colors, and non-background bounding box(0, 136, 639, 399). Sending extra Enters after confirmation dismisses this fatal dialog and leaves the process back at DOS, explaining the first failed capture.
2026-07-04: heap startup initialization source pass
Commands run from /Users/peter/ai/agi/reverse:
grep -n "heap initialization\|heap init\|0x0a55\|0x0a57\|0x1476\|0x1485\|0x13d6" docs/src/clean_room_executable_notes.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/agi_executable.mdndisasm -b 16 -o 0x13a0 -e 0x13a0 build/cleanroom/AGI.decrypted.exe | sed -n '1,220p'rg -n "0a55|0a57|0a5b|0a5f|0a59|heap\\.base|heap\\.limit|current_top" build docs/src tools tests- Local byte-pattern scan over
build/cleanroom/AGI.decrypted.exefor writes to0x0a55,0x0a57,0x0a5b, and0x0a5f. ndisasm -b 16 -o 0x1600 -e 0x1600 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x43d0 -e 0x43d0 build/cleanroom/AGI.decrypted.exe | sed -n '1,160p'xxd -g 1 -s 0x1620 -l 0x90 build/cleanroom/AGI.decrypted.exexxd -g 1 -s 0x4400 -l 0x60 build/cleanroom/AGI.decrypted.exe
Documented result:
- The existing heap helper labels remain correct for allocator behavior:
[0x0a55]is the current top,[0x0a57]is the base,[0x0a59]is the room/reset mark,[0x0a5b]is the limit,[0x0a5d]is the temporary mark, and[0x0a5f]is the high-water pointer. The helper cluster around image0x1600includes the direct rewind, temporary mark, room/reset mark, reset, free-memory-byte update, and heap-status display paths already documented in earlier notes. - Startup memory setup around image
0x43eais the missing initialization source. It computes memory sizing globals, resizes/probes the resident block with DOSAH=4a, requests a runtime memory block with DOSAH=48h, and on success converts the returned segment into a DS-relative byte offset by subtracting0x0a01and shifting left four bits. - That converted offset is stored into both
[0x0a55]and[0x0a57], so the heap current pointer initially equals the heap base. The limit is then computed from word[0x112c]as([0x112c] << 4) + [0x0a57]and stored into[0x0a5b]. - If either DOS allocation in this startup routine fails, the path displays a startup memory error and terminates through DOS rather than entering the interpreter with a partial heap.
2026-07-04: sound hardware-output source pass
Commands run from /Users/peter/ai/agi/reverse:
grep -n "sound\|tone\|attenuation\|speaker\|driver\|0x80f3\|0x1790\|0x1806" docs/src/clean_room_executable_notes.md docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/logic_bytecode.md docs/src/compatibility_testing.mdndisasm -b 16 -o 0x80e0 -e 0x80e0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x8150 -e 0x8150 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'ndisasm -b 16 -o 0x7f80 -e 0x7f80 build/cleanroom/AGI.decrypted.exe | sed -n '1,260p'ndisasm -b 16 -o 0x82c0 -e 0x82c0 build/cleanroom/AGI.decrypted.exe | sed -n '1,180p'- Added
pc_speaker_divisor()andpc_speaker_event_enabled()totools/agi_sound.py. python3 -B -m unittest tests.test_sound_resources
Documented result:
- The already documented channel scheduling remains the portable gameplay contract: active channel set, countdown/event timing, per-channel attenuation nibble, and completion flag. The hardware-output pass adds only the source-backed driver-interface details.
- On hardware selectors
0and8,code.sound.driver_write_toneuses the PIT/PC-speaker path. If the current attenuation nibble is0x0f, it clears bits0and1of port0x61. Otherwise it computes a divisor from the 16-bit tone word as12 * (((tone_word & 0x3f) << 4) + ((tone_word >> 8) & 0x0f)), writes mode byte0xb6to port0x43, writes the low and high divisor bytes to port0x42, and sets bits0and1of port0x61. - On stop, the selector
0/8path calls the same tone helper with a silence control byte, while other selector paths write bytes0x9f,0xbf,0xdf, and0xffto port0xc0. - For non-
0/8selectors,code.sound.driver_write_tonewrites encoded tone/control bytes to port0xc0: it writes the high tone byte, and writes the low tone byte unless the high byte’s top three bits are all set. Selector2first applies the small control-byte adjustment at helper0x8345. code.sound.driver_write_attenuationmaintains the low-nibble attenuation value, applies a per-channel envelope/delta table when active, adjusts selector2attenuation values below 8 upward by 2, combines the low nibble with a stored high-nibble channel mask, and writes the result to port0xc0.- The new local test locks down the source formula for sound 1’s first event:
tone word
0x8037produces PC-speaker divisor10560, and its control byte0x9fhas attenuation nibble0x0f, so it is silent on the PC-speaker gate.
2026-07-04: sound attenuation envelope source model
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x8362)) count=360 2>/dev/null | ndisasm -b 16 -o 0x8162 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x82f3)) count=140 2>/dev/null | ndisasm -b 16 -o 0x80f3 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x8196)) count=180 2>/dev/null | ndisasm -b 16 -o 0x7f96 -xxd -g 1 -s 0x17b8 -l 72 SQ2/AGIDATA.OVLpython3 -B -m unittest tests.test_sound_resources
Documented result:
code.sound.driver_startinitializes each channel’s envelope table pointer at[0x17b0..0x17b7]to0x17b8, each envelope index at[0x17a0..0x17a7]to0xffff, and the per-channel active words to0xffff.- On event reads,
code.sound.driver_tickresets envelope index+0x17a0to zero for channels withBX != 6before reading a new duration/tone/control record. Channel 3 (BX == 6) keeps its current envelope index across event reads in the observed source. code.sound.driver_write_attenuation(0x8162) reads the base attenuation byte from[BX+0x17a8]. If it is0x0f, the helper skips envelope and selector-2 adjustment and writes the silent low nibble with the channel mask.- If the base attenuation is not
0x0fand envelope index[BX+0x17a0]is not0xffff, the helper reads one byte from table pointer[BX+0x17b0]at that index. Byte0x80disables the envelope and copies previous envelope value[BX+0x17a9]into the base attenuation byte. Other bytes are applied as signed-ish deltas from the base attenuation, not cumulative deltas from the previous envelope value; negative underflow clamps to zero and positive overflow clamps to0x0f. The clamped value is stored in[BX+0x17a9]. - After envelope processing, the helper adds runtime byte
[0x0020]and clamps to0x0f. Hardware selector2then raises non-silent attenuation values below8by2. Finally it ORs the low nibble with the high channel mask from[BX+0x17fc]and writes the result to port0xc0. - The default table at
0x17b8beginsfe fd fe ff 00 00 01 01 ...and terminates with0x80. The observed channel masks are0x90,0xb0,0xd0, and0xf0. - Added
SoundAttenuationState,SoundAttenuationOutput,default_attenuation_envelope(),sound_channel_output_mask(), andsound_attenuation_output()totools/agi_sound.py. Local tests cover the source table bytes, channel masks, selector-2 adjustment, delta clamps, and0x80terminator behavior.
2026-07-04: source-backed opcode dynamic-probe audit
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1080,1112p' docs/src/compatibility_testing.mdsed -n '1268,1298p' docs/src/compatibility_testing.mdrg -n "0x6e|0x83|0x8e|0xaa|0xad|source-backed" docs/src/logic_bytecode.md docs/src/runtime_model.md docs/src/compatibility_testing.md PROGRESS.mdsed -n '387,397p' PROGRESS.md
Documented result:
- The five non-QEMU-validated action rows are intentionally source-backed for the current full-EGA spec target rather than unfinished core semantics.
0x6e(shake_screen_like) is source-backed for its CRT/display-register timing loop. The existing dispatch smoke proves bytecode continuation, but a screenshot is not a useful portable oracle for the transient hardware effect.0x83(clear_global_0139) is source-backed at the main-cycle mirror point. Logic script writes occur after the pre-logic mirror and can be overwritten by the next restore path, so a bytecode-only QEMU fixture would mostly prove the harness timing rather than the interpreter contract.0x8e(set_global_0141_and_refresh) resets the event-pair capacity state. The downstream save/restore replay behavior is already QEMU-backed through0xab/0xac; probing the raw reset directly would require a narrow internal state hook.0xaa(copy_save_description_to_string_slot) copies from runtime data segment buffer0x0e72; the earlier failed staticAGIDATA.OVLpatch did not populate that runtime buffer. A representative dynamic probe would need to drive the save/restore selector that fills it.0xad(increment_global_1530) is source-backed in the keyboard IRQ release gate. A direct QEMU fixture would depend on raw scan-code release timing rather than a stable high-level gameplay observable.- Updated
PROGRESS.mdanddocs/src/compatibility_testing.mdso these rows are no longer treated as high-value dynamic-probe work unless a future harness can drive their runtime state directly.
2026-07-04: update-list draw-order source model
Commands run from /Users/peter/ai/agi/reverse:
rg -n "draw ordering|draw order|update-list|update list|dirty|persistent|0x6a54|0x6a8e|0x16ff|0x1703|0x57cf|0x9db6|0x9e35" docs/src/graphics_object_pipeline.md docs/src/runtime_model.md docs/src/symbolic_labels.md tools tests- Initial exploratory
ndisasmwindows around0x69f0,0x5700, and0x9d80; the first executable windows were discarded becausendisasm -ewas again easy to misread as an end address rather than a file skip. - Corrected source slices with the EXE header offset included:
ndisasm -b 16 -o 0x6a20 -e 0x6c20 build/cleanroom/AGI.decrypted.exe,ndisasm -b 16 -o 0x5728 -e 0x5928 build/cleanroom/AGI.decrypted.exe,ndisasm -b 16 -o 0x0358 -e 0x0558 build/cleanroom/AGI.decrypted.exe,ndisasm -b 16 -o 0x042f -e 0x062f build/cleanroom/AGI.decrypted.exe,ndisasm -b 16 -o 0x045e -e 0x065e build/cleanroom/AGI.decrypted.exe, andndisasm -b 16 -o 0x4cbb -e 0x4ebb build/cleanroom/AGI.decrypted.exe. rg -n "0x1322|1322|127a|priority_table|data\\.priority|0x124a" docs/src/symbolic_labels.md docs/src/graphics_object_pipeline.md docs/src/runtime_model.md toolsxxd -g 1 -s 0x1240 -l 0x20 SQ2/AGIDATA.OVLxxd -g 1 -s 0x1320 -l 0x20 SQ2/AGIDATA.OVL- Local byte-pattern scan over
build/cleanroom/AGI.decrypted.exefor writes to word0x124a. - Added update-list ordering helpers to
tools/agi_graphics.py. python3 -B -m unittest tests.test_graphics_rendering
Documented result:
- Source confirms the two root wrappers already documented:
0x6a8erebuilds and draws root0x1703, then root0x16ff;0x6aabrefreshes root0x1703, then root0x16ff. - Shared builder
0x0358(root, callback)scans the 43-byte object table in memory order. Accepted records are stored with a draw key. The key is object baseline field+0x05unless flag bit0x0004is set, in which case it is0x4cbb(object[+0x24]). - The builder then selects the smallest remaining key on each pass. It uses a
signed comparison against an initial
0x00ffbest key and preserves the first object-table entry for equal keys. Consumed keys are overwritten with0x00ff. - Helper
0x042finserts newly allocated 16-byte render nodes at the head of the root list; the first inserted node remains the root tail. Helper0x045edraws from tail toward previous pointers, saving a backing rectangle throughIBM_OBJS.OVL:0x9db0and then drawing throughIBM_OBJS.OVL:0x9db6. Combining these paths means objects draw in ascending key order within a root, while equal-key objects draw in object-table order and later entries can cover earlier entries. - Helper
0x4cbb(value)in SQ2’s normal mode scans the priority table from one-past index0xa8downward and returns the first index whose byte is less thanvalue;value == 0returns0xffff. The local AGIDATA byte at0x127a + 0xa8is zero, and a local byte-pattern scan found only the0x4d10helper clearing word[0x124a], not a write that enables the alternate direct formula branch. - Added
ObjectDrawCandidate,priority_value_to_sort_y,object_update_root,object_update_sort_key, andobject_update_draw_orderto the local renderer helpers. The focused graphics test module now includes source-model tests for root order, stable equal-key order, and the SQ2 sentinel behavior in the fixed-priority reverse mapping.
2026-07-04: dirty-rectangle union source model
Commands run from /Users/peter/ai/agi/reverse:
ndisasm -b 16 -o 0x5762 -e 0x5962 build/cleanroom/AGI.decrypted.exe(the command was useful for the first routine body but also confirmed again thatndisasm -eis a skip offset, not an end offset, so the terminal output continued past0x57ce).python3 -B -m unittest tests.test_graphics_rendering
Documented result:
- Helper
0x5762(object)returns without display work when word[0x1216]is zero. - Otherwise it loads the current frame pointer from object word
+0x10, the saved frame pointer from object word+0x12, stores the current frame pointer back into+0x12, and computes one display rectangle covering both the current and saved object footprints. - The vertical calculation treats object Y fields as baselines. The current top
is
object[+0x05] - current_frame[+0x01] + 1; the saved top isobject[+0x18] - saved_frame[+0x01] + 1. - The horizontal calculation uses left X plus frame width. The rectangle passed
to overlay entry
0x980cisleft = min(current_left, saved_left),bottom = max(current_bottom, saved_bottom),width = max(current_right, saved_right) - left, andheight = bottom - min(current_top, saved_top) + 1. - Added
DirtyRectanddirty_rect_union()totools/agi_graphics.py, with focused tests for identical footprints and old/current footprints on opposite sides of the union.
2026-07-04: control-acceptance source model
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x58b8)) count=220 2>/dev/null | ndisasm -b 16 -o 0x56b8 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x4919)) count=260 2>/dev/null | ndisasm -b 16 -o 0x4719 -python3 -B -m unittest tests.test_graphics_rendering
Documented result:
code.object.control_acceptance(0x56b8) derives object byte+0x24from the baseline priority table unless object flag bit0x0004is set, computes the logical-buffer offset for the object coordinate, and scans one row using the current frame width.- Object priority/control byte
+0x24 == 0x0fbypasses the buffer scan and returns accepted. - High nibble
0x00rejects immediately. High nibble0x10rejects unless object flag bit0x0002is set. High nibble0x20leaves final class state(flag3=true, flag0=false). High nibble0x30leaves final class state(flag3=false, flag0=true). Other nonzero high nibbles leave final class state(flag3=false, flag0=false). - The source resets class state for each scanned cell, so the final scanned
class state controls the post-scan gates. This corrects the earlier wording
that implied class
0x20was latched once encountered anywhere. - After a complete scan, object flag bit
0x0100rejects states whose flag0 component is false, and object flag bit0x0800rejects states whose flag0 component is true. When object byte+0x02is zero, the final class state is also written to global flags 3 and 0. - Added
ControlAcceptanceandcontrol_acceptance_scan()totools/agi_graphics.py, with focused local tests for class rejection, priority-15 bypass, final-class ordering, and event-byte-zero global flag values. - A neighboring slice of
code.object.collision_test(0x4719) confirmed the previously documented object-object skip bit0x0200, object-table scan, grouping-byte skip, horizontal overlap test, and current/saved Y crossing test.
2026-07-04: view header reserved bytes
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x3bf7)) count=360 2>/dev/null | ndisasm -b 16 -o 0x39f7 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x3e1b)) count=180 2>/dev/null | ndisasm -b 16 -o 0x3c1b -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x60db)) count=180 2>/dev/null | ndisasm -b 16 -o 0x5edb -python3 -B tools/inspect_view.py | head -n 40python3 -B -c "import sys; sys.path.insert(0,'tools'); from agi_graphics import iter_valid_resources; from collections import Counter; c=Counter(payload[:2] for _,payload in iter_valid_resources('VIEWDIR')); print(c); print(sorted((k.hex(),v) for k,v in c.items()))"python3 -B -m unittest tests.test_graphics_rendering
Documented result:
code.view.load_resource(0x39f7) loads/caches a view payload, then calls display-mode helper0x591fand the update-list rebuild path. It does not interpret payload bytes+0x00or+0x01.code.object.bind_view(0x3ae7) copies the cached payload pointer into the object record and reads payload byte+0x02as the group count.code.object.select_group_table(0x3c1b) reads group offsets frompayload + 0x05 + selected_group * 2, then reads the selected group count from the group pointer.- The preview/display helper at
0x5edbbinds the view and uses the existing object/frame selection path; previous source notes also identified its consumer of the preview text offset at payload bytes+0x03..+0x04. - A local census found all 203 valid SQ2 view resources have first two payload
bytes
01 01. - The current clean-room model therefore treats view payload bytes
+0x00and+0x01as reserved header bytes for SQ2: preserved in the format model, but unused by the observed loader, binder, group/frame selector, and preview paths. Future interpreter-version comparisons should flag any divergent use.
2026-07-04: direct corner-path unit coverage
Commands run from /Users/peter/ai/agi/reverse:
rg -n "0xF4|0xf4|corner|draw_corner|base_.*corner|F5|0xf5" tools tests docs/src/compatibility_testing.md docs/src/graphics_object_pipeline.mdpython3 -B -m unittest tests.test_graphics_rendering
Documented result:
- The picture fuzz corpus and prior QEMU batches already include synthetic corner-path streams, but the focused graphics unit tests only made the command-resume behavior explicit.
- Added direct local regression tests for
0xf4(draw_corner_path_y_first) and0xf5(draw_corner_path_x_first) point sets. These tests do not add a new original-engine observation; they make the existing source-modeled rare handlers harder to accidentally regress while picture renderer work continues.
2026-07-04: WORDS.TOK decoder tests
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/inspect_words.py --prefix look --limit 10python3 -B tools/inspect_words.py --prefix get --limit 10python3 -B tools/inspect_words.py --prefix anyword --limit 10python3 -B tools/inspect_words.py --limit 5python3 -B -m unittest tests.test_wordspython3 -B -m unittest tests.test_graphics_rendering
Documented result:
- The local WORDS.TOK decoder reads 26 big-endian letter offsets followed by
prefix-compressed entries. The local SQ2 file has 1,099 decoded entries and a
zero offset for the
xbucket. - Known IDs used by parser probes are now locked down in local tests:
anywordhas ID0x0001,lookhas ID0x0002, andgethas ID0x0005. - Prefix-compressed phrase reconstruction is covered by tests for phrases such
as
look across,look down, andget inside. - This complements the original-engine
parser_edges_001QEMU batch, which validates matchinglook get, wildcard ID0x0001, and terminator ID0x270fthrough condition0x0e.
2026-07-04: save path validation plan
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5ddd)) count=360 2>/dev/null | ndisasm -b 16 -o 0x5bdd -xxd -g 1 -s 0x135f -l 16 SQ2/AGIDATA.OVLdd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5fea)) count=120 2>/dev/null | ndisasm -b 16 -o 0x5dea -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x603e)) count=120 2>/dev/null | ndisasm -b 16 -o 0x5e3e -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x51ea)) count=120 2>/dev/null | ndisasm -b 16 -o 0x4fea -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5fb2)) count=90 2>/dev/null | ndisasm -b 16 -o 0x5db2 -python3 -B -m unittest tests.test_save_resources
Documented result:
code.dos.validate_path(0x5bdd) skips leading spaces before measuring the path. If the resulting string is empty it callscode.dos.get_current_directory(0x5db2) to fill the same buffer.- If the last character is slash or backslash and the path length is greater
than one, the validator strips that trailing separator in place before
selecting a validation path. The separator table at
AGIDATA.OVL:0x135fcontains backslash, slash, and a terminator. - If the effective path has a drive prefix (
text[1] == ':'), the drive letter is lowercased by helper0x4feaand stored in byte[0x1363]; otherwise helper0x5deareads the current DOS drive letter into[0x1363]. - A single slash/backslash path returns success immediately. A two-character
drive path such as
A:calls helper0x5e3e, which switches to the requested drive, checks whether DOS accepted it, and switches back. Other paths call DOS find-first helper0x5e01with attribute0x10. - Added
SavePathValidationPlanandsave_path_validation_plan()totools/agi_save.py, with tests for leading spaces, empty-current-directory fallback, root acceptance, trailing separator stripping, drive-only paths, and normal directory lookup classification. The helper models source-level string handling and DOS-check selection; it does not claim to know whether a given path exists in a future DOS environment.
2026-07-04: resource cache record layout polish
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x3b79)) count=130 2>/dev/null | ndisasm -b 16 -o 0x3979 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x4be8)) count=130 2>/dev/null | ndisasm -b 16 -o 0x49e8 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x4c5e)) count=130 2>/dev/null | ndisasm -b 16 -o 0x4a5e -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x52d8)) count=90 2>/dev/null | ndisasm -b 16 -o 0x50d8 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5326)) count=240 2>/dev/null | ndisasm -b 16 -o 0x5126 -
Documented result:
- View cache lookup
0x3979walks the list rooted at[0x0ffa], compares record byte+0x02against the requested view number, and leaves the link slot for insertion in[0x1000]. View loader0x39f7allocates 5 bytes on a miss, links the record through that slot, stores the view number at+0x02, and stores the payload pointer at+0x03. - Picture cache lookup
0x49e8walks the list rooted at the static first record[0x120e], compares record byte+0x02, and leaves the insertion slot in[0x1214]. Picture loader0x4a3buses the static first record when that insertion slot is zero; otherwise it allocates 5 bytes, links it from the previous record, stores the picture number at+0x02, and stores the payload pointer at+0x03. - Sound cache lookup
0x50d8walks the list rooted at static record[0x125a], compares record word+0x02, and leaves the insertion slot in[0x1268]. Sound loader0x5126uses the static first record for the first sound, otherwise allocates 14 bytes. It stores the sound number word at+0x02, the payload pointer at+0x04, and derives four channel stream pointers at+0x06,+0x08,+0x0a, and+0x0cfrom the first four little-endian payload offsets. - Existing logic loader notes already pinned down the 10-byte logic cache
record: next pointer at
+0x00, logic number byte at+0x02, message count at+0x03, bytecode pointer at+0x04, current instruction pointer at+0x06, and message-offset-table pointer at+0x08.
2026-07-04: picture raw-operand scanner edge
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x6675)) count=80 2>/dev/null | ndisasm -b 16 -o 0x6475 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x6694)) count=260 2>/dev/null | ndisasm -b 16 -o 0x6494 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x6803)) count=260 2>/dev/null | ndisasm -b 16 -o 0x6603 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x68ab)) count=260 2>/dev/null | ndisasm -b 16 -o 0x66ab -python3 -B -m unittest tests.test_graphics_renderingpython3 -B -m unittest tests.test_picture_fuzzpython3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --cleanpython3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_033_raw_visual_operand --case base_034_raw_control_operand --case base_035_raw_pattern_mode_operand --dos-prefix RO --fixture-root build/picture-fuzz/fixtures --output build/picture-fuzz/batches/raw_operand_001.json --boot-wait 5 --draw-wait 8 --stop-on-failure
Documented result:
- The top-level picture scanner at
0x6475dispatches command bytes0xf0..0xfa, then resumes at0x647ausing theALbyte left by the handler. This makes handler-local byte consumption significant. - Handlers
0x6494(0xf0set visual),0x64c7(0xf2set control), and0x6524(0xf9set pattern mode) each use a rawlodsboperand read and then preload the next byte. They do not reject operands>= 0xf0. - Coordinate/list readers
0x66c1,0x66d4, and0x66b8do reject bytes above0xef, returning carry with the command-looking byte inALfor the scanner to process next. This is the source distinction between raw one-byte operands and coordinate/list data. - Updated
PictureRendererwithread_raw_byte()and changed0xf0,0xf2, and0xf9to use it. Added local graphics tests showing that command-looking bytes after those opcodes are operands, not commands. - Added three safe curated fuzz cases:
base_033_raw_visual_operand,base_034_raw_control_operand, andbase_035_raw_pattern_mode_operand. Regenerating the deterministic corpus produced 1,060 cases, of which 1,058 are marked safe for QEMU. - Original-engine snapshot batch
raw_operand_001matched all three new cases with 0 mismatches and 0 errors. This confirms the source-modeled scanner edge on the visible EGA surface.
2026-07-04: picture relative-line underflow edge
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x685e)) count=180 2>/dev/null | ndisasm -b 16 -o 0x665e -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x68e1)) count=260 2>/dev/null | ndisasm -b 16 -o 0x66e1 -python3 -B -m unittest tests.test_graphics_renderingpython3 -B -m unittest tests.test_picture_fuzzpython3 -B tools/picture_fuzz.py generate --count 1024 --seed 4097 --output build/picture-fuzz/corpus --cleanpython3 -B tools/picture_fuzz.py batch-qemu --snapshot --case base_036_relative_x_underflow_wraps --case base_037_relative_y_underflow_wraps --dos-prefix RU --fixture-root build/picture-fuzz/fixtures --output build/picture-fuzz/batches/relative_underflow_001.json --boot-wait 5 --draw-wait 8 --stop-on-failurepython3 -B -m unittest tests.test_compatibility_suitepython3 -B tools/compatibility_suite.py --dry-run --include-qemu-smokepython3 -B tools/compatibility_suite.py --include-qemu-smoke --report build/compatibility-suite/qemu_smoke_002.json
Documented result:
- Handler
0x665ereads the initial coordinate pair through0x66b8, plots it, then consumes relative bytes while they are<= 0xef. - For X, bits
0x70supply a magnitude and bit0x80chooses subtraction. The handler adds or subtracts that magnitude inBH, an 8-bit coordinate register, then clamps only ifBH > 0x9f. Subtracting from zero therefore underflows to a high unsigned byte and is clamped to0x9f, not to zero. - For Y, bits
0x07supply a magnitude and bit0x08chooses subtraction. The same byte-register behavior applies, followed by a high-side clamp to0xa7. - Updated
PictureRenderer.draw_relative_lines()to model this 8-bit wrap-and-high-clamp behavior. Added local graphics tests for X underflow from(0,10)to the right edge and Y underflow from(10,0)to the bottom edge. - Added safe curated fuzz cases
base_036_relative_x_underflow_wrapsandbase_037_relative_y_underflow_wraps. Regenerating the deterministic corpus produced 1,062 cases, of which 1,060 are marked safe for QEMU. - Original-engine snapshot batch
relative_underflow_001matched both new cases with 0 mismatches and 0 errors. - Promoted the new pair into the QEMU smoke layer as
picture_fuzz_relative_underflow_qemu. The updated smoke reportqemu_smoke_002.jsonpassed after running 235 local tests, mdBook, opcode-evidence check, parser QEMU probes, command-resume probes, raw-operand probes, and the new relative-underflow probes.
2026-07-04: parser normalization and output-slot model
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1b58)) count=180 2>/dev/null | ndisasm -b 16 -o 0x1958 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1aac)) count=260 2>/dev/null | ndisasm -b 16 -o 0x18ac -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1b9d)) count=260 2>/dev/null | ndisasm -b 16 -o 0x199d -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1c6b)) count=360 2>/dev/null | ndisasm -b 16 -o 0x1a6b -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1dc7)) count=130 2>/dev/null | ndisasm -b 16 -o 0x1bc7 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x1de4)) count=100 2>/dev/null | ndisasm -b 16 -o 0x1be4 -xxd -g 1 -s 0x0940 -l 48 SQ2/AGIDATA.OVLxxd -g 1 -s 0x0c70 -l 80 SQ2/AGIDATA.OVLpython3 -B -m unittest tests.test_words
Documented result:
- Re-read action
0x75at0x1958, parser helper0x18ac, normalization helper0x199d, dictionary lookup helper0x1a6b, unknown-token helper0x1bc7, and dictionary-entry advance helper0x1be4. - Added symbolic labels for the parser action/helper routines and data labels
for separator bytes
0x0c67, ignored bytes0x0c75, parsed IDs0x0c7b, parsed word pointers0x0c8f, dictionary base pointer[0x0ca5], normalized parse buffer0x0ca7, and current parse pointer[0x0cd1]. - Added local source-model helpers to
tools/inspect_words.py:parser_separator_bytes(),parser_ignored_bytes(),normalize_parser_text(), andparse_words(). - Local tests now validate the SQ2 separator table (
,.?!();:[]{}), ignored punctuation table (apostrophe, backtick, hyphen, double quote), separator collapse, ignored-punctuation removal, case-insensitive lookup, zero-ID word filtering, unknown-word reporting, and the ten-output-word limit. - The output-slot model corrects an easy-to-miss detail: ignored zero-ID
dictionary words such as
the,a, andido not increment the parser’s output index. An unknown word after ignored terms therefore recordsparsed_nonzero_word_count + 1; for example a phrase shaped likethe <unknown> lookreports output slot 1, not raw token ordinal 2.
2026-07-04: heap formula test model
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x15d6)) count=260 2>/dev/null | ndisasm -b 16 -o 0x13d6 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x16bd)) count=240 2>/dev/null | ndisasm -b 16 -o 0x14bd -rg -n "bump|heap|0x13d6|0x14bd|memory" docs/src/runtime_model.md docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.mdpython3 -B -m unittest tests.test_heap
Documented result:
- Rechecked the allocator helper
code.heap.allocate(0x13d6) and the heap-status actioncode.heap.show_status_action(0x14bd) against the existing heap notes and symbolic labels. - Added
tools/agi_heap.pyas a local source-model helper for the bump heap: allocation returns the old current pointer, advances the current pointer, recomputes the free-byte count and byte variable 8 page value, and updates the high-water pointer only when the new current pointer exceeds the prior high-water value. - The helper also models fatal allocation overflow, one-shot temporary-mark
restore, dynamic reset to the room/reset mark, and the five heap-status
numbers printed by action
0x87. - Added
tests/test_heap.py; the focused suite passed 7 tests. This makes the allocator/status formulas executable compatibility evidence while leaving the visibly rendered out-of-memory path as optional future coverage.
2026-07-04: bit-0x80 view mirror edge tests
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5a7d)) count=420 2>/dev/null | ndisasm -b 16 -o 0x587d -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x5bac)) count=260 2>/dev/null | ndisasm -b 16 -o 0x59ac -sed -n '2668,2734p' docs/src/clean_room_executable_notes.mdpython3 -B -m unittest tests.test_graphics_rendering
Documented result:
- Re-read
code.object.rewrite_frame_orientation(0x587d) and the nearby placement helpers before expanding local coverage for the view/cel mirror model. - The source loop skips explicit leading transparent runs until it sees the first nontransparent run. If the row terminator is reached first, it writes an empty rebuilt row.
- For rows with visible data, the helper emits the original implicit trailing transparent width before reversing the counted run bytes. Widths larger than 15 are emitted as multiple transparent run bytes because a run length is only four bits.
- Added focused local tests for all-transparent rows, implicit transparent padding, long transparent chunking, and reversal from the first visible run. The graphics suite passed 57 tests after correcting one arithmetic mistake in the expected transparent width.
2026-07-04: object-overlay priority gate edge tests
Commands run from /Users/peter/ai/agi/reverse:
rg --files SQ2 build/cleanroom | rg "IBM|OBJ|OVL|AGI.decrypted"ls -l SQ2 build/cleanroomsed -n '700,770p' docs/src/clean_room_executable_notes.mdsed -n '2520,2550p' docs/src/clean_room_executable_notes.mdndisasm -b 16 -o 0x9db0 SQ2/IBM_OBJS.OVL | sed -n '1,260p'python3 -B -m unittest tests.test_graphics_rendering
Documented result:
- Confirmed again that the draw entry is in
SQ2/IBM_OBJS.OVL, not the base executable image: entry0x9db6jumps to0x9e35. - The draw loop extracts each run byte as color/high nibble plus count/low
nibble. If the run color is the transparent nibble from frame control byte
+0x02, it advances by the run length without writing. - For nontransparent pixels, destination high nibbles above
0x20are compared directly with the object’s shifted priority/control nibble. Thejabranch rejects only strictly greater values, so equal priority writes are allowed. - Destination high nibbles
<= 0x20enter the downward scan at0x9ec6. If the scan finds a high nibble above0x20, that value is compared with the same inclusive rule; if the scan reaches the lower limit first,CHremains zero and even priority 0 passes the local gate. - Rejection at
0x9ee5increments the destination pointer and continues the run loop, so a blocked pixel does not suppress later pixels in the same run. - Added local tests for equal scanned priority, no-hit priority 0, and per-pixel run continuation. The graphics suite passed 60 tests.
2026-07-04: input-word sequence matcher edge
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x0b5c)) count=220 2>/dev/null | ndisasm -b 16 -o 0x095c -sed -n '336,420p' docs/src/logic_bytecode.mdsed -n '56,104p' docs/src/runtime_model.mdsed -n '5950,5985p' docs/src/clean_room_executable_notes.mdpython3 -B -m unittest tests.test_wordspython3 -B -m unittest tests.test_logic_interpreter_probe tests.test_wordspython3 -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_wordpython3 -B tools/logic_opcode_evidence.py
Documented result:
- Re-read condition handler
code.logic.condition_input_word_sequence(0x095c). The handler first rejects whendata.words.parsed_count_or_error_positionis zero, flag 4 is already set, or flag 2 is clear. Otherwise it walks the variable-length operand word IDs againstdata.words.parsed_ids. - Operand word ID
0x0001is a one-word wildcard. Operand word ID0x270fimmediately forces success, skips any remaining operand words, and sets flag 4 through the normal success path. - A non-terminator operand after all parsed words have been consumed fails. A too-short operand list also fails because the parsed count remains nonzero when the operand loop ends.
- Added
InputWordSequenceResultandinput_word_sequence_matches()totools/inspect_words.py, with local tests for exact match, wildcard, terminator skip, extra/non-terminator failure, too-short failure, flag gates, and the unknown-token terminator edge. - The unknown-token edge is now dynamically confirmed: parsing
flarblestores a nonzero parser count/error-position and sets flag 2; condition0x0ewith only word ID0x270fmatched in QEMU batchparser_unknown_terminator_001with 1 match, 0 mismatches, and 0 errors.
2026-07-04: compatibility suite manifest runner
Commands run from /Users/peter/ai/agi/reverse:
rg -n "compatibility suite|runner|manifest|broad suite|run_.*suite|snapshot|preset broad|parser_edges|raw_operand" tools tests docs/src/compatibility_testing.md PROGRESS.md AGENTS.mdls tools testssed -n '1,180p' docs/src/compatibility_testing.mdsed -n '380,398p' PROGRESS.mdpython3 -B -m unittest tests.test_compatibility_suitepython3 -B tools/compatibility_suite.py --dry-runpython3 -B tools/compatibility_suite.py --dry-run --include-qemu-smokepython3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_001.json
Documented result:
- Added
tools/compatibility_suite.py, a local-by-default manifest/runner for the current compatibility layers. The default selection runs the full local unit suite,mdbook build docs, andpython3 -B tools/logic_opcode_evidence.py --check. - QEMU checks are explicit opt-ins. The smoke layer includes parser edge batches and targeted picture-fuzz scanner/raw-operand batches. The broad layer includes the representative picture timed carousel and the current view/object stress carousel.
- Added
tests/test_compatibility_suite.pyto lock down manifest contents, default layer selection, explicit QEMU layer selection, unknown-name rejection, stop-on-first-failure behavior, and report writing without running real subprocesses. - The focused runner tests passed. A real default runner invocation wrote
build/compatibility-suite/local_001.jsonafter the local unit suite passed with 230 tests, the mdBook built, and opcode evidence checked cleanly.
2026-07-04: sound tone-output boundary model
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x821c)) count=220 2>/dev/null | ndisasm -b 16 -o 0x801c -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x82f3)) count=260 2>/dev/null | ndisasm -b 16 -o 0x80f3 -dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x8362)) count=180 2>/dev/null | ndisasm -b 16 -o 0x8162 -python3 -B -m unittest tests.test_sound_resources
Documented result:
- Re-read
code.sound.driver_tick(0x801c), tone output helpercode.sound.driver_write_tone(0x80f3), the selector-2 internal control adjustment helper at0x8145, and the attenuation helper at0x8162. - Added
SoundToneOutput,sound_tone_output(), andsound_stop_silence_output()totools/agi_sound.py. - For selectors
0and8, the model reports whether the PC-speaker gate is enabled and, when enabled, the source PIT divisor. A silent attenuation nibble produces disabled gate state and no divisor. - For other selectors, the model reports the port-
0xc0tone bytes: high tone byte first, low tone byte only when the high byte’s top three bits are not all set. The stop-core path writes0x9f,0xbf,0xdf, and0xff. - Local sound tests now cover these tone/silence outputs in addition to the
prior resource parser, schedule, PC-speaker divisor, attenuation envelope,
and completion tests.
tests.test_sound_resourcespassed with 16 tests.
2026-07-04: cross-version workflow chapter
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,220p' docs/src/SUMMARY.mdsed -n '1,120p' docs/src/symbolic_labels.mdrg -n "cross-version|cross version|symbolic label|other games|future interpreter" docs/src AGENTS.md PROGRESS.md
Documented result:
- Added
docs/src/cross_version_workflow.mdand linked it from the mdBook summary. - The chapter defines the evidence package to collect for each future interpreter/game pair, the structural anchors to use when mapping existing symbolic labels to moved routines, the recommended source-first pass order, compatibility suite tiers, and delta-recording rules.
- The workflow explicitly avoids claiming that another interpreter has already been compared. It moves the cross-version tracker from an empty placeholder to a documented process that is blocked only on future local inputs.
2026-07-04: compatibility suite QEMU smoke layer
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/compatibility_suite.py --include-qemu-smoke --report build/compatibility-suite/qemu_smoke_001.json
Documented result:
- A first sandboxed attempt reached the QEMU smoke layer but failed when QEMU
tried to bind the local VNC socket with
Operation not permitted. This was a sandbox permission failure, not an interpreter or fixture mismatch. - The rerun with local VNC/socket access passed and wrote
build/compatibility-suite/qemu_smoke_001.json. - The selected command set included the local unit suite,
mdbook build docs,tools/logic_opcode_evidence.py --check, parser-edge QEMU probes, unknown-word terminator parser QEMU probe, picture command-resume fuzz QEMU probes, and raw-operand picture fuzz QEMU probes. - Every selected command returned zero. The original-engine probe reports were:
parser_edges_suite.jsonwith 3 matches,parser_unknown_terminator_suite.jsonwith 1 match,command_resume_suite.jsonwith 3 matches, andraw_operand_suite.jsonwith 3 matches.
2026-07-04: compatibility suite QEMU broad layer
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/compatibility_suite.py --include-qemu-broad --report build/compatibility-suite/qemu_broad_002.json
Documented result:
- The broad suite selection includes the local checks, all QEMU smoke checks, and the broad QEMU resource sweeps.
- The smoke checks repeated the same clean results as the dedicated smoke run: parser edge probes matched 3/3, the unknown-word terminator probe matched 1/1, command-resume picture fuzz probes matched 3/3, raw-operand picture fuzz probes matched 3/3, and relative-line underflow probes matched 2/2.
- The suite-level broad picture carousel
build/picture-carousel/batches/picture_carousel_broad_suite.jsonmatched all 8 broad real-picture cases from one engine process. - The suite-level view/object stress carousel
build/view-carousel/batches/view_carousel_stress_suite.jsonmatched all 19 current base-plus-stress cases from one engine process. build/compatibility-suite/qemu_broad_002.jsonrecords return code 0 for all selected commands.
2026-07-04: object control-acceptance branch tests
Commands run from /Users/peter/ai/agi/reverse:
dd if=build/cleanroom/AGI.decrypted.exe bs=1 skip=$((0x58b8)) count=360 2>/dev/null | ndisasm -b 16 -o 0x56b8 -python3 -B -m unittest tests.test_graphics_rendering
Documented result:
- Re-read
code.object.control_acceptanceat0x56b8. The high-nibble scanner resets its class-state bytes for each scanned cell. Classes0x10,0x20, and0x30have explicit branches; other nonzero high nibbles fall through after setting the final state to(flag3=false, flag0=false). - Added local tests for that fall-through branch: it accepts with no rejection
bits, rejects when bit
0x0100is set, and accepts when bit0x0800is set. - Added a local test for priority/control byte
0x0f, which bypasses scanning and clears both reported event flags when object byte+0x02is zero. tests.test_graphics_renderingpassed with 64 tests.
2026-07-07: Gold Rush AGI v3 resource compression first pass
Context:
- The user identified
games/GRas a local Gold Rush data set using AGI interpreter version 3 and asked for a source-first comparison with the SQ2 version 2 interpreter. - The known QEMU BIOS text-rendering problem was set aside as an on-screen text caveat. Dynamic checks, if needed later, should use the FreeDOS image path rather than the old MS-DOS image.
Commands run from /Users/peter/ai/agi/reverse:
git status --shortrg --filesls -la games && ls -la games/GR && ls -la games/SQ2file games/GR/AGI games/SQ2/AGIxxd -g 1 -l 64 games/GR/AGIxxd -g 1 -l 64 games/GR/GRDIRpython3 -B tools/decrypt_agi.py --game-dir games/SQ2 --output build/cleanroom/SQ2_AGI.decrypted.exedd if=games/GR/AGI bs=1 skip=$((0x30a0+0x200)) count=760 2>/dev/null | ndisasm -b 16 -o 0x30a0 -dd if=games/GR/AGI bs=1 skip=$((0x44d0+0x200)) count=520 2>/dev/null | ndisasm -b 16 -o 0x44d0 -dd if=games/GR/AGI bs=1 skip=$((0x07d0+0x200)) count=720 2>/dev/null | ndisasm -b 16 -o 0x07d0 -dd if=games/GR/AGI bs=1 skip=$((0x9a40+0x200)) count=420 2>/dev/null | ndisasm -b 16 -o 0x9a40 -dd if=games/GR/AGI bs=1 skip=$((0x33c0+0x200)) count=520 2>/dev/null | ndisasm -b 16 -o 0x33c0 -dd if=games/GR/AGI bs=1 skip=$((0x0200+0x200)) count=520 2>/dev/null | ndisasm -b 16 -o 0x0200 -xxd -g 1 -s 0xf50 -l 128 games/GR/AGIDATA.OVLstrings -a -t d games/GR/AGIDATA.OVLstrings -a -t d games/SQ2/AGIDATA.OVLpython3 -B tools/agi_resources.py --game-dir games/GR --summary --kind logic --number 0python3 -B tools/agi_resources.py --game-dir games/GR --kind picture --number 1python3 -B tools/agi_resources.py --game-dir games/GR --kind view --number 0python3 -B tools/agi_resources.py --game-dir games/SQ2 --summary --kind logic --number 0python3 -B tools/disassemble_logic.py --game-dir games/GR --statspython3 -B -m unittest tests/test_agi_resources.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_logic_doc_coverage.py tests/test_sound_resources.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests
Documented result:
games/GR/AGIis already an MZ executable.games/SQ2/AGIremains an encrypted/data file and was decrypted tobuild/cleanroom/SQ2_AGI.decrypted.exefor comparison.- GR
AGIDATA.OVLcontainsVersion 3.002.149; SQ2AGIDATA.OVLcontainsVersion 2.936. - GR uses a combined
GRDIR. Its first four little-endian words are section offsets: logic0x0008, picture0x02e7, view0x05c6, and sound0x08c6. The resulting section counts are 245 logic entries, 245 picture entries, 256 view entries, and 51 sound entries. - The GR directory loader at image
0x44defirst formats a combined directory name using the runtime prefix and"%sdir", then falls back to separatelogdir,picdir,viewdir, andsnddirfiles if the combined open fails. - The v3 absent-entry helper at image
0x4599rejects only exactff ff ff. This differs from SQ2 v2’s high-nibble0xfrejection. - GR image
0x33c2opens sixteen volume handles using"%svol.%d". This explains observed local directory entries pointing atGRVOL.9throughGRVOL.12. - The v3 generic record reader at image
0x30d0reads a 7-byte header:12 34, a metadata/volume byte, an expanded little-endian length, and a stored little-endian length. - Metadata bit
0x80selects the picture-nibble transform at image0x9a5b. Otherwise equal expanded/stored lengths are read directly, and unequal lengths use the dictionary decompressor at image0x07f4. - The dictionary decompressor uses 9-bit initial codes, reset code
0x100, end code0x101, and grows to 10 and 11 bits. The picture-nibble transform expands packed color/control nibbles after picture commands0xf0and0xf2back into ordinary byte operands. - Added
tools/agi_resources.pywith split v2 and combined v3 container detection, v3 dictionary expansion, v3 picture-nibble expansion, and a small CLI summary/inspection mode. - Added
tests/test_agi_resources.py. The new focused tests passed. - Extended
tools/disassemble_logic.pyso logic payload loading usestools/agi_resources.py; v3 combined games select action table base0x0440and condition table base0x0762. - The decoded GR logic census had no parse errors. The v3 action dispatcher
accepts action slots through
0xb5, but this Gold Rush data set uses action opcodes only through0xa9. The condition dispatcher compares predicate bytes with0x26; a later static comparison pass corrected this note by showing that only entries0x00..0x12are structured table records in the observedAGIDATA.OVL, while bytes above that overlap string/data. - Focused regression checks passed:
tests/test_agi_resources.py, plustests/test_logic_doc_coverage.pyandtests/test_sound_resources.pywithAGI_GAME_DIR=games/SQ2. - A full local unit-suite run with
AGI_GAME_DIR=games/SQ2currently fails in older fixture builders because the private game files are read-only and copied fixtures try to overwrite files such asLOGDIRandVOL.3without changing destination permissions. This appears unrelated to the new GR resource parser and should be handled separately if full-suite green status is needed before the next commit.
2026-07-07: GR / SQ2 static opcode, object, view, and picture comparison
Context:
- The user asked for a source-only pass through every GR logic opcode compared with SQ2, plus the same style of comparison for object, view, and picture implementation code. Observable behavior checks were intentionally deferred.
- All evidence in this pass comes from local game directories, the local SQ2
decrypted executable, local disassembly with
ndisasm, and local parsing tools.
Commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -n '1,220p' PROGRESS.mdsed -n '1,260p' docs/src/clean_room_executable_notes.mdtail -n 80 docs/src/progress_log.mdsed -n '1,220p' docs/src/symbolic_labels.mdndisasm -b 16 -o 0x0000 -e 0x200 games/GR/AGI > build/gr-sq2-static/gr_agi_image.ndisasmndisasm -b 16 -o 0x0000 -e 0x200 build/cleanroom/SQ2_AGI.decrypted.exe > build/gr-sq2-static/sq2_agi_image.ndisasmpython3 -B tools/compare_gr_sq2_static.py --sq2-game-dir games/SQ2 --gr-game-dir games/GR --sq2-exe build/cleanroom/SQ2_AGI.decrypted.exe --gr-exe games/GR/AGI --output build/gr-sq2-static/opcode_static_report.mdpython3 -B tools/compare_gr_sq2_static.py --helppython3 -B -m py_compile tools/compare_gr_sq2_static.pypython3 -B -m py_compile tools/compare_gr_sq2_static.py tools/disassemble_logic.pyAGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py --statsAGI_GAME_DIR=games/SQ2 python3 -B tools/disassemble_logic.py --statsmdbook build docsAGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testspython3 -B tools/compatibility_suite.pyAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.pyxxd -g 1 -s 0x0762 -l 192 games/GR/AGIDATA.OVL- Local one-off Python census using
tools.agi_resourcesto read all present SQ2 and GR picture/view resources and count transform types. - Focused
ndisasm/sedreads over GR and SQ2 image ranges around action dispatch, condition dispatch, picture command dispatch, view selectors, object update lists, frame timers, motion helpers, and display refresh helpers. xxd -g 1 -l 16 games/SQ2/OBJECTxxd -g 1 -l 16 games/GR/OBJECTxxd -g 1 -s 315 -l 16 games/SQ2/OBJECTxxd -g 1 -s 1798 -l 16 games/GR/OBJECT
Generated local artifacts:
tools/compare_gr_sq2_static.py: deterministic comparison helper requiring explicit SQ2 and GR game/executable paths.build/gr-sq2-static/gr_agi_image.ndisasm: full GR loaded-image disassembly.build/gr-sq2-static/sq2_agi_image.ndisasm: full SQ2 loaded-image disassembly.build/gr-sq2-static/opcode_static_report.md: generated static comparison report.
Documented result:
- Shared action opcode table entries
0x00..0xafhave identical argument counts and operand metadata in SQ2 and GR. - Normalized handler-entry snippets match for 159 shared action opcodes and
differ for 17 shared actions:
0x12,0x6f,0x73,0x76,0x77,0x78,0x79,0x7c,0x7d,0x80,0x84,0x89,0x8a,0xa3,0xa4,0xa9, and0xad. - The changed shared action snippets cluster around room switching,
input/text/window handling, save/restart/inventory UI paths, key-map
capacity, and two GR state bytes. Important static observations:
- GR action
0x79raises the key-map loop limit from0x27to0x31. - GR actions
0xa3and0xa4route to the generic no-op/return handler rather than setting/clearing SQ2’s input-width word. - GR action
0xadsets byte[0x0405]to1; GR-only action0xb5sets the same byte to0. SQ2 action0xadincrements byte[0x1530]. - GR action
0x84preserves object0 byte+0x22when it is already4; SQ2 clears that field unconditionally.
- GR action
- GR-only action slots
0xb0..0xb5are present in the v3 action table.0xb0,0xb2,0xb3, and0xb4route to the generic no-op/return handler after operand consumption.0xb1stores its one operand in word[0x0403]; local cross-references show later code tests[0x0403]before a menu/popup-like path.0xb5clears byte[0x0405]. - Shared condition table entries
0x00..0x12have identical parser contracts and no normalized handler-entry differences. - GR condition dispatch code compares predicate bytes with
0x26, but the bytes after the first 19 four-byte entries are not a confirmed handler table. Forced decoding ofAGIDATA.OVL:0x07ae..yields punctuation/filename bytes such as.,;:'!-,words.tok, andobject, followed by zeros. The disassembler now treats only0x00..0x12as the structured condition table for the observed GR input. - The local GR logic census still has no parse errors after that conservative
condition-table correction. Observed GR scripts use condition opcodes only
through
0x0e. - Resource-reader implementation is the major known container difference:
code.resource.load_all_directoriesandcode.resource.read_volume_payload_onceare different as expected because GR uses combinedGRDIR, prefixedGRVOL.N, 7-byte headers, dictionary expansion, and picture-nibble expansion. - View runtime slices match as relocated skeletons after the v3 resource reader produces an expanded payload: view load/cache, object-view binding, group table selection, frame selection, and view discard all compared cleanly.
- Picture runtime slices mostly match as relocated skeletons: load/cache,
prepare/overlay/discard, command scan, all eleven picture command handlers
0xf0..0xfa, coordinate reads, line drawing, pixel write, seed fill, and pattern plotting. Differences are display-mode refresh paths:code.picture.decode_no_clear,code.display.fill_buffer_word, andcode.display.full_refreshomit SQ2’s display-mode-2 overlay refresh branch in GR. - Object runtime slices mostly match as relocated skeletons: update-list
sorting/insertion/draw/refresh, collision test, control acceptance,
dirty-rectangle update, placement, active/inactive list rebuild/flush/refresh,
and membership toggles. GR packages rectangle save/restore/draw routines in
the main executable image (
0x5b67,0x5ba6,0x5be3) while SQ2 uses object-overlay entry points inIBM_OBJS.OVL. - Static object/motion differences that still need semantic naming:
- GR frame-timer update adds an extra helper-gated branch before using the four-plus-group direction table.
- GR motion dispatch accepts one additional object mode selector
(
cmp ax,0x3instead of SQ2’scmp ax,0x2) before falling through to the same boundary-check tail. - Straight-line
ndisasmdifferences insidecode.object.advance_frame_by_modeandcode.motion.rectangle_boundary_checkare embedded jump-table bytes, not enough by themselves to claim behavioral differences; the surrounding branch bodies were manually inspected as relocated skeletons.
- Local resource data census:
- SQ2 pictures: 75 directory-present entries, 74 decoded direct payloads, one bad v2 header at picture 147.
- SQ2 views: 203 present direct payloads.
- GR pictures: 186 present payloads, all using the picture-nibble transform.
- GR views: 247 present payloads, all using the dictionary transform.
OBJECTfile bytes differ in length and content (games/SQ2/OBJECTlength 331,games/GR/OBJECTlength 1814), but the interpreter-side object-table initialization skeleton is the same: it reads/decrypts the file, computes inventory/object table roots, uses a 43-byte object-record stride, clears and initializes the object table, and seeds the same prompt/direction defaults after address relocation.- Verification:
mdbook build docspassed.AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testspassed 251 tests. Runningpython3 -B tools/compatibility_suite.pywithoutAGI_GAME_DIRfailed at import time because the project now requires an explicit game directory; rerunning asAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.pypassed the same 251 tests and mdBook build.
2026-07-07: Version ledger and read-only fixture copy fix
Context:
- The user asked for a
Versionsdocumentation chapter to track differences between observed interpreter versions. - The user also clarified that generated test/original-engine fixtures should
never modify private files under
games/; fixture builders should copy the selected game intobuild/and modify that generated copy.
Commands run from /Users/peter/ai/agi/reverse:
rg -n "fixture|LOGDIR|VOL\\.3|games/|AGI_GAME_DIR|copytree|copy2|chmod|write_bytes|open\\(" tests tools docs/src PROGRESS.md AGENTS.mdsed -n '1,260p' tools/qemu_fixture.pysed -n '620,980p' tools/qemu_fixture.pysed -n '1,520p' tests/test_qemu_fixture.pyrg -n "shutil\\.copy|copytree|copy2|LOGDIR\\)\\.write|VOL\\.3\\)\\.write|PICDIR\\)\\.write|VIEWDIR\\)\\.write|AGIDATA\\.OVL\\)\\.write|destination / \\\"LOGDIR\\\"|fixture / \\\"LOGDIR\\\"|games/" tools testsAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_qemu_fixture.pymdbook build docsAGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests
Documented result:
- Added
docs/src/versions.mdand linked it fromdocs/src/SUMMARY.md. The chapter currently records SQ2 / AGI 2.936 and Gold Rush / AGI 3.002.149 differences: executable form, resource container, compression/transform paths, dispatch-table range, and generated-fixture readiness. - Updated
tools/qemu_fixture.pyso generated fixture copies are writable even when the selected game input is read-only. The shared copy primitive now cleans generated destinations, preserves.ppmcapture files, copies source files, and adds owner read/write permission on the copy before patching. - Added a fixture-destination guard: paths under repository
games/are rejected before any generated directory is created or patched. Destinations that are the selected game directory, a child of it, or a parent of it are rejected for the same reason. - Added focused tests proving that a read-only copied
LOGDIRcan be patched in the fixture copy, that a destination undergames/is rejected, and that a destination parent of the selected game is rejected. - Updated
AGENTS.mdto state the immutable-games/rule explicitly. - Verification passed:
mdbook build docs, the focusedtests/test_qemu_fixture.pyslice, and the full local unit suite withAGI_GAME_DIR=games/SQ2(251tests).
2026-07-07: Gold Rush v3 extra action opcode source pass
Context:
- The user asked to figure out the extra opcodes. This pass focuses on the
Gold Rush / AGI v3 action slots beyond the SQ2 action table,
0xb0..0xb5, using local disassembly and local decoded resources. - No external AGI documentation or source was consulted.
- No targeted QEMU fixture was run for this pass. The current generated original-engine fixture writers are still v2/SQ2-container-oriented, while the relevant GR effects were directly visible in the opcode handlers and their local consumers. Observable v3 fixture tests remain useful later.
Commands run from /Users/peter/ai/agi/reverse:
git status --shortrg -n "0xb0|0xb1|0xb2|0xb3|0xb4|0xb5|0x0403|0x0405|GR-only|extra action|extra opcodes|v3-only" PROGRESS.md docs/src tools testssed -n '1,260p' tools/compare_gr_sq2_static.pysed -n '1,240p' tools/agi_resources.pyrg -n "0403|0405|970B|9724|63A8|63B0|000097|000063A|000064" build/gr-sq2-static/gr_agi_image.ndisasmsed -n '11140,11320p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '17280,17480p' build/gr-sq2-static/gr_agi_image.ndisasm- Local Python using
tools.compare_gr_sq2_static.load_tableto print GR action-table entries0xb0..0xb5. sed -n '6120,6260p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '17030,17240p' build/gr-sq2-static/gr_agi_image.ndisasmrg -n "1b67|1b71|1b73|1b75|1b79|1b77|1b7b|1b7d|1b7f|1b81|1b83|1b85|1b87|1b88|03f9|03fd|0403|0405" build/gr-sq2-static/gr_agi_image.ndisasmsed -n '16680,17040p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '9240,9320p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '1,240p' tests/test_logic_doc_coverage.pyrg -n "0x403|0x405|\\[0x403\\]|\\[0x405\\]" build/gr-sq2-static/gr_agi_image.ndisasmsed -n '11180,11340p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '17030,17230p' build/gr-sq2-static/gr_agi_image.ndisasm
Observed GR action-table entries:
| Opcode | Handler | Args | Metadata | Source-backed interpretation |
|---|---|---|---|---|
0xb0 | 0x5286 | 0 | 0x00 | Reserved/no-op slot. Handler 0x5286 only returns the bytecode pointer passed to it. |
0xb1 | 0x970b | 1 | 0x00 | Reads one immediate byte, zero-extends it, and stores it in word [0x0403]. |
0xb2 | 0x5286 | 0 | 0x00 | Reserved/no-op slot. |
0xb3 | 0x5286 | 4 | 0x00 | Reserved/no-op slot after four table-declared fixed operands. |
0xb4 | 0x5286 | 2 | 0xc0 | Reserved/no-op slot after two table-declared variable operands. |
0xb5 | 0x63b0 | 0 | 0x00 | Stores zero in byte [0x0405]. |
Detailed observations:
- Generic handler
0x5286saves/restores registers, loadsAXfrom[bp+0x8], and returns. It has no state writes and no operand reads. - Handler
0x970b(0xb1) reads the byte at the incoming bytecode pointer, increments the pointer, zero-extends the byte, stores the word at[0x0403], and returns the incremented pointer. - GR menu interaction routine
0x9724begins withcmp word [0x403],0and returns without drawing/waiting if the word is zero. The same routine otherwise draws the menu structure rooted at[0x1b71], waits for input, navigates enabled menu nodes, and enqueues type-3 item events through0x46f4. - Existing shared menu actions build and request the menu separately:
0x9cbuilds menu headings in the linked structure rooted at[0x1b71],0x9dadds menu items,0x9efinalizes the structure,0x9f/0xa0enable/disable items, and0xa1sets request word[0x1b67]when flag0x0eis set. The main cycle path calls0x9724only when[0x1b67]is nonzero, so0xb1is a separate interaction gate rather than a menu-build opcode. - GR shared action
0xadat0x63a8stores one in byte[0x0405]. GR-only action0xb5at0x63b0stores zero in the same byte. - The GR keyboard interrupt hook at
0x63b8tests[0x0405]on selected tracked-key release paths. When the byte is nonzero, it calls event enqueue helper0x46f4with type2and value0.
Documentation/tooling updates from this pass:
- Added v3-specific action names in
tools/disassemble_logic.pywithout changing the SQ2ACTION_NAMEScatalog:set_menu_interaction_gatefor0xb1,clear_key_release_event_gatefor0xb5, and reserved/no-op names for0xb0,0xb2,0xb3, and0xb4. - Updated
tools/compare_gr_sq2_static.pynotes so the generated static report records the local consumers of[0x0403]and[0x0405]. - Updated
PROGRESS.md,docs/src/versions.md,docs/src/logic_bytecode.md, anddocs/src/symbolic_labels.mdwith the v3-only opcode interpretations and new symbolic data labelsdata.menu.interaction_gate_0403anddata.input.key_release_enqueue_gate_0405.
Verification after the documentation/tooling updates:
- Regenerated
build/gr-sq2-static/opcode_static_report.mdwithpython3 -B tools/compare_gr_sq2_static.py --sq2-game-dir games/SQ2 --gr-game-dir games/GR --sq2-exe build/cleanroom/SQ2_AGI.decrypted.exe --gr-exe games/GR/AGI --output build/gr-sq2-static/opcode_static_report.md. AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py --statsparsed the local GR logic resources with no errors.python3 -B -m py_compile tools/disassemble_logic.py tools/compare_gr_sq2_static.pypassed.mdbook build docspassed.AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testspassed 251 tests.AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.pypassed the same 251 tests and mdBook build.git diff --checkpassed.
2026-07-09: Gold Rush / SQ2 shared action delta source pass
Context:
- The user asked to update
PROGRESS.mdwith the planned GR/SQ2 comparison items, then continue. - This pass used local disassembly first. No external AGI documentation/source and no QEMU confirmation were used.
- Inputs were the already generated
build/gr-sq2-static/*_agi_image.ndisasmfiles plus exact-offset disassembly from local executable bytes where linearndisasmswallowed handler-entry bytes as neighboring inline data.
Commands run from /Users/peter/ai/agi/reverse:
git status --shortsed -n '1,260p' PROGRESS.mdsed -n '1,260p' docs/src/versions.mdsed -n '1,260p' docs/src/symbolic_labels.mdsed -n '1,220p' docs/src/progress_log.mdsed -n '1,260p' tools/compare_gr_sq2_static.pytail -n 180 docs/src/clean_room_executable_notes.mdrg -n "0x6f|0x73|0x76|0x77|0x78|0x79|0x7c|0x7d|0x80|0x84|0x89|0x8a|0xa3|0xa4|0xa9|0xad|0xb1|0xb5|Gold Rush|GR / SQ2|v3" docs/src/logic_bytecode.md docs/src/versions.md docs/src/clean_room_executable_notes.md docs/src/symbolic_labels.md PROGRESS.md- Local Python using
tools.compare_gr_sq2_static.load_tableto print the SQ2/GR action-table entries for the changed shared action set. sed -n '8548,8628p' build/gr-sq2-static/sq2_agi_image.ndisasmsed -n '8768,8855p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '10900,11040p' build/gr-sq2-static/sq2_agi_image.ndisasmsed -n '11272,11445p' build/gr-sq2-static/gr_agi_image.ndisasm- Exact-offset local Python disassembly for handlers
0x175c,0x19d4,0x31d8,0x351e,0x2753,0x29e5,0x2472,0x26e0,0x7041, and0x73b9. AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py --limit 256 | rg "switch_room_like|0x12|\\broom"- Exact-offset local Python disassembly for GR helpers
0x0062,0x07bc,0x169b,0x11b3,0x341c,0x9648,0x3b00, and0x3ab0. rg -n "0dc1|DC1|\\[0xdc1\\]|03f9|0dc3|0dc5" build/gr-sq2-static/gr_agi_image.ndisasm docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md- Exact-offset local Python disassembly for SQ2/GR
code.object.frame_timer_update,code.motion.pre_mode_and_boundary_update,code.motion.dispatch_mode_step, andcode.motion.rectangle_boundary_check. - Local Python byte dump of the SQ2 and GR motion-mode dispatch tables at image
offsets
0x06adand0x06bd.
Input/text cluster observations:
- GR action-table entries match SQ2 parser contracts for actions
0x6f,0x73,0x76,0x77,0x78,0x89,0x8a,0xa3,0xa4, and0xa9, but the handler bodies remove SQ2’s display-mode-2/input-width branches. - SQ2
0x6fat image0x78f0stores input-line bounds and computes display offset[0x1379]with an alternate display-mode-2 branch. GR0x6fat image0x7c24stores relocated globals and computes display offset[0x11b1] = arg0 << 3unconditionally. - SQ2 string/number prompt actions
0x73and0x76have alternate paths for display mode[0x1130] == 2when input-width word[0x0d0f] == 0. GR prompt handlers0x0e92and0x756buse the normal prompt/editor path only. - SQ2
0x77,0x78,0x89, and0x8atest display mode and input-width state before clearing/redrawing/refreshing/erasing. GR handlers0x3b0c,0x3b2e,0x3a48, and0x3a29use the normal relocated input buffers and visible row helpers without those special branches. - SQ2 actions
0xa3and0xa4set/clear word[0x0d0f]; GR maps both table entries to the generic no-op/return handler0x5286. - SQ2 action
0xa9at0x1f2brestores active saved-window state and clears both[0x0d0f]and[0x0d1d]. GR action0xa9at0x21a2restores the relocated active saved-window rectangle and clears only active word[0x0b24].
Event/key/menu observations:
- SQ2 action
0x79at image0x4c3dreads a two-byte key word and one mapped value, then scans up to0x27four-byte slots rooted at[0x0145]for the first empty key word. GR action0x79at0x4e98is the same shape but scans up to0x31slots. - SQ2 action
0xadat0x602fincrements byte[0x1530]. The SQ2 keyboard IRQ hook tests[0x1530] != 0before enqueueing(type=2, value=0)on selected tracked-key release paths. - GR action
0xadat0x63a8sets byte[0x0405] = 1; GR-only action0xb5at0x63b0clears the same byte. GR keyboard IRQ hook0x63b8tests[0x0405]before the selected key-release enqueue. - GR-only action
0xb1at0x970bstores its immediate operand in word[0x0403]. GR menu interaction routine0x9724returns immediately while[0x0403] == 0, so this is a separate interaction gate after menu request state has been set.
Room/inventory/save/restart/object-state observations:
- SQ2 action
0x12at image0x175creads the immediate room byte and calls room-switch helper0x1792. GR action0x12at0x19d4calls helper0x0062first: bytes below0x7eor above0x80pass through unchanged, while bytes0x7e,0x7f, and0x80return0x49. - Decoded local GR scripts contain
switch_room_like(#126),switch_room_like(#127), andswitch_room_like(#128), so the GR remap is live behavior for this interpreter/game pair, not dead code. - SQ2 action
0x7cat0x31d8enters the carried-item selector through the established text/input save-restore path. GR action0x7cat0x351efollows the relocated skeleton but clears word[0x0dc1]before return. The selector helper sets[0x0dc1] = 1while handling the flag-13 interactive input path, so the current label is a temporary selector/input gate. - SQ2 save action
0x7dat0x2753writes the known five-block envelope. GR save action0x7dat0x29e5writes the relocated five-block envelope but calls helper0x07bcover the object/inventory chunk before and after the save writes. Helper0x07bcXORs a caller-supplied byte range with repeating key bytes at data addressDS:0x072cuntil the key byte is zero. - SQ2 restart action
0x80at0x2472redraws the prompt marker at the end of the accepted path. GR restart action0x80at0x26e0first records the prompt-marker visible word through helper0x3b00, erases the marker, and redraws it when restart was accepted or, after a canceled restart, only if the marker had been visible before entry. - SQ2 action
0x84at0x7041sets[0x0139] = 1and clears object 0 byte+0x22unconditionally. GR action0x84at0x73b9sets[0x0139] = 1but skips the clear when object 0 byte+0x22is already4.
Object/motion observations:
- SQ2
code.object.frame_timer_updateat0x0563uses the direction-to-loop table for object views with group count byte+0x0b >= 4when bit0x2000is clear. GR0x055ckeeps the two/three-group table path, but the four-plus path is split: exactly four groups uses the direction table without the new flag gate, and more than four groups uses the direction table only when flag0x14is set. A later targeted QEMU probe in these notes corrected the earlier no-auto-select shorthand for the exactly-four case. - SQ2
code.motion.dispatch_mode_stepat0x067aaccepts modes1..3after decrementing object byte+0x22. GR0x068aaccepts modes1..4. The GR jump table at0x06bdmaps mode1to random motion, mode2to approach-first-object, and both modes3and4to the target-direction helper. - The surrounding GR pre-mode/boundary and rectangle-boundary helpers remain relocated skeletons of the SQ2 logic after accounting for embedded jump-table bytes in linear disassembly.
Documentation/tooling updates from this pass:
- Updated
PROGRESS.mdwith the requested ordered comparison queue, then replaced the queue with source-pass status and the remaining v3 fixture work. - Refined
tools/compare_gr_sq2_static.pychanged-action notes so regenerated reports describe the now-source-backed deltas. - Updated
docs/src/versions.md,docs/src/logic_bytecode.md,docs/src/runtime_model.md, anddocs/src/symbolic_labels.mdwith the source-backed GR/SQ2 differences and new GR address associations.
v3 Direct Logic Fixture Writer
Goal: turn the source-backed Gold Rush / AGI v3 deltas into testable behavior
without modifying private inputs under games/.
Commands and local reads:
sed -n '1,1240p' tools/qemu_fixture.pysed -n '1,760p' tools/agi_resources.pysed -n '1,620p' tests/test_qemu_fixture.pysed -n '1,90p' tools/project_paths.pyrg -n "copy_game_tree|build_.*fixture|patch_dir_entry|SQ2|AGI_GAME_DIR|--game-dir|detect_layout|v3|GRDIR|GRVOL" tools tests docs/src PROGRESS.md- Local Python
detect_layout(games/GR)/read_directory_entries(...)inspection. AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py --stats- Local Python count of GR combined-directory slots and present entries.
python3 -B -m py_compile tools/qemu_fixture.py tools/agi_resources.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_qemu_fixture.py tests/test_agi_resources.py
Observations:
tools/qemu_fixture.pypreviously importedSQ2fromtools/disassemble_logic.py, which made fixture construction depend on the selected game at module import time and hid an SQ2-oriented source path inside copy helpers.- The copy path now accepts
game_dir=...explicitly or usesAGI_GAME_DIRthroughproject_paths.game_dir()only when the caller omits a source. Fixture destinations are still rejected undergames/and when they would overwrite the selected source game. - Existing v2 fixture builders retain the split-directory
VOL.3packing behavior. The compatibility aliascopy_sq2_tree()remains for older probes, but it no longer imports or references a global SQ2 path. - Added
v3_volume_record(...)for the observed direct/uncompressed v3 header:12 34 metadata expanded_len stored_len payload. The writer uses equal expanded and stored lengths and keeps the metadata low nibble aligned with the patched directory volume. - Added
patch_combined_dir_entry(...)for v3 combined directories. It uses the section offsets and section ends recovered bydetect_layout()and patches the selected resource entry atsection_offset + resource_no * 3. - Added
build_v3_logic_fixture(...). It copies the selected v3 game into the generated destination, detects the copied combined layout, appends a direct record to the existing prefixed volume for the selected logic resource, and patches that logic entry in the combined directory. - Added
python3 -B tools/qemu_fixture.py v3-logic payload.bin --game-dir ...as a reusable CLI wrapper for the direct logic fixture path. - The test fixture uses a tiny synthetic
GRDIR/GRVOL.1layout, not private game files, to prove the direct-record append and directory patch can be read back throughread_volume_record(...). - The current local GR combined-directory counts are: logic
245slots /182present, picture245slots /186present, view256slots /247present, and sound51slots /44present.
Status:
- Basic v3 logic fixture writing is implemented and covered by focused unit tests.
- Generated v3 picture/view packing is intentionally still absent. Add it only when a targeted behavior probe needs generated picture or view payloads rather than original game resources.
- Verification after the CLI/docs update:
python3 -B -m py_compile tools/qemu_fixture.py tools/agi_resources.py,AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests(254tests),mdbook build docs,AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py, andgit diff --checkall passed.
Gold Rush v3 Room-Remap Behavior Probe
Goal: convert the source-backed GR action 0x12 room-target remap into an
original-engine compatibility check.
Commands and local reads:
AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py 73AGI_GAME_DIR=games/GR python3 -B tools/disassemble_logic.py 0python3 -B tools/setup_freedos_image.py --forcepython3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --output build/gr-v3-behavior/room_remap_build_001.jsonpython3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --run-qemu --output build/gr-v3-behavior/room_remap_qemu_001.json --boot-wait 5 --draw-wait 8python3 -B tools/gr_v3_behavior_probe.py --probe direct-draw --game-dir games/GR --picture 1 --fixture-root build/gr-v3-behavior/direct-draw-fixtures --output build/gr-v3-behavior/direct_draw_pic001_qemu_001.json --run-qemu --boot-wait 5 --draw-wait 8python3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --picture 1 --run-qemu --output build/gr-v3-behavior/room_remap_dispatch_qemu_pic001_001.json --boot-wait 5 --draw-wait 8python3 -B tools/gr_v3_behavior_probe.py --game-dir games/GR --picture 1 --run-qemu --output build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json --boot-wait 5 --draw-wait 8python3 -B tools/inspect_ppm.pyon the generated QEMU captures.
Probe construction:
- A direct draw sanity fixture patches GR logic
0topicture_logic_payload(1). QEMU capturebuild/gr-v3-behavior/direct-draw-fixtures/direct_draw/qemu_capture.ppmwas nonblank (14unique colors), proving that direct v3 logic replacement executes under the original GR interpreter. - The first room-remap fixture pair only patched logic
0to switch rooms and logic0x49to draw a marker picture. It produced equal but all-black captures. This was not accepted as evidence. - The failure exposed an important harness requirement: replacing logic
0removes the original global dispatch tail. GR logic0normally reachescall_logic_var(v0)near bytecode offset0x0ca0, so a custom logic0that switches rooms must also continue dispatching the current room. - The corrected
switch_room_payload()fires the switch once behind guard variablev250, then executescall_logic_var(v0)andendon each cycle. Logic0x49in both fixtures is patched to draw picture1.
QEMU result:
- Report:
build/gr-v3-behavior/room_remap_dispatch_qemu_pic001_001.json. - Direct target fixture: logic
0usesswitch_room_like(#0x49). - Alias target fixtures: logic
0usesswitch_room_like(#0x7e),switch_room_like(#0x7f), orswitch_room_like(#0x80). - All fixtures patch logic
0x49to the same picture-display payload. - The expanded QEMU report is
build/gr-v3-behavior/room_remap_all_qemu_pic001_001.json; all alias captures match direct target0x49. - The four QEMU captures are byte-identical:
45518c409f738a1fb2f4233db202f64d2e0e94011a9559e8ace0d952362814ab. inspect_ppmreports all four captures as640x400,14unique colors, and non-background bounding box(0, 0, 639, 399).
Conclusion:
- The source-backed GR helper
code.room.remap_reserved_room_targetis now dynamically validated for0x7e,0x7f, and0x80all mapping to0x49. - Final verification for this pass:
python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py,AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests(257tests),mdbook build docs,AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py, andgit diff --checkall passed.
Gold Rush v3 Key-Map Capacity Behavior Probe
Goal: convert the source-backed GR action 0x79 key-map capacity delta into an
observable original-engine check.
Commands and local reads:
rg -n "00004E9[0-9A-F]|00004EA[0-9A-F]|00004EB[0-9A-F]|00004EC[0-9A-F]|00004ED[0-9A-F]|00004EE[0-9A-F]|00004EF[0-9A-F]" build/gr-sq2-static/gr_agi_image.ndisasmrg -n "000063A[0-9A-F]|000063B[0-9A-F]|000063C[0-9A-F]|000063D[0-9A-F]|000063E[0-9A-F]" build/gr-sq2-static/gr_agi_image.ndisasmrg -n "0000970[0-9A-F]|0000971[0-9A-F]|0000972[0-9A-F]|0000973[0-9A-F]|0000974[0-9A-F]|0000975[0-9A-F]|0000976[0-9A-F]" build/gr-sq2-static/gr_agi_image.ndisasmpython3 -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 8python3 -B tools/inspect_ppm.pyon each generated capture.
Source observations:
- GR action
0x79at image0x4e98reads two operand bytes into a little-endian key/event word, reads one mapped status value, then scans slots rooted at[0x0145]. - The GR loop compares
DIwith0x31, so it can fill slots0..48. The SQ2 source-backed comparison showed the same handler shape but a loop bound of0x27, so SQ2 fills slots0..38. - GR action
0xadat image0x63a8stores byte[0x0405] = 1, GR-only action0xb5at image0x63b0stores[0x0405] = 0, and the GR keyboard IRQ hook at0x63b8tests[0x0405]before enqueueing a type-2 zero event on the selected scan-code release path. - GR-only action
0xb1at image0x970bstores its immediate operand into word[0x0403];code.menu.interactat0x9724returns immediately while that word is zero.
Probe construction:
- The new
key_map_capacity_payload()emits 48 dummy0x79mappings, then emits0x79('x', 0, 7)as the 49th mapping. The generated payload has49occurrences of opcode0x79, and the target mapping appears after48earlier mapping opcodes. - The positive fixture patches copied GR logic
0with that payload, sends typed keyxthrough the QEMU monitor, and draws original GR picture1only when status byte7is observed. - The direct fixture patches logic
0to draw picture1immediately. - The no-key control uses the same slot-48 mapping payload but sends no key.
QEMU result:
- Report:
build/gr-v3-behavior/key_map_capacity_qemu_pic001_002.json. - Expected matches:
slot_48_key_mapshould matchdirect_picture;slot_48_no_keyshould not. - The report matches those expectations exactly.
- Direct and keyed captures are byte-identical PPM files with SHA-256
45518c409f738a1fb2f4233db202f64d2e0e94011a9559e8ace0d952362814ab. inspect_ppmreports direct and keyed captures as640x400,14unique colors, and non-background bounding box(0, 0, 639, 399).- The no-key capture has one unique color and no non-background bounding box.
Conclusion:
- The source-backed GR key-map loop bound of
0x31is now dynamically validated for the final slot, showing that a mapping beyond SQ2’s0x27slot count is observable through the original GR interpreter’s event/status path. - The
[0x0405]key-release gate and[0x0403]menu-interaction gate remain source-backed; add raw scan-code/menu timing probes only if the final spec needs observable confirmation for those gate paths. - Final verification for this pass:
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests(259tests),mdbook build docs,python3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py,git diff --check, andAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.pyall passed.
Gold Rush v3 Save Object/Inventory XOR Model
Goal: turn the source-backed GR save transform into implementation-ready helper code and tests.
Commands and local reads:
sed -n '1,260p' tools/save_roundtrip_probe.pysed -n '1,240p' tools/agi_save.pypython3 -B tools/disassemble_logic.py --game-dir games/GR --limit 256 | rg "verify_game_signature|save_game_state|restore_game_state|copy_save_description|0x8f|0x7d|0x7e|0xaa"- Exact-offset local Python/
ndisasmreads for GR image offsets0x29e5,0x2aba,0x2b5b,0x2b7c,0x07bc, and0x2792. - Local Python byte read of the sequence starting at numeric image offset
0x072c; this was later recognized as a segment-confusion error, because the helper usesDI = 0x072cas a data-segment address. AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_save_resources.pypython3 -B -m py_compile tools/agi_save.py
Source observations:
- GR save action
0x7dat image0x29e5begins by computing[0x07d6] + [0x07da], then calls helper0x07bc(start=[0x07d6], end=[0x07d6]+[0x07da]). - The same action calls
0x07bcover the same range again at0x2b61before returning, after the selector/file I/O cleanup path. - The write sequence calls length-prefixed writer
0x2b7cfor five blocks. The third call writes start[0x07d6]with length[0x07da], so the XORed range is exactly the third saved state block. - Helper
0x07bcinitializesDI = 0x072c, XORs each byte in the caller range with byte[DI], increments both pointers, and when byte[DI]is zero it resetsDIto0x072c. - Rechecking the addressing against
games/GR/AGIDATA.OVLshows that data-segment address0x072ccontains the zero-terminated ASCII textAvis Durgan. The earlier 59-byte byte sequence came from reading main-code bytes at the same numeric offset and is not the save transform key.
Implementation/test updates:
- Added
GR_V3_OBJECT_INVENTORY_XOR_KEY,xor_with_repeating_key(...), andgr_v3_object_inventory_save_xor(...)totools/agi_save.py. - Added tests proving the GR transform uses the exact
Avis Durgankey, wraps after byte 10, matches the original-engine save prefix known vector, round trips, and rejects an empty generic XOR key.
Conclusion:
- The GR v3 save transform is now source-backed and executable in the local
save helper model. The current model describes the on-disk third block as the
Avis DurganXOR-transformed form of the runtime object/inventory block, with the second in-memory pass restoring runtime bytes before action return. - A later QEMU save-file extraction probe promoted the source-backed transform to original-engine evidence; see the next section. The promoted fixture uses a blank save prefix and does not resolve the GR verifier/save-prefix path.
- Final verification for this pass:
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests(262tests),mdbook build docs,python3 -B -m py_compile tools/agi_save.py tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py,git diff --check, andAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.pyall passed.
Gold Rush v3 Save Extraction Probe
Goal: confirm the source-mapped GR save XOR transform against a save file written by the original interpreter.
Commands and local reads:
sed -nreads ofPROGRESS.md,tools/gr_v3_behavior_probe.py,tools/save_roundtrip_probe.py,tools/qemu_fixture.py,tools/agi_save.py, and the relevant tests.- Initial local Python parsing used the wrong message offset base and led to the provisional, later-corrected hypothesis that GR message text might be plain in resource bytes. A follow-up pass below corrected this: GR logic message text is encrypted in the same observed message-text region.
strings -a -t x games/GR/AGIDATA.OVLandxxd -s 0x0700 -l 0x260 -g 1 games/GR/AGIDATA.OVLconfirmed the same message XOR key text exists in GR data at offset0x072c, while GR logic message resources observed in this pass are already readable.ndisasmreads around GR image offsets0x108c,0x245e,0x5035, and0x5ede, plus byte reads around file offset0x60d7, confirmed the local0x8fverifier/copy shape and embedded verifier string bytes.- Local script scan found GR’s original
0x8fuse in logic101at bytecode offset0x0004, with message number3. The raw encrypted bytes for that message are35 35 61; after decrypting from the message text-region start, the message isGR\0. AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_qemu_fixturepython3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tests/test_gr_v3_behavior_probe.py tests/test_qemu_fixture.py- Initial exploratory QEMU run with a synthetic
0x8f("GR")fixture exited before saving;mdirfound no save file, and the capture showed the later typed description at DOS. This result was not promoted as save behavior. - Promoted QEMU run:
python3 -B tools/gr_v3_behavior_probe.py --probe save-xor-extract --game-dir games/GR --fixture-root build/gr-v3-behavior/save-xor-fixtures --dos-prefix GRS --run-qemu --output build/gr-v3-behavior/save_xor_extract_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/save_xor_extract.raw --snapshot-qcow build/gr-v3-behavior/snapshot/save_xor_extract.qcow2 --post-run-raw build/gr-v3-behavior/snapshot/save_xor_extract_after.raw --save-output build/gr-v3-behavior/SG_001.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08
Implementation/test updates:
tools/qemu_fixture.pynow lets generated logic resources opt out of SQ2-style message text encryption withencrypt_messages=False; the default encrypted behavior remains correct for observed SQ2 and GR logic resources.tools/gr_v3_behavior_probe.pynow has--probe save-xor-extract. The promoted fixture omits0x8f verify_game_signature, so it writes a blank-prefixSG.1save and keeps the test focused on action0x7d.tests/test_qemu_fixture.pycovers encrypted-default and plain-message logic-resource construction.tests/test_gr_v3_behavior_probe.pycovers the GR save extraction payload, the optional verifier-message form, and stale save removal in copied fixtures.tools/compatibility_suite.pynow has an opt-inqemu-v3layer containing the GR save-XOR extraction probe; it is intentionally separate from the SQ2-oriented smoke/broad layers because it depends on privategames/GR.
QEMU result:
- Report:
build/gr-v3-behavior/save_xor_extract_qemu_001.json. - Suite-level report:
build/compatibility-suite/qemu_v3_save_001.json, whose namedgr_save_xor_extract_qemucommand returned zero and wrotebuild/gr-v3-behavior/save_xor_extract_suite.json. - Extracted save:
build/gr-v3-behavior/SG_001.1. - Description:
codex gr probe. - Block lengths:
1028,989,1811,100, and12. - First block begins with a blank signature prefix, as expected for a fixture
that does not call
0x8f. - The third block’s on-disk prefix is
c87769f82158e57363fb6f5dd6686f91457dca6606ac4011. - After
gr_v3_object_inventory_save_xor(), the third block prefix is8901008b011c9001049a011ca0011cb10108b80167c20167. - Applying the same XOR helper a second time restores the emitted third-block bytes. The report marks all checks passed.
Conclusion:
- GR action
0x7dis now both source-backed and original-engine validated for the v3 save envelope and third-block XOR transform. - This first promoted QEMU evidence deliberately avoids GR’s verifier/save-prefix path; the following correction section covers that path.
Gold Rush v3 Signed Save Extraction Correction
Goal: correct the GR message-encoding hypothesis and promote the
0x8f("GR") save-prefix path to original-engine evidence.
Commands and local reads:
python3 -B tools/disassemble_logic.py --game-dir games/GR 101- Exact-offset
ndisasmreads around GR image offsets0x108c,0x245e,0x5035, and0x5ec2..0x5eff. - Local Python parse of GR logic 101’s message area using table-base-relative offsets and decryption from the start of the message text region.
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_qemu_fixture tests.test_compatibility_suitepython3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/compatibility_suite.py tests/test_gr_v3_behavior_probe.py tests/test_compatibility_suite.pyAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --dry-run --include-qemu-v3- Signed QEMU run:
python3 -B tools/gr_v3_behavior_probe.py --probe save-xor-extract --verify-signature --game-dir games/GR --fixture-root build/gr-v3-behavior/save-xor-signed-fixtures --dos-prefix GRS --run-qemu --output build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/save_xor_extract_signed.raw --snapshot-qcow build/gr-v3-behavior/snapshot/save_xor_extract_signed.qcow2 --post-run-raw build/gr-v3-behavior/snapshot/save_xor_extract_signed_after.raw --save-output build/gr-v3-behavior/GRSG_001.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08
Source observations:
- GR action
0x8fat image0x108creads the immediate message number, resolves it through message helper0x245e, copies from that message pointer intoDS:0x0002with bounded copy helper0x5035, and then calls verifier helper0x5ede. - Helper
0x5ec2copies the embeddedGR\0string from code offset0x5ed7into data buffer0x0f88. Helper0x5edecomparesDS:0x0002against the embedded code string and calls the shared exit helper on the first mismatch. - GR logic 101 uses
0x8f(#3)near bytecode offset0x0004. Its message table has encrypted text bytes6f 76 4c 14 16 7d 75 35 35 61; decrypting the text region yields messages.\0,%g69\0, andGR\0. The earlier synthetic signed fixture failed because it storedGR\0in plain text, so the loader decrypted it into the wrong runtime bytes before the verifier compared it.
Implementation/test updates:
gr_save_extract_payload(verify_signature=True)now uses the normal encrypted-message default instead ofencrypt_messages=False.tools/gr_v3_behavior_probe.py --probe save-xor-extract --verify-signaturenow expectsGRSG.N, checks that the first save-state block starts withGR\0, and reportssignature_prefix: "GR".tools/compatibility_suite.pyadds named commandgr_signed_save_xor_extract_qemuin the opt-inqemu-v3layer.tests/test_gr_v3_behavior_probe.pynow asserts that the verifier message is encrypted in the fixture payload and decrypts toGR\0.
QEMU result:
- Report:
build/gr-v3-behavior/save_xor_extract_signed_qemu_001.json. - Extracted save:
build/gr-v3-behavior/GRSG_001.1. - Expected save file inside DOS:
GRSG.1. - Description:
codex gr probe. - Block lengths:
1028,989,1811,100, and12. - First block prefix bytes:
47 52 00 00 00 00 00 00. - Third-block encoded prefix and SHA-256 match the blank-prefix run:
c87769f82158e57363fb6f5dd6686f91457dca6606ac4011and00c9fc2f1cc1ff71f2779804f993dea7389227c486a016556c45a9a0fb63f6a8. - Third-block decoded prefix and SHA-256 also match the blank-prefix run:
8901008b011c9001049a011ca0011cb10108b80167c20167and5a833f40a62fc2e367e60600592d8033219586797a3e0a1b3a142accb64bc237.
Conclusion:
- GR’s
0x8fverifier/save-prefix path is now source-backed and original-engine validated for save creation. The correct generated fixture shape uses encrypted logic-message text, just like observed GR logic 101. - A follow-up pass below validates the restore side of the same signed save path.
Gold Rush v3 Signed Restore Round Trip
Goal: validate the source-mapped GR restore path for a signature-prefixed
GRSG.1 save without treating malformed save data as part of the behavioral
model.
Commands and local reads:
git status --shortrg -n "Highest-Value|signed|restore|Gold Rush|v3" PROGRESS.md docs/src/clean_room_executable_notes.md docs/src/runtime_model.md docs/src/versions.md docs/src/compatibility_testing.md docs/src/symbolic_labels.md docs/src/progress_log.mdsed -nreads oftools/gr_v3_behavior_probe.py,tools/save_roundtrip_probe.py,tools/qemu_fixture.py,tools/qemu_snapshot.py,tools/compatibility_suite.py, and the focused tests.rizin -q -a x86 -b 16 -c "pd 145 @ 0x2994" games/GR/AGIrizin -q -a x86 -b 16 -c "pd 75 @ 0x2b44" games/GR/AGIrizin -q -a x86 -b 16 -c "pd 35 @ 0x2ac8" games/GR/AGIrizin -q -a x86 -b 16 -c "pd 35 @ 0x09be" games/GR/AGI- A prior exploratory
rizinread without explicit-a x86 -b 16decoded the bytes as the host architecture. That output was discarded and not used as evidence. python3 -B -m py_compile tools/gr_v3_behavior_probe.py tests/test_gr_v3_behavior_probe.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probepython3 -B tools/gr_v3_behavior_probe.py --probe signed-restore-roundtrip --game-dir games/GR --fixture-root build/gr-v3-behavior/signed-restore-dryrun-fixtures --dos-prefix GRT --output build/gr-v3-behavior/signed_restore_roundtrip_dryrun_001.json- Direct QEMU run:
python3 -B tools/gr_v3_behavior_probe.py --probe signed-restore-roundtrip --game-dir games/GR --fixture-root build/gr-v3-behavior/signed-restore-qemu-fixtures --dos-prefix GRT --run-qemu --output build/gr-v3-behavior/signed_restore_roundtrip_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/signed_restore_roundtrip_001.raw --snapshot-qcow build/gr-v3-behavior/snapshot/signed_restore_roundtrip_001.qcow2 --post-run-raw build/gr-v3-behavior/snapshot/signed_restore_roundtrip_after_001.raw --save-output build/gr-v3-behavior/GRSG_restore_001.1 --boot-wait 5 --draw-wait 8 --path-prompt-wait 2 --slot-wait 1 --description-wait 1 --confirmation-wait 1 --key-delay 0.08
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suiteAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --dry-run --include-qemu-v3- The first suite-wrapper run of
gr_signed_restore_roundtrip_qemufailed before boot because sandboxed QEMU could not bind VNC (Failed to bind socket: Operation not permitted). The same named command passed after rerunning with escalation for local VNC binding:
AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_signed_restore_roundtrip_qemu --report build/compatibility-suite/qemu_v3_signed_restore_001.json
Source observations:
- GR restore action
0x7eis at image0x2792, which appears at raw file offset0x2994in the local MZ image. The prologue sets[0x0438] = 1, saves the caller continuation in[bp-0xce], temporarily stores0x40in[0x0b1c], and calls selector helper0x8aebwith mode/message byte0x72. - The success path opens the selected file through
0x625c, reads the 31-byte description with0x62f9, then calls the length-prefixed read helper at image0x2942/raw0x2b44for five blocks. - The five restore destinations mirror the GR save writer: first block into
0x0002, second into[0x07d0], third into[0x07d6], fourth into[0x153e], and fifth into0x07ea. - After the third block has been loaded and all five reads have succeeded, raw
0x2ad1..0x2ae0computes[0x07d6] + [0x07da]and calls image0x07bc/ raw0x09be, the same repeating-key XOR helper used by the save writer. This makes restore decode the on-disk object/inventory block back into its runtime representation. - The restore success path then restores display bytes from
[0x0f4f]and[0x0f51], updates the hardware-mode flag byte[0x001f], calls display and resource refresh helpers, clears[bp-0xce]to zero, refreshes menu/list state, and returns that zero continuation. This matches the existing model: successful restore restarts execution through restored state rather than continuing after opcode0x7e.
Implementation/test updates:
tools/gr_v3_behavior_probe.pynow has--probe signed-restore-roundtrip.- The probe builds a save-producing fixture whose logic calls
0x8f("GR"), sets a restored-marker flag and marker variables, and invokes0x7d. The generatedGRSG.1is extracted from the QEMU disk image. - A restore fixture copies that generated save into its generated game
directory, starts with a deliberately different marker X coordinate, calls
0x8f("GR"), and invokes0x7e. - The restore fixture begins each cycle with an
if flagbranch. Only a successful restore brings back the saved flag and saved X coordinate, causing the next cycle to draw the saved-state marker. If restore fails/cancels and continues after0x7e, the fixture draws the unrestored marker instead. - Direct comparison fixtures draw the expected saved-state marker and the unrestored-control marker without using save/restore UI.
tests/test_gr_v3_behavior_probe.pynow covers the signed restore save payload, restore payload, generated fixture copy ofGRSG.1, and direct comparison fixtures.tools/compatibility_suite.pyadds namedqemu-v3commandgr_signed_restore_roundtrip_qemu, with a manifest test intests/test_compatibility_suite.py.
QEMU result:
- Direct report:
build/gr-v3-behavior/signed_restore_roundtrip_qemu_001.json. - Suite report:
build/compatibility-suite/qemu_v3_signed_restore_001.json. - Underlying suite probe report:
build/gr-v3-behavior/signed_restore_roundtrip_suite.json. - The save-generation phase wrote and extracted
build/gr-v3-behavior/GRSG_restore_suite.1. - Save description:
codex gr probe. - Block lengths:
1028,989,1811,100, and12. - First block prefix bytes:
47 52 00 00 00 00 00 00. - Third block encoded SHA-256:
00c9fc2f1cc1ff71f2779804f993dea7389227c486a016556c45a9a0fb63f6a8. - Third block decoded SHA-256:
5a833f40a62fc2e367e60600592d8033219586797a3e0a1b3a142accb64bc237. - Capture comparison hashes from the suite probe:
- restored:
b16282219c5608e75e5b22a1fe3fe016f3ebeed52fa20b0b301260f02a3f713c - expected direct:
b16282219c5608e75e5b22a1fe3fe016f3ebeed52fa20b0b301260f02a3f713c - unrestored control:
160a4ed1bab5ec6eb901ae2c5e3198a081000c0261cf6ad89eec4033e88861b4
- restored:
- Checks all passed:
save_generation_passed,restored_matches_expected_direct,restored_differs_unrestored_control, andexpected_direct_differs_unrestored_control.
Conclusion:
- GR action
0x7eis now source-backed and original-engine validated for a valid, signature-prefixedGRSG.1restore. The v3 save/restore model should apply the repeatingAvis DurganXOR transform to the object/inventory block on both write and read sides, with successful restore returning through restored state rather than continuing after the restore opcode. - Malformed save behavior remains intentionally out of scope for the compatibility spec because invalid files can drive the original interpreter into garbage-memory/exploit-like behavior.
Gold Rush v3 Restart Prompt-Marker Truth Table
Goal: correct and model the GR-specific prompt-marker redraw branch in action
0x80.
Commands and local reads:
rg -n "GR restart|prompt-marker visible|redraws? (the )?marker|only if.*visible|restart preserves prompt|0x0403|0x0405|0x26e0" docs/src PROGRESS.md tools tests- Exact-offset local Python/
ndisasmreads for GR image offsets0x26e0,0x3b00,0x3ab0, and0x3ad9. AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests/test_restart_model.pypython3 -B -m py_compile tools/agi_restart.py
Source observations:
- GR action
0x80at image0x26e0calls helper0x3b00and stores the current prompt-marker visible word from[0x0dc3]in a local before erasing the marker through0x3ad9. - After confirmation/reset work, the branch at
0x276fcallscode.input.show_prompt_marker(0x3ab0) when the confirmation result is nonzero, or when the confirmation result is zero and the saved visible word is nonzero. - Therefore the redraw predicate is:
restart_was_accepted OR prompt_marker_was_visible_before_entry
Implementation/test updates:
- Added
tools/agi_restart.pywithgr_v3_restart_redraws_prompt_marker(accepted, marker_was_visible). - Added
tests/test_restart_model.pycovering all four truth-table rows.
Conclusion:
- The earlier shorthand “redraws only if it had been visible” was too narrow. The source-backed GR v3 model is: accepted restart redraws the marker; canceled restart redraws only when the marker had been visible on entry. A QEMU probe remains optional because this is a text/prompt-marker effect and the disassembly branch is direct.
- Final verification for this pass:
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests(263tests),mdbook build docs,python3 -B -m py_compile tools/agi_restart.py tools/agi_save.py tools/gr_v3_behavior_probe.py tools/qemu_fixture.py tools/agi_resources.py tools/compare_gr_sq2_static.py,git diff --check, andAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.pyall passed.
Gold Rush v3 Instrumented Motion Mode 4 Probe
Goal: validate the GR-specific motion dispatcher branch for object mode 4
without pretending ordinary bytecode can set that internal state directly.
Commands and local reads:
sed -n '560,640p' PROGRESS.mdsed -n '1,620p' tools/gr_v3_behavior_probe.pysed -n '320,900p' tools/qemu_fixture.pysed -n '2700,2945p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '12880,13150p' build/gr-sq2-static/gr_agi_image.ndisasm- Local Python scan of direct near-call targets in
games/GR/AGIfor image offsets0x1975,0x1888,0x18cf, and0x1909. - Local Python scan of present GR picture/view resources through
tools/agi_resources.py. AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probepython3 -B tools/gr_v3_behavior_probe.py --probe motion-mode-4 --game-dir games/GR --fixture-root build/gr-v3-behavior/motion-mode-4-fixtures --dos-prefix GRM --run-qemu --output build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.json --snapshot-raw build/gr-v3-behavior/snapshot/motion_mode_4.raw --snapshot-qcow build/gr-v3-behavior/snapshot/motion_mode_4.qcow2 --boot-wait 5 --draw-wait 8
Source observations:
- GR
code.motion.dispatch_mode_stepat image0x068areads object byte+0x22, decrements it, accepts values through3, and uses the embedded jump table at0x06bd. The fourth slot dispatches to the same helper at0x1888used by mode3. - SQ2 dispatcher
0x067aaccepts only decremented values through2, so SQ2 modes above3skip autonomous-mode dispatch. - GR action
0x51at image0x705cnormally stores byte3into object field+0x22, then seeds target X/Y and completion state and calls helper0x1888once. Action0x52follows the same mode-3 shape with variable operands. - The helper-shaped code at image
0x1975writes object 0 byte+0x22 = 4, target X/Y bytes+0x27/+0x28, and saved step byte+0x29when[0x0139]is nonzero. A direct near-call scan found no ordinary call to0x1975in the local GR main image, and no bytecode action table entry points at it. Its natural entry path remains unresolved/source-only. - The first attempt to patch action
0x51used the loaded-image offset as a file offset and failed with context bytes8b f8 a1 d0instead ofc6 45 22 03. The patch helper now translates loaded-image offsets through the MZ header size when the copied interpreter begins withMZ.
Implementation/test updates:
- Added
--probe motion-mode-4totools/gr_v3_behavior_probe.py. - The probe builds three copied GR fixtures under
build/: stationary object, unmodified action-0x51mode-3 movement, and an instrumented copy where byte0x03at loaded-image offset0x707fis patched to0x04. - Added tests covering the generated motion payload, the expected patch context, rejection of unexpected interpreter bytes, and fixture construction.
QEMU result:
- Report
build/gr-v3-behavior/motion_mode_4_qemu_pic001_001.jsonpassed. - The instrumented mode-4 capture matched the unmodified mode-3 movement
capture from
(20,80)to(50,80)on GR picture 1/view 0. - The stationary control did not match the moving capture.
Conclusion:
- The GR v3 internal dispatcher branch for object motion mode
4is now instrumented-QEMU-validated: once mode4exists in object byte+0x22, it follows the same visible target-direction path as mode3. - This is not evidence that ordinary logic bytecode can create mode
4; the natural seeding path is still source-only and should remain separate in the implementation spec.
Gold Rush v3 Frame-Selection Gate Probe
Goal: resolve and validate the GR-specific branch inside
code.object.frame_timer_update (0x055c) for automatic direction-based group
selection on views with exactly four groups versus more than four groups.
Commands and local reads:
sed -n '560,700p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '600,690p' build/gr-sq2-static/sq2_agi_image.ndisasm- Local Python census of GR view resources through
tools/agi_resources.py, using payload byte+0x02as the group count and group offsets at+0x05. - Local decoded-frame comparison of candidate GR views 33, 39, 177, and other four-plus-group resources.
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probepython3 -B tools/gr_v3_behavior_probe.py --probe frame-selection-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/frame-selection-fixtures --dos-prefix GRF --run-qemu --output build/gr-v3-behavior/frame_selection_gate_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/frame_selection_gate.raw --snapshot-qcow build/gr-v3-behavior/snapshot/frame_selection_gate.qcow2 --boot-wait 5 --draw-wait 8
Source observations:
- SQ2
code.object.frame_timer_updateat image0x0563uses the two/three group table when object byte+0x0bis 2 or 3. When+0x0b >= 4, it indexes the four-plus group table at data0x08e7. - GR
code.object.frame_timer_updateat image0x055ckeeps the two/three group path, then differs at image0x05ac:- If
+0x0b == 4, it jumps directly to the four-plus table path at0x05c6. - Otherwise it tests flag
0x14via helper0x7818. - If flag
0x14is clear, it skips selection and leaves the target as sentinel4. - If flag
0x14is set, it still requires+0x0b > 4before using the four-plus table.
- If
- Therefore the earlier shorthand was backwards for exactly-four-loop views:
exactly four groups are not excluded; they bypass the new flag gate and use
the same four-plus table as SQ2. Only group counts greater than four are
gated on flag
0x14. - Local GR resources include suitable stock views, so no synthetic v3 view
packing was needed:
- View 177 has exactly four groups, with visibly distinct group 0 and group 1 frames.
- View 39 has more than four groups, with visibly distinct group 0 and group 1 frames.
Implementation/test updates:
- Added
--probe frame-selection-gatetotools/gr_v3_behavior_probe.py. - The probe builds copied GR fixtures under
build/for group-0/group-1 controls, exact-four flag-clear/flag-set cases, and more-than-four flag-clear/flag-set cases. It uses ordinary logic bytecode only; the GR interpreter is not patched. - Added focused tests covering the generated gate payload, control payload, and fixture case list.
QEMU result:
- Report
build/gr-v3-behavior/frame_selection_gate_qemu_001.jsonpassed. - Exact-four view 177 selected group 1 for direction
6both with flag0x14clear and with flag0x14set; both captures matched the group-1 control and did not match the group-0 control. - More-than-four view 39 remained on group 0 while flag
0x14was clear; after flag0x14was set, it selected group 1. The group-0 and group-1 controls were distinct.
Conclusion:
- GR / AGI v3 automatic direction group selection should be modeled as:
two/three groups use the two/three table; exactly four groups use the
four-plus table; more than four groups use the four-plus table only when flag
0x14is set. Sentinel target4still means “do not change group.”
2026-07-10: GR v3 restart prompt-marker QEMU confirmation
Goal: confirm the source-mapped GR action 0x80 canceled-restart
prompt-marker branch with an original-engine fixture.
Commands and local reads:
sed -n '619,700p' PROGRESS.mdsed -n '8960,9035p' docs/src/clean_room_executable_notes.mdsed -n '1,260p' tools/agi_restart.pysed -n '1,260p' tests/test_restart_model.pysed -n '1,360p' tools/gr_v3_behavior_probe.pysed -n '820,1320p' tools/gr_v3_behavior_probe.pyrg -n "restart|prompt|input_line|0x77|0x78|confirm_restart|show_prompt" tools/logic_interpreter_probe.py tests/test_logic_interpreter_probe.py docs/src/runtime_model.md docs/src/logic_bytecode.mdpython3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/compatibility_suite.py tests/test_gr_v3_behavior_probe.py tests/test_compatibility_suite.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suite tests.test_restart_modelpython3 -B tools/gr_v3_behavior_probe.py --probe restart-prompt-marker --game-dir games/GR --fixture-root build/gr-v3-behavior/restart-prompt-dryrun-fixtures --dos-prefix GRP --output build/gr-v3-behavior/restart_prompt_marker_dryrun_001.jsonpython3 -B tools/gr_v3_behavior_probe.py --probe restart-prompt-marker --game-dir games/GR --fixture-root build/gr-v3-behavior/restart-prompt-qemu-fixtures --dos-prefix GRP --run-qemu --output build/gr-v3-behavior/restart_prompt_marker_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/restart_prompt_marker_001.raw --snapshot-qcow build/gr-v3-behavior/snapshot/restart_prompt_marker_001.qcow2 --boot-wait 5 --draw-wait 8AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_restart_prompt_marker_qemu --report build/compatibility-suite/qemu_v3_restart_prompt_001.json
Source-first model:
- The earlier source pass showed GR action
0x80recording the current prompt-marker visible word before erasing it. - After confirmation, accepted restart always redraws the marker; canceled restart redraws the marker only when it was visible on entry.
- The local helper
gr_v3_restart_redraws_prompt_marker()keeps this truth table pinned for implementation use.
Fixture implementation:
- Added
--probe restart-prompt-markertotools/gr_v3_behavior_probe.py. - The probe builds four copied GR fixtures under
build/: hidden control, visible control, hidden then Escape-canceled restart, and visible then Escape-canceled restart. - The fixture logic uses ordinary bytecode only: picture load/show, prompt
marker message setup through
0x6c, input row setup through0x6f, hidden or visible marker setup through0x77/0x78, and restart confirmation through0x80. - The QEMU report compares both whole captures and the foreground-pixel count
in logical prompt rectangle
(0,40)..(39,47). - The v3 compatibility suite now has named command
gr_restart_prompt_marker_qemu.
QEMU result:
- Direct report
build/gr-v3-behavior/restart_prompt_marker_qemu_001.jsonpassed. - Suite report
build/compatibility-suite/qemu_v3_restart_prompt_001.jsonpassed after rerunning with VNC socket permission; the first unprivileged attempt failed before QEMU saved the DOS snapshot withFailed to bind socket: Operation not permitted. - Hidden control and hidden canceled restart had 0 prompt-row foreground
pixels and identical capture hash
82d824134a00e40ae092e86b396b4b712e1d8ad48e7ad181d484ffbe8fa79f28. - Visible control and visible canceled restart had 8 prompt-row foreground
pixels and identical capture hash
cd7a1c8f5bf5eee32a6e818fb1c49d274db9175de9cbc7ed3f95a721df0e5a96.
Conclusion:
- The original GR interpreter confirms the canceled-restart half of the
source-backed truth table: Escape-canceled restart restores the prompt marker
only when it was visible before action
0x80erased it. The accepted-restart half remains source-backed because the reset path immediately restarts engine state, making a clean visual oracle less useful than the direct branch.
2026-07-10: GR v3 menu interaction gate QEMU confirmation
Goal: confirm the source-mapped GR-only action 0xb1
set_menu_interaction_gate with an original-engine fixture.
Commands and local reads:
git status --shortrg -n "menu-gate|menu gate|0xb1|Highest-Value|raw key-release|GR / SQ2|Menus|Compatibility|restart prompt|set_menu_interaction|code.menu.interact|0403" PROGRESS.md docs/src/versions.md docs/src/runtime_model.md docs/src/logic_bytecode.md docs/src/compatibility_testing.md docs/src/symbolic_labels.md docs/src/clean_room_executable_notes.md docs/src/progress_log.md AGENTS.mdsed -n '1,220p' PROGRESS.mdtail -n 80 docs/src/progress_log.mdsed -n '40,180p' docs/src/versions.mdsed -n '470,530p' docs/src/runtime_model.mdsed -n '110,140p' docs/src/logic_bytecode.mdsed -n '1488,1504p' docs/src/logic_bytecode.mdsed -n '1080,1135p' docs/src/compatibility_testing.mdsed -n '150,190p' AGENTS.mdtail -n 120 docs/src/clean_room_executable_notes.mdsed -n '500,640p' PROGRESS.mdsed -n '250,285p' docs/src/symbolic_labels.mdsed -n '428,545p' docs/src/symbolic_labels.mdsed -n '540,610p' PROGRESS.mdrg -n "282|test" PROGRESS.md docs/src/compatibility_testing.mdsed -n '1,260p' tools/gr_v3_behavior_probe.pysed -n '494,516p' docs/src/runtime_model.mdsed -n '976,990p' docs/src/compatibility_testing.mdsed -n '70,90p' docs/src/compatibility_testing.mdsed -n '1478,1530p' docs/src/compatibility_testing.mdsed -n '380,438p' tools/compatibility_suite.pyrg -n "0403|1b67|970B|9724|038D|38d|000097|000038" build/gr-sq2-static/gr_agi_image.ndisasmsed -n '17600,17760p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '4250,4320p' build/gr-sq2-static/gr_agi_image.ndisasmpython3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/compatibility_suite.py tests/test_gr_v3_behavior_probe.py tests/test_compatibility_suite.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suitepython3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-dryrun-fixtures --dos-prefix GRG --output build/gr-v3-behavior/menu_gate_dryrun_001.jsonAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --dry-run --include-qemu-v3python3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-qemu-fixtures --dos-prefix GRG --run-qemu --output build/gr-v3-behavior/menu_gate_qemu_001.json --snapshot-raw build/gr-v3-behavior/snapshot/menu_gate_001.raw --snapshot-qcow build/gr-v3-behavior/snapshot/menu_gate_001.qcow2 --boot-wait 5 --draw-wait 8python3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-qemu-fixtures-2 --dos-prefix GRG --run-qemu --output build/gr-v3-behavior/menu_gate_qemu_002.json --snapshot-raw build/gr-v3-behavior/snapshot/menu_gate_002.raw --snapshot-qcow build/gr-v3-behavior/snapshot/menu_gate_002.qcow2 --boot-wait 5 --draw-wait 8python3 -B tools/gr_v3_behavior_probe.py --probe menu-gate --game-dir games/GR --fixture-root build/gr-v3-behavior/menu-gate-qemu-fixtures-3 --dos-prefix GRG --run-qemu --output build/gr-v3-behavior/menu_gate_qemu_003.json --snapshot-raw build/gr-v3-behavior/snapshot/menu_gate_003.raw --snapshot-qcow build/gr-v3-behavior/snapshot/menu_gate_003.qcow2 --boot-wait 5 --draw-wait 8AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_menu_gate_qemu --report build/compatibility-suite/qemu_v3_menu_gate_001.json
Source-first model:
- GR-only action handler
0x970breads one immediate byte, zero-extends it, and stores the word at[0x0403]. - GR action
0xa1at0x96ebstill tests flag 14 and writes word[0x1b67] = 1as the ordinary menu request. - The GR main-cycle path around image
0x38ddnotices[0x1b67] != 0and callscode.menu.interactat image0x9724. code.menu.interactfirst compares word[0x0403]with zero. If it is zero, it returns immediately; if nonzero, it proceeds into the existing draw/wait modal menu path. Therefore0xb1is an interaction gate, not part of menu construction.
Fixture implementation:
- Added
--probe menu-gatetotools/gr_v3_behavior_probe.py. - The final probe builds three copied GR fixtures under
build/:menu_gate_blocked_control,menu_gate_enabled_request, andmenu_gate_disabled_request. - The blocked control draws a visible object marker at the blocked location.
- The request fixtures build a one-heading, one-item menu, finalize it, set
flag 14, execute
0xb1(1)or0xb1(0), execute0xa1, and then end the logic stream so the top-level engine cycle can service[0x1b67]. - The promoted oracle does not depend on pressing Enter inside the menu. It
checks only the gate:
0xb1(0)should match the blocked control, while0xb1(1)should differ by entering the modal menu path.
Important correction:
- An earlier fixture ended with a self-loop. That kept logic 0 executing inside
the interpreter and prevented the top-level main cycle from reaching the
[0x1b67]check. Both enabled and disabled cases therefore matched the blocked control even though the generated0xb1bytes were correct. - Replacing the self-loop with the structural
endaction allowed the original engine’s main cycle to process the request. The Enter-driven accepted-marker oracle still was not stable, so the final evidence uses a no-key modal-gate oracle.
QEMU result:
- Direct report
build/gr-v3-behavior/menu_gate_qemu_003.jsonpassed. menu_gate_disabled_requestmatchedmenu_gate_blocked_control.menu_gate_enabled_requestdiffered from both the blocked control and the disabled request.- Capture SHA-256 values:
- blocked control and disabled request:
160a4ed1bab5ec6eb901ae2c5e3198a081000c0261cf6ad89eec4033e88861b4 - enabled request:
e463cb17d86267bda970277df82d51c6b51dc743327f51c856a25de65399155b
- blocked control and disabled request:
- The named suite command
build/compatibility-suite/qemu_v3_menu_gate_001.jsonpassed after rerunning with VNC socket permission; the first unprivileged attempt failed before QEMU launched withFailed to bind socket: Operation not permitted.
Conclusion:
- The original GR interpreter confirms the disassembly model for action
0xb1: zero blocks a requested menu before the modal menu draw/wait path, and nonzero permits the existing menu interaction path to run after0xa1.
2026-07-10: SQ2/GR tracked key-release IRQ source model
Goal: turn the remaining source-backed raw key-release gate into an implementation-facing state model without relying on QEMU keyboard-release timing.
Commands and local reads:
git status --shortsed -n '621,640p' PROGRESS.mdrg -n "key-release|key release|0xad|0xb5|0405|1530|release_event|tracked-key|type-2 zero|raw key" PROGRESS.md docs/src tools tests build/gr-sq2-static/gr_agi_image.ndisasm build/gr-sq2-static/sq2_agi_image.ndisasmrg -n "map_key|raw_key|status_byte|key_release|release|sendkey|post_launch_key_names|post_launch_keys|key_names" tools/logic_interpreter_probe.py tools/gr_v3_behavior_probe.py tests/test_logic_interpreter_probe.py tests/test_gr_v3_behavior_probe.pysed -n '10870,11140p' build/gr-sq2-static/sq2_agi_image.ndisasmsed -n '11220,11480p' build/gr-sq2-static/gr_agi_image.ndisasmsed -n '188,240p' docs/src/symbolic_labels.mdsed -n '470,505p' docs/src/logic_bytecode.mdsed -n '1285,1305p' docs/src/compatibility_testing.mdsed -n '5677,5708p' docs/src/clean_room_executable_notes.mdrg -n "Restart|Save|heap|motion|keyboard|input|model|source-modeled|class .*Model|def .*model|gr_v3" tools tests | head -n 200ls tools | sortls tests | sortsed -n '1,220p' tools/agi_restart.pysed -n '1,220p' tests/test_restart_model.pypython3 -B -m py_compile tools/agi_input.py tests/test_input_model.pypython3 -B -m unittest tests.test_input_model
Source observations:
- SQ2 action
0xadat image0x602fisinc byte [0x1530], so the gate is an unsigned byte and wraps from0xffto zero. - GR action
0xadat image0x63a8stores byte[0x0405] = 1. - GR-only action
0xb5at image0x63b0stores byte[0x0405] = 0. - The SQ2 keyboard IRQ hook at image
0x6036and the GR hook at image0x63b8have the same tracked-key latch shape after relocation:- read the raw scan byte from port
0x60; - mask off bit
0x80and accept only scan codes0x47..0x51; - require the corresponding enable-table byte to be nonzero;
- on keydown, if the selected latch was clear, clear all tracked latches and set only the selected latch;
- on duplicate keydown, do not enqueue a script event;
- on key release, clear the selected latch only if it had been set;
- enqueue event
(type=2, value=0)only when that release path cleared a latch and the version-specific gate byte is nonzero.
- read the raw scan byte from port
Implementation/test updates:
- Added
tools/agi_input.pywith a portableKeyReleaseIrqStatemodel, SQ2/GR gate-writer helpers, andprocess_tracked_key_irq_scan(). - Added
tests/test_input_model.pyto cover:- SQ2
0xadenabling a later release event; - SQ2 byte wraparound from
0xffto zero; - GR
0xad/0xb5set/clear behavior; - keydown clearing other tracked latches;
- disabled and out-of-range scan bytes producing no event;
- model validation for table lengths and gate byte range.
- SQ2
Conclusion:
- The tracked release-key behavior is now source-modeled at a portable state level. A direct QEMU fixture remains optional only if the final target needs raw hardware IRQ timing evidence; it is not necessary for the valid-data AGI semantics currently being specified.
2026-07-10: v3 generated picture/view fixture packing
Goal: remove the remaining fixture-writer gap for targeted Gold Rush / AGI v3
graphics probes without modifying private local inputs under games/.
Commands and local reads:
git status --shortsed -n '621,640p' PROGRESS.mdrg -n "does not yet pack|direct-record logic|v3.*picture/view|picture-nibble|v3-synthetic|v3 fixture" AGENTS.md PROGRESS.md docs/src tests toolssed -n '220,340p' docs/src/resource_files.mdsed -n '1,220p' docs/src/versions.mdsed -n '660,900p' tools/qemu_fixture.pysed -n '900,980p' tools/qemu_fixture.pysed -n '1260,1365p' tools/qemu_fixture.pyrg -n "def encode_picture_nibbles|encode_picture_nibbles" tools/agi_resources.py tests/test_agi_resources.py tests/test_qemu_fixture.pysed -n '265,345p' tools/agi_resources.pypython3 -B -m py_compile tools/agi_resources.py tools/qemu_fixture.py tests/test_agi_resources.py tests/test_qemu_fixture.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_agi_resources tests.test_qemu_fixture
Source/model observations:
- The v3 generic reader accepts direct records when the expanded and stored lengths in the 7-byte record header are equal. That path is already present in original GR resources and is suitable for controlled generated logic/view fixtures.
- The v3 picture path is selected by metadata bit
0x80; the low nibble still names the volume and must match the directory entry volume. The stored payload is a nibble stream that expands to ordinary picture bytes. - The inverse picture-nibble writer packs ordinary bytes as two nibbles, except
the operand following picture commands
0xf0and0xf2, which is stored as one nibble. The generated expanded stream must end at0xff.
Implementation/test updates:
- Added
encode_picture_nibbles()totools/agi_resources.pyas the inverse of the observed GR picture-nibble expansion for valid expanded picture streams. - Added v3 fixture helpers in
tools/qemu_fixture.py:v3_picture_volume_record(), genericpatch_v3_resource(),patch_v3_picture_resource(),patch_v3_view_resource(),build_v3_synthetic_picture_fixture(), andbuild_v3_synthetic_picture_view_fixture(). - Added CLI commands
v3-synthetic-pictureandv3-synthetic-picture-view. - Added local read-back tests for the picture-nibble record wrapper, copied v3 picture patching, copied v3 view patching, and full synthetic picture/view fixture construction.
Conclusion:
- Targeted v3 picture/view probes can now be generated under
build/without patchinggames/. The project still does not implement a v3 dictionary compressor; generated view payloads use the original interpreter’s direct-record path, while original compressed local resources continue to be decoded through the observed reader model.
2026-07-10: v3 synthetic picture/view fixture QEMU probe
Goal: confirm the generated GR v3 picture/view fixture writer against the original interpreter after adding local read-back tests.
Commands and local reads:
git status --shortsed -n '1,180p' PROGRESS.mdsed -n '180,360p' PROGRESS.mdsed -n '620,660p' PROGRESS.mdsed -n '1,260p' tools/gr_v3_behavior_probe.pysed -n '260,620p' tools/gr_v3_behavior_probe.pysed -n '620,980p' tools/gr_v3_behavior_probe.pysed -n '980,1380p' tools/gr_v3_behavior_probe.pysed -n '1380,1760p' tools/gr_v3_behavior_probe.pysed -n '1760,2140p' tools/gr_v3_behavior_probe.pysed -n '1,260p' tests/test_gr_v3_behavior_probe.pysed -n '260,620p' tests/test_gr_v3_behavior_probe.pysed -n '1,220p' tests/test_compatibility_suite.pysed -n '1,340p' tools/compatibility_suite.pysed -n '430,510p' tests/test_qemu_fixture.pypython3 -B -m py_compile tools/gr_v3_behavior_probe.py tools/compatibility_suite.py tests/test_gr_v3_behavior_probe.py tests/test_compatibility_suite.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_gr_v3_behavior_probe tests.test_compatibility_suitepython3 -B tools/gr_v3_behavior_probe.py --probe synthetic-picture-view --game-dir games/GR --fixture-root build/gr-v3-behavior/synthetic-picture-view-fixtures --dos-prefix GSP --output build/gr-v3-behavior/synthetic_picture_view_001.jsonAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --name gr_synthetic_picture_view_qemu --report build/compatibility-suite/qemu_v3_synthetic_picture_view_001.json- Repeated the same compatibility-suite command with elevated execution after
the first QEMU attempt failed before launch with
Failed to bind socket: Operation not permitted.
Fixture design:
- The blank control patches logic 0 to loop without drawing.
- The picture-only fixture patches logic 0 to show generated picture 0 and
stores picture 0 as a v3 picture-nibble record in
GRVOL.1. - The picture/view fixture uses the same generated picture plus a direct v3
view 0 record. The logic draws the picture, loads view 0, and uses action
0x7ato place group 0 frame 0 at(20,80)with priority15. - The generated picture payload is
f0 01 f8 50 50 ff: select visual color 1, seed fill at(80,80), then terminate. - The generated view payload is a one-loop, one-frame, 4x4 opaque run-length
encoded cel with color 4 and transparent color 0:
00 00 01 00 00 07 00 01 03 00 04 04 00 44 00 44 00 44 00 44 00.
Implementation/test updates:
- Added
--probe synthetic-picture-viewtotools/gr_v3_behavior_probe.py. - Added
build_gr_synthetic_picture_view_fixtures()and a QEMU reducer that compares blank, picture-only, and picture-plus-view captures. - Added local tests that read the generated v3 records back through
tools/agi_resources.pyand confirm the expected picture-nibble/direct transforms. - Added compatibility-suite command
gr_synthetic_picture_view_qemu.
QEMU result:
build/gr-v3-behavior/synthetic_picture_view_suite.jsonpassed.- The blank control had one unique color.
- The picture-only capture had two unique colors and differed from blank by 215,040 pixels.
- The picture-plus-view capture had three unique colors and differed from picture-only by 128 pixels.
- Capture hashes:
- blank control:
f3ee47648d6ba080ffab59f9c5cc84d66a44ee6de07c5fa3edbe222e95021062 - picture only:
e9b3a51fc2fe85e39ba7c88c726e4835d8586d6cfa50bea45727e08e71a424a4 - picture plus view:
a1680189d4c06001263bbcec3edcea29b9323ca584e2a533918693d5cc60113a
- blank control:
Conclusion:
- The original GR interpreter accepts the generated picture-nibble picture record and direct view record for this controlled fixture. This promotes the v3 fixture writer as reusable compatibility infrastructure while preserving the source-backed renderer/model distinction.
2026-07-10: sound/audio subsystem specification pass
Goal: consolidate the already source-backed sound findings into a dedicated implementation-facing mdBook chapter.
Commands and local reads:
git status --shortsed -n '1,220p' PROGRESS.mdsed -n '420,660p' PROGRESS.mdtail -n 60 docs/src/progress_log.mdrg -n "Sound|sound|audio|tone|PC-speaker|speaker|duration|waveform|Remaining|0x62|0x63|0x64" PROGRESS.md docs/src tools testsrg --files docs/src tools tests | rg "sound|audio"sed -n '1,220p' docs/src/SUMMARY.mdsed -n '135,205p' docs/src/runtime_model.mdsed -n '300,340p' docs/src/symbolic_labels.mdsed -n '1,260p' tests/test_sound_resources.pysed -n '1,260p' tools/agi_sound.pysed -n '260,360p' tools/agi_sound.pyrg -n "Sound/audio|Sound|sound|audio|Highest-Value|Final spec|Remaining" PROGRESS.mdtail -n 80 docs/src/progress_log.mdtail -n 80 docs/src/clean_room_executable_notes.mdsed -n '100,220p' docs/src/current_status.mdsed -n '520,552p' PROGRESS.mdsed -n '630,652p' PROGRESS.mdsed -n '1,80p' docs/src/resource_files.mdsed -n '1,80p' docs/src/graphics_object_pipeline.mdpython3 -B -m unittest tests.test_sound_resourcesAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_sound_resourcesmdbook build docsgit diff --checkgit status --shortfind tests tools -type d -name __pycache__ -print
Observations consolidated:
- Sound payloads begin with four little-endian channel offsets, followed by
duration/tone/control channel streams terminated by duration
0xffff. - The current SQ2 corpus has 49 present sound resources, all with four sorted in-bounds channel offsets and terminating streams.
0x62,0x63, and0x64expose sound loading, start-with-completion-flag, and stop/clear behavior to logic bytecode.- Driver start initializes channel countdowns to
1, so the first channel record is consumed on the first active sound tick. - Selectors
0and8advance only channel 0; other observed selectors advance all four channels. - Flag 9 is a playback gate tested at the start of the tick path. Clearing it causes immediate stop/completion on tick 1.
- The PC-speaker path computes a divisor from the event tone word and treats
attenuation nibble
0x0fas silence. - The non-PC path emits tone bytes and channel/attenuation bytes to port
0xc0; the stop path emits0x9f 0xbf 0xdf 0xff. - The attenuation envelope table uses signed deltas from the event base
attenuation and terminates with sentinel
0x80.
Documentation updates:
- Added
docs/src/sound_and_audio.md. - Added the new chapter to
docs/src/SUMMARY.md. - Updated
PROGRESS.mdanddocs/src/current_status.mdto reference the dedicated sound/audio chapter.
Validation:
- The first focused sound test command failed with the expected explicit-game
guard:
game directory required; pass --game-dir PATH or set AGI_GAME_DIR. AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_sound_resourcespassed 16 tests.mdbook build docspassed.git diff --checkpassed.- No Python
__pycache__directories were present undertests/ortools/after the focused test run.
Conclusion:
- The sound/audio subsystem now has a chapter-level contract for resource parsing, playback scheduling, completion flags, and hardware-driver output boundaries. Analog waveform synthesis remains explicitly outside the current interpreter compatibility target.
2026-07-10: read-only multi-game census tooling
Goal: create a reproducible source-first inventory step for the additional
local game directories without modifying private inputs under games/.
Commands and local reads:
sed -n '640,670p' PROGRESS.mdsed -n '1,260p' docs/src/versions.mdsed -n '1,220p' docs/src/cross_version_workflow.mdrg -n "v3|GR|Gold|compression|picture-nibble|direct view|loader error|static delta|behavioral" docs/src PROGRESS.md tools testsrg -n "v3|GR|combined|dictionary|nibble|prefixed|expanded|stored|compression|Version|SQ2|sound" docs/src/resource_files.mdsed -n '80,240p' docs/src/resource_files.mdsed -n '228,390p' docs/src/resource_files.mdsed -n '1,260p' tools/agi_resources.pysed -n '260,560p' tools/agi_resources.pysed -n '1,180p' tests/test_agi_resources.pyfind games -maxdepth 2 -type ffind games -maxdepth 1 -type dgit ls-files toolssed -n '1,220p' tools/project_paths.pysed -n '1,220p' tests/test_compatibility_suite.pysed -n '1,220p' tests/test_qemu_fixture.pysed -n '1,120p' .gitignorepython3 -B -m py_compile tools/game_census.py tests/test_game_census.pypython3 -B -m unittest tests.test_game_censuspython3 -B tools/game_census.py --games-root games --format json --output build/cross-version/game_census.jsonpython3 -B tools/game_census.py --games-root games --format markdown --output build/cross-version/game_census.mdsed -n '1,80p' build/cross-version/game_census.mdpython3 -m json.tool build/cross-version/game_census.jsonmdbook build docsgit diff --checkAGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testsfind tests tools -type d -name __pycache__ -printrm -rf tests/__pycache__ tools/__pycache__AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --checkAGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_game_census_dispatch_001.jsonpython3 -m json.tool build/compatibility-suite/local_game_census_dispatch_001.json
Implementation:
- Added
tools/game_census.py. - The tool requires one or more explicit
--game-dirpaths or an explicit--games-rootpath. - It detects v2 split and v3 combined layouts through
tools/agi_resources.py. - It extracts local
Version ...strings from known interpreter data files. - It counts entries, present entries, volumes, readable record transforms, and stored/expanded byte totals per resource family.
- It records per-record header/expansion errors instead of aborting the whole inventory.
- Added synthetic tests in
tests/test_game_census.pyfor split layout, combined layout, version extraction, deduplication, and Markdown formatting.
Validation:
python3 -B -m py_compile tools/game_census.py tests/test_game_census.pypassed.python3 -B -m unittest tests.test_game_censuspassed 5 tests.python3 -B -m unittest tests.test_game_census tests.test_agi_resourcespassed 13 tests.mdbook build docspassed.git diff --checkpassed.AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testspassed 304 tests.- Removed the generated
tests/__pycache__andtools/__pycache__directories after the full test run.
Current private-input census:
- v2 split layouts: KQ1
Version 2.917, KQ2Version 2.411, KQ3Version 2.936, LSL1Version 2.440, PQ1Version 2.917, and SQ2Version 2.936. - v3 combined layouts: KQ4D
Version 3.002.102withDMDIR/DMVOL.N, and GRVersion 3.002.149withGRDIR/GRVOL.N. - GR has no record errors under the current v3 reader.
- SQ2 still reports the two known out-of-range end entries from LOGDIR/PICDIR.
- KQ1 has four sound entries that fail the generic v2 header check.
- KQ4D has multiple suspect sound-section entries that fail the generic v3 header check. These should be source-inspected before any behavioral rule is added.
Conclusion:
- The project now has a repeatable first-pass inventory for future cross-version comparison. Record errors are planning evidence for later disassembly, not part of the clean-room behavioral model for valid resources.
2026-07-10: KQ4D dispatch-table detection and sound references
Goal: follow up on the KQ4D census errors by fixing v3 logic disassembly for non-GR table bases and checking whether decoded KQ4D scripts reference the suspect sound-section entries.
Commands and local reads:
ls -l games/KQ4D/DMDIR games/KQ4D/DMVOL.* games/KQ4D/AGIxxd -g1 -l 96 games/KQ4D/DMDIRxxd -g1 -s 0x2d5 -l 256 games/KQ4D/DMDIRfile games/KQ4D/AGI games/GR/AGI games/SQ2/AGI- Python local decode of KQ4D
DMDIRsound entries, printing present-like indices, raw triples, target volume/offsets, and target header bytes. python3 -B tools/disassemble_logic.py --helpwithout a game directory, which failed with the expected explicit-game guard.AGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --helpAGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --statsbefore the fix, showing garbage table data from the old GR-specific v3 table bases.- Python local scans comparing SQ2/GR/KQ4D AGIDATA table bytes and searching for action/condition argc/meta signatures.
rg -n "dispatch_table_layout|load_table|ACTION_NAMES|COND_NAMES|disassemble_logic" tests tools docs/srcsed -n '430,530p' tools/disassemble_logic.pyrg -n "0x0440|0x0762|0x061D|0x08FD|dispatch table|AGIDATA dispatch" docs/src tests toolsAGI_GAME_DIR=games/SQ2 python3 -B -m py_compile tools/disassemble_logic.py tests/test_disassemble_logic_tables.pyAGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_disassemble_logic_tablesAGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --statsAGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --limit 80 | rg -n "load_sound|start_sound|stop_sound|sound"AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_disassemble_logic_tables tests.test_game_census tests.test_agi_resourcesmdbook build docsgit diff --checkAGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testsfind tests tools -type d -name __pycache__ -printrm -rf tests/__pycache__ tools/__pycache__
Observations:
- KQ4D
DMDIRhas header offsets0x0008,0x00f8,0x01e5, and0x02d5. - The KQ4D sound section contains clean v3 records at sound indices
70..79. - Later present-looking sound-section triples often point into compressed data
rather than to
12 34volume record headers. - The old
tools/disassemble_logic.pyv3 path hard-coded GR table bases0x0440and0x0762, so KQ4D stats were decoded with the wrong operand table. - The first 16 action-table argc/meta pairs form an exact signature at:
- SQ2
AGIDATA.OVL:0x061d - GR
AGIDATA.OVL:0x0440 - KQ4D
AGIDATA.OVL:0x0620
- SQ2
- The 19 structured condition-table argc/meta pairs form an exact signature at:
- SQ2
AGIDATA.OVL:0x08fd - GR
AGIDATA.OVL:0x0762 - KQ4D
AGIDATA.OVL:0x0942
- SQ2
- KQ4D has the same v3 action-table shape through opcode
0xb5; the extra slots at0xb0..0xb5have the same operand metadata shape as GR. - After table detection, KQ4D stats are coherent. Decoded KQ4D scripts use
load_sound,start_sound_with_flag, andstop_sound_or_clear_sound_state, but only with sound resource numbers70..79.
Implementation:
- Added signature-based dispatch table detection to
tools/disassemble_logic.py. - Added
tests/test_disassemble_logic_tables.pycovering SQ2, GR, and KQ4D table bases. - Updated resource, bytecode, version, symbolic-label, and progress docs to treat v3 action/condition table bases as build-specific associations.
Validation:
AGI_GAME_DIR=games/SQ2 python3 -B -m py_compile tools/disassemble_logic.py tests/test_disassemble_logic_tables.py tools/game_census.py tests/test_game_census.pypassed.AGI_GAME_DIR=games/SQ2 python3 -B -m unittest tests.test_disassemble_logic_tables tests.test_game_census tests.test_agi_resourcespassed 16 tests.mdbook build docspassed.git diff --checkpassed.AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testspassed 307 tests.- Removed the generated
tests/__pycache__andtools/__pycache__directories after the full test run. AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --checkpassed.AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_game_census_dispatch_001.jsonpassed. The report records zero return codes for local unit tests, mdBook build, and the opcode-evidence freshness check.
Conclusion:
- KQ4D can now be used as a v3 logic-disassembly input. Its current scripts only reference the clean sound records, so later bad KQ4D sound-section triples remain out-of-model planning evidence until source inspection proves a valid script path can observe them.
2026-07-10: separate clean-room specification book
The project deliverable was clarified as a human-readable specification of
externally observable AGI behavior, not a replacement engine. The existing
docs/ mdBook remains the reverse-engineering evidence record, including
disassembly and DOS implementation details. A separate spec/ mdBook now
serves as the clean-room interface for an independent implementation team.
The specification starts with an explicit behavioral boundary and conformance
model. It excludes original addresses, registers, instruction sequences,
overlay organization, memory layout, and inferred source structure unless an
item itself has an externally observable effect. Evidence must first be
recorded here, then deliberately restated in spec/ as a portable behavioral
contract.
The default compatibility manifest now builds both mdBooks. This keeps the evidence record and the clean-room deliverable independently renderable as the project evolves.
Initial validation found that the installed mdBook version does not accept the
newer book.multilingual configuration key. The unsupported key was removed;
it did not affect the specification content or directory structure.
Validation:
mdbook build specpassed.AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py --report build/compatibility-suite/local_spec_split_001.jsonpassed all 307 local tests, built both mdBooks, and passed the opcode-evidence freshness check.git diff --checkpassed.
2026-07-10: first behavioral-specification promotion pass
The first substantive clean-room specification pass promoted evidence into four implementation-independent chapters:
- version profiles for the currently promoted 2.936 and 3.002.149 behavior;
- split-v2 and combined-v3 resource container formats, including dictionary and picture-nibble expansion;
- portable scalar, logic, resource, object, inventory, graphics, and cycle state; and
- sound resource parsing, countdown scheduling, completion flags, channel participation, PC-speaker divisor output, and four-channel tone/silence output.
No original addresses, symbolic machine-code labels, disassembly commands, local paths, or test-harness references were copied into the substantive chapters. A new structural test verifies mdBook summary targets and rejects evidence-only terminology in future substantive spec chapters.
The four-channel attenuation-envelope contract was intentionally not promoted as complete. The evidence records that channels 0 through 2 reset their envelope index on each event while channel 3 preserves it, but the complete portable initialization/transition contract still needs a focused source pass. The spec states this limitation rather than filling the gap by inference.
A direct scan of all 468 readable dictionary-compressed Gold Rush records found
initial 9-bit code 0x100 in every case. The valid v3 dictionary stream contract
therefore requires a reset code at the beginning, removing ambiguity around
decoder startup state.
The next specification pass added logic payload/message framing, message XOR
decoding, main-stream jumps and conditional blocks, AND/OR/NOT condition-list
semantics, all 19 valid 2.936 condition opcodes, and action opcodes
0x00..0x20. Room switching is stated as portable state transitions and
re-entry ordering rather than as cleanup routine calls. Picture load, prepare,
overlay, and show remain separate operations in the normative text because
their visibility distinction is observable.
Before validation, the nested-call return rule was checked directly against
the local instruction stream. This exposed a stale earlier statement in the
evidence chapters: ordinary opcode 0x00 returns the instruction pointer after
the terminator and does not clear the logic record’s resume field. Therefore a
normally terminated callee returns to the caller’s next action. Only an action
path that returns a zero continuation, such as room switching, propagates an
abort through call_logic. Both the evidence book and specification were
corrected before the chapter was promoted as validated.
The next action-catalog group promotes opcodes 0x21..0x64 using portable
object concepts rather than record offsets. It covers object activation and
position, view/loop/cel selection, priority and update partitions, horizon and
control gates, collision distance, four animation modes, targeted/approach/
random movement, rectangle bounds, inventory locations, and sound control.
The final action-catalog group promotes 0x65..0xaf plus v3 slots
0xb0..0xb5. It preserves two unusual byte-stream effects: 0x95 consumes an
extra byte when tracing is already active, while runtime 0xaf consumes no
operand despite table-driven scanners assigning it length one. The configured
message parameters used by 0x97/0x98 were later resolved from source as
row, column, and width overrides. A structural test now requires every accepted
v2 and v3 action opcode to appear in the specification.
Validation:
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testspassed 309 tests.mdbook build docspassed.mdbook build specpassed.AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --checkpassed.git diff --checkpassed.
2026-07-10: picture-command specification promotion
The picture evidence was restated as a portable full-EGA behavioral contract
in spec/src/picture_resources.md. The chapter separates picture loading,
prepare-time clearing, overlay decoding, and visible presentation; specifies
the guarded data-reader versus raw-operand distinction; and covers every
command from 0xf0 through 0xfa plus the 0xff terminator.
Raster semantics are explicit rather than delegated to conventional graphics primitives. The specification includes modulo-256 relative endpoints and line error accumulators, the visual-first seed-fill connectivity rule, all eight pattern row-word families and column masks, the seeded stipple transition, and the pattern plotter’s linear X-160 write into the next logical row. These are all externally distinguishable for valid command streams.
The specification deliberately excludes truncated operands and unsupported
command-boundary bytes 0xfb..0xfe as malformed-data behavior. A structural
test requires all valid picture commands and the terminator to remain present
in the chapter.
2026-07-10: view, object, input, and persistence specification promotion
The next clean-room promotion pass added four portable chapters:
spec/src/view_resources.mdfor view offsets, row runs, mutable mirrored-cel orientation, baseline placement, transparent pixels, priority scanning, and preview strings;spec/src/object_behavior.mdfor lifecycle, placement, update cadence, direction-based loop selection, cel cycling, movement, crossing collision, footprint-control acceptance, target/approach/random motion, drawing order, and refresh;spec/src/input_text_and_menus.mdfor the dictionary file, string slots, parser normalization/results/matching, event types and mappings, text surfaces, inventory selection, and menu state; andspec/src/session_and_persistence.mdfor room transitions, resource replay, selector behavior, save framing, the v3 block transform, restore/restart, and process termination.
The object pass reopened source ranges before promotion. The disposable SQ2 image was regenerated with:
python3 -B tools/decrypt_agi.py --game-dir games/SQ2 \
--output build/cleanroom/AGI.decrypted.exe
Focused instruction reads covered the collision helper at image 0x4719,
approach helper 0x0b36, random-motion helper 0x3f5a, target-motion helper
0x1672, and shared direction classifier 0x16ed. The collision helper proves
that horizontal equality counts as overlap and that a vertical collision is
current-baseline equality or strict saved/current order reversal. The action
catalog was corrected accordingly: object reset action 0x21 also enables
update/cycling state and selects the later partition; movement-rectangle
membership is strict; and actions 0x40/0x41 gate the final footprint class
state rather than latching whether one class appeared anywhere.
The motion helpers prove these portable details:
- target/near classification uses strict bands, so equality with either threshold remains directional;
- random mode uses random value modulo 9 for direction and repeatedly samples modulo 51 until its countdown is at least 6;
- approach recovery chooses a nonzero modulo-9 direction, calculates half the center/baseline Manhattan distance plus one, and samples a delay no smaller than the step; and
- retry-delay subtraction uses byte arithmetic followed by a signed nonnegative branch.
tools/agi_graphics.py now contains deterministic source-model helpers for
those transitions. tests/test_graphics_rendering.py supplies random words
explicitly and checks strict threshold edges, random countdown rejection,
initial approach sentinel handling, stuck recovery, and retry-delay return to
direct movement.
The persistence promotion deliberately stops short of claiming arbitrary binary save interchange. The 31-byte header, five little-endian length-prefixed blocks, known profile lengths, signature checks, replay language, control flow, and Gold Rush block-3 XOR key are specified. A complete portable mapping for every byte within all five blocks remains the next major serialization task.
Focused validation had three harmless false starts caused by stale guessed
module names: tests.test_agi_graphics, tests.test_logic_opcode_evidence, and
tests.test_agi_input. The actual modules are
tests.test_graphics_rendering, tests.test_logic_doc_coverage, and
tests.test_input_model. Corrected focused runs passed 68, 69, 75, 83, 90,
and finally 101 tests as the chapters accumulated.
Full validation after the complete promotion pass:
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testspassed 323 tests.mdbook build docspassed.mdbook build specpassed.AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --checkpassed.git diff --checkpassed.
2026-07-10: profile 2.936 save-block mapping
The save-state work resumed with a byte-complete map rather than a list of only recognized fields. A fresh static instruction listing was generated from the disposable decrypted executable:
ndisasm -b 16 -o 0 -e 512 build/cleanroom/AGI.decrypted.exe \
> build/cleanroom/SQ2_AGI_image.ndisasm
The first saved block starts with the seven-byte signature area and continues
through all 256 variables, 32 packed-flag bytes, timer/horizon/movement globals,
the key map, string storage, text configuration, and replay checkpoint. Block
positions in the clean specification are relative to the block start; the
evidence-side source association is the contiguous range 0x0002..0x05e2.
An absolute-data-reference scan plus focused reads of the string actions found five unresolved ranges. All 11 local SQ2 saves agree on their current contents:
| Block-1 position | Size | Observed bytes |
|---|---|---|
0x012d | 2 | 00 00 |
0x013d | 2 | 0f 00 |
0x01df | 44 | all zero |
0x03eb | 480 | all zero |
0x05d6 | 1 | zero |
The 960-byte range beginning at source address 0x020d is exactly 24 groups
of 40 bytes, but that arithmetic alone is not a slot contract. The parser action
at image 0x750b explicitly accepts only indexes below 12, and current local
logic resources use only the lower slots. The first 480 bytes are therefore
mapped as twelve script-visible string slots; the following 480 bytes remain an
opaque bank. String-copy actions can calculate beyond the lower bank without a
bounds check, but malformed/out-of-range script behavior is outside the valid
data model and is not evidence for 24 portable slots.
Block 2 has length 0x0387, exactly 21 records times the already source-mapped
43-byte object stride. Its field map covers every byte without padding. The
restore routine at code.restore.replay_resource_events first preserves each
record’s low X byte and flag word while caches/lists are reset. After resource
replay it restores X, replaces object byte +0x02 with the object index, reloads
the selected view when available, and calls the normal view-binding path. That
path preserves valid selected loop/cel indexes while rebuilding payload, loop,
and cel pointers, loop/cel counts, width, and height. Active/list state is
rebuilt from the saved flags before the exact saved flag word is restored.
Consequently the serialized pointer-shaped words are compatibility tokens, not
portable object identity, and the event byte is normalized to object index on
successful restore. All 11 local saves already contain indexes 0..20 there.
Block 4 has length 0x00c8, exactly 100 two-byte replay-pair slots. The active
pair count and checkpoint count are in block 1. Only the active prefix is replay
semantics; the remaining pairs are inactive capacity but still serialized.
The block-3 pass reopened code.inventory.initialize_metadata_and_objects at
image 0x0fa5, the carried-item list builder at 0x3203, and inventory actions
0x5c..0x61. The local 331-byte OBJECT file XOR-decodes with the already
source-derived repeating Avis Durgan key. Its decoded prefix is:
78 00 14
The loader interprets the first word as the item-table byte size and the third
byte as the maximum drawable-object index. It advances the runtime inventory
root past that header, sets the item-table end to root plus 0x78, records the
runtime/save length as file length minus 3, and allocates (0x14 + 1) object
records. This derives the observed structures rather than merely fitting them:
- 40 three-byte inventory entries occupy block-3 positions
0x0000..0x0077; - 21 drawable objects times 43 bytes produce block-2 length
0x0387; and - the remaining 208 block-3 bytes are the zero-terminated item-name pool.
Each inventory entry is name_offset:u16le, location:u8; name offsets are
relative to the runtime block. The current block has 40 entries, including 20
placeholder entries sharing the ? string and 20 distinct later names. Across
all 11 local saves, only 11 location-byte positions vary. Every name offset and
all 208 name-pool bytes match the decoded metadata file exactly. The location
value 0xff is the carried-item marker consumed by the inventory list builder.
Block 5 was resolved directly from code.logic.serialize_resume_table at image
0x1364 and code.logic.restore_resume_from_table at 0x13a5. The serializer
starts with SI = 0x0977, not the pointer stored there. It therefore emits a
four-byte pair from the static cache-shaped head before following its +0x00
next pointer through the linked logic records. Current state makes that leading
pair (logic 0, offset 0). Each linked record contributes:
logic_number:u16le = zero-extended record byte +0x02
resume_offset:u16le = word(+0x06) - word(+0x04)
After the last linked record, the serializer writes only 0xffff into the next
record’s first word. It includes all four bytes of that terminator record in the
returned block length, so the second word is ignored/stale rather than
necessarily initialized by this pass. All 11 local saves happen to contain zero
there. Their block lengths 16, 20, 24, and 28 are exact multiples of
four and their nonterminal records begin with the static (0,0) head followed
by the cached logic-0 record and other cached logics.
The restore helper scans block 5 from the beginning for the first record whose
logic number matches a newly loaded cache record. On a match it sets the resume
pointer to the newly loaded bytecode-entry pointer plus the saved offset; on
0xffff it returns without changing the default entry pointer. Only resource
replay kind 0 calls this helper. The replay-pair sequence is therefore
authoritative for which logics are loaded, while block 5 only supplies relative
resume metadata. Corpus comparison confirms the two lists need not be equal:
some block-5 cache records have no replay load, and some replayed logic numbers
have no matching block-5 record and retain offset zero.
tools/agi_save.py now contains exhaustive maps/parsers for blocks 1 through 4
and the decoded metadata header that relates blocks 2 and 3. Region validation
rejects gaps, overlaps, duplicate names, nonpositive sizes, and wrong total
lengths. Tests cover all 1505 block-1 bytes, every field in each 43-byte object
record, the fixed 21-record count, all 328 inventory/name bytes, the 100 replay
pairs, the five unresolved block-1 ranges across all local saves, and the
object-index event-byte invariant. The block-5 parser additionally validates
four-byte alignment, byte-sized logic numbers, terminal placement, first-match
lookup, duplicate entries, and ignored terminator payload. All five observed
profile 2.936 save blocks now have byte-complete maps; the next specification
task is the subsystem/version conformance matrix.
2026-07-10: clean specification conformance matrix
spec/src/conformance_matrix.md now turns the accumulated chapter boundaries
into explicit claim guidance. It classifies the shared behavior of promoted
profiles, the known 2.936/3.002.149 variants, dimensions supplied by each
selected game’s data, partially specified domains, and behavior outside the
current full-EGA target.
The matrix separates gameplay conformance from binary save interchange. The 2.936 save claim requires all five block maps, replay reconstruction, first-match logic-resume lookup, and byte preservation for opaque block-1 ranges. The 3.002.149 gameplay variants are listed, but arbitrary binary save interchange remains explicitly unavailable until its five blocks receive the same portable field mapping. A structural test requires all matrix sections and both profile claim statements to remain present.
Validation after the save mapping and matrix pass:
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s testspassed 341 tests.mdbook build docspassed.mdbook build specpassed.AGI_GAME_DIR=games/SQ2 python3 -B tools/logic_opcode_evidence.py --checkpassed.git diff --checkpassed.
2026-07-10: Gold Rush v3 save XOR key correction
The GR v3 save-map pass reopened the save helper at image 0x07bc after the
original-engine save block failed to decode to a plausible runtime block under
the previously modeled 59-byte key. The key observation is that helper 0x07bc
loads DI = 0x072c and reads [DI], so 0x072c is a data-segment address.
Reading the same numeric offset from the main executable image had accidentally
sampled code bytes.
Local evidence:
games/GR/AGIDATA.OVLcontains zero-terminated ASCIIAvis Durganat offset0x072c.- The original-engine encoded save-block prefix
c87769f82158e57363fb6f5dd6686f91457dca6606ac4011decodes with repeatingAvis Durganto8901008b011c9001049a011ca0011cb10108b80167c20167. - The decoded block matches the decoded GR
OBJECTruntime payload, confirming the block transform rather than merely proving XOR round-trip behavior.
Implementation/test correction:
GR_V3_OBJECT_INVENTORY_XOR_KEYis nowb"Avis Durgan".- The focused tests assert the exact key, the 11-byte wrap point, and the original save prefix known vector. This prevents any arbitrary repeating XOR key from passing by self-inverse round-trip behavior alone.
2026-07-10: Gold Rush v3 save blocks 2-5 structure pass
After correcting the GR v3 block-3 transform, the generated original-engine saves and decoded local metadata were rechecked for portable block dimensions.
Local reads:
games/GR/OBJECT, decoded with repeatingAvis Durgan.- Generated original-engine saves under
build/gr-v3-behavior/, includingGRSG_001.1,GRSG_restore_001.1, and suite-generated signed/blank-prefix saves.
Observed metadata and save layout:
- Decoded
OBJECTheader:- item-table byte size
0x0189; - maximum drawable object index
0x16(22); - runtime inventory payload length
0x0713.
- item-table byte size
- All checked generated GR saves have block lengths
1028,989,1811,100, and12. - Block 2 length
989is exactly23 * 43, matching object indexes0..22and the already source-mapped object-record stride. - Block 3 decodes with
gr_v3_object_inventory_save_xor()to the decodedOBJECTruntime payload byte-for-byte. The decoded block contains 131 three-byte inventory entries followed by a 1418-byte zero-terminated name pool. - Block 4 length
100is exactly 50 replay pairs. - The observed block 5 payload is
00 00 00 00 00 00 00 00 ff ff 00 00, matching the same four-byte logic-resume grammar used by the 2.936 profile: leading head record, cached logic-0 record, and terminator.
Implementation/test updates:
tools/agi_save.pynow exposes generic object-record, replay-pair, inventory-block, and decoded-object-metadata helpers, plus GR-specific constants/wrappers for the observed profile 3.002.149 dimensions.tests/test_save_resources.pynow checks the GROBJECTmetadata dimensions whengames/GRis present and checks the generated signed save dimensions whenbuild/gr-v3-behavior/GRSG_001.1is present.
Conclusion:
- Blocks 2 through 5 of the observed Gold Rush v3 save state now have portable structure maps. Block 1 remains the outstanding byte-complete mapping task for profile 3.002.149 binary save interchange.
2026-07-10: Gold Rush v3 save block 1 structure pass
Goal: map the first observed profile 3.002.149 save block with the same byte-complete discipline used for profile 2.936.
Source observations:
- GR save action
0x7dat image0x29e5writes the first state block at image0x2aba..0x2ac9. - The writer helper call receives:
- file handle from
[bp-0xcc]; - start address
0x0002; - length
0x0406 - 0x0002 = 0x0404.
- file handle from
- Therefore block 1 is exactly
DS:0x0002..0x0405, serialized as 1028 bytes. - The same action uses
[0x0141] * 2for block 4 length; observed saves have[0x0141] = 0x0032, matching 50 replay pairs.
Mapped block-1 partition:
| Position | Size | Meaning |
|---|---|---|
0x0000 | 7 | Signature prefix. |
0x0007 | 256 | Variables. |
0x0107 | 32 | Flags. |
0x0127 | 4 | Timer ticks. |
0x012b | 2 | Horizon. |
0x012d | 2 | Opaque word. |
0x012f | 12 | Movement rectangle/coupling/prepared-picture fields. |
0x013b | 2 | Movement rectangle enabled word. |
0x013d | 2 | Opaque word, observed 0f 00. |
0x013f | 2 | Replay-pair capacity, observed 0x0032. |
0x0141 | 2 | Active replay-pair count. |
0x0143 | 196 | Forty-nine four-byte key-map entries. |
0x0207 | 4 | Opaque gap before string slots. |
0x020b | 480 | Twelve 40-byte string slots rooted at DS:0x020d. |
0x03eb | 22 | Text/status/display/replay-checkpoint words and prompt byte. |
0x0401 | 2 | Menu interaction gate word at DS:0x0403. |
0x0403 | 1 | Key-release enqueue gate byte at DS:0x0405. |
The detailed clean spec table expands the grouped ranges above into individual
fields. The region map covers every byte exactly once and keeps four opaque
ranges explicit: 0x012d..0x012e, 0x013d..0x013e, 0x0207..0x020a, and
0x03f6.
Implementation/test updates:
tools/agi_save.pynow definesGR_V3_BLOCK1_REGIONSandsplit_gr_v3_block1(...).tests/test_save_resources.pyvalidates full byte coverage, the expanded 49-slot key map, twelve string slots, observedGR\0signature prefix, replay capacity0x0032, row defaults, menu gate, key-release gate, and explicit opaque ranges.
Conclusion:
- The observed Gold Rush profile 3.002.149 save state now has a byte-complete structural map for all five blocks. The remaining save-related work is to resolve whether the explicit opaque ranges change during valid execution and to repeat this process for other interpreter/game profiles when promoted.
2026-07-10: configured modal-message parameters
Goal: resolve the action 0x97/0x98 parameter meanings from source before
leaving text-window behavior as a partial clean-spec gap.
Source observations:
- SQ2 action
0x97at image0x1c54reads an immediate message number and calls shared helper0x1c96. - SQ2 action
0x98at image0x1c71reads a variable number, fetchesvar[arg0]as the message number, and calls the same helper. - Helper
0x1c96reads three following bytes into:[0x0d0b];[0x0d0d];[0x0d09].
- If the third byte is zero, helper
0x1c96stores0x001ein[0x0d09]before display. - After the display path returns, helper
0x1c96resets all three globals to0xffff. code.text.display_message_windowat image0x1d96consumes those globals:[0x0d09], when not0xffff, overrides the maximum formatted text width;[0x0d0b], when not0xffff, overrides the computed message-window row;[0x0d0d], when not0xffff, overrides the computed message-window column.
- Without a row override, the helper centers vertically from the formatted line
count and the current display text area; it then adds the display base row
[0x05dd]. - Without a column override, the helper centers horizontally with
(40 - formatted_line_width) / 2.
Conclusion:
- The three configured-message operands are now resolved as one-shot
row/column/width parameters. The remaining text presentation limitation is
exact glyph bitmap selection for a target platform, not the meaning of
actions
0x97and0x98.
2026-07-10: four-channel sound envelope spec promotion
Goal: close the stale clean-spec gap for four-channel sound attenuation semantics by rechecking the source-backed sound model and promoting only the portable command-output contract.
Commands run from /Users/peter/ai/agi/reverse:
sed -n '1,260p' PROGRESS.mdsed -n '1,220p' docs/src/current_status.mdsed -n '1,260p' docs/src/sound_and_audio.mdsed -n '1,260p' spec/src/sound.mdsed -n '1,380p' tools/agi_sound.pysed -n '1,360p' tests/test_sound_resources.pysed -n '7192,7238p' docs/src/clean_room_executable_notes.mdsed -n '7794,7824p' docs/src/clean_room_executable_notes.mdndisasm -b 16 -o 0x7f96 build/cleanroom/AGI.decrypted.exe -e 0x8196 | sed -n '1,90p'ndisasm -b 16 -o 0x801c build/cleanroom/AGI.decrypted.exe -e 0x821c | sed -n '1,170p'ndisasm -b 16 -o 0x8162 build/cleanroom/AGI.decrypted.exe -e 0x8362 | sed -n '1,170p'xxd -g 1 -s 0x17b8 -l 96 games/SQ2/AGIDATA.OVL
Source observations rechecked:
code.sound.driver_startcopies four channel stream pointers, initializes all four channel countdowns to1, stores the default envelope table pointer for each channel, disables each channel’s envelope index with0xffff, and marks each channel active.- The playback tick decrements each active channel countdown. While the countdown is still nonzero, it calls the attenuation helper without consuming a new sound event.
- When a countdown reaches zero, channels 0, 1, and 2 reset their envelope
index to zero before reading the next event. Channel 3 (
BX == 6) does not take that reset branch, so it preserves its current envelope index across event boundaries. - A channel terminator decrements the remaining-active-channel count, clears
the channel active word, sets the channel base attenuation to
0x0f, and calls the attenuation helper. - The attenuation helper skips envelope and device-specific adjustment when the
base attenuation is
0x0f. - Otherwise, if the envelope index is enabled, the helper reads one byte from
the channel envelope table. Byte
0x80disables the envelope and copies the previous envelope value back into the base attenuation. Other bytes are added to the event’s base attenuation as signed 8-bit deltas; negative underflow clamps to zero and positive overflow clamps to0x0f. - The helper stores each clamped envelope result as the previous envelope
value, adds runtime global attenuation adjustment byte
0x0020, clamps to0x0f, applies the selector-2 non-silent below-8 adjustment, combines the result with the channel mask, and writes the resulting command byte. - The default table bytes are:
fe fd fe ff 00 00 01 01 01 01 02 02 02 02 02 02 02 02 03 03 03 03 03 03 03 04 04 04 04 05 05 05 05 06 06 06 06 06 07 07 07 07 08 08 08 08 09 09 09 09 0a 0a 0a 0a 0b 0b 0b 0b 0b 0b 0c 0c 0c 0c 0c 0c 0d 80. - Existing helper/test coverage in
tools/agi_sound.pyandtests/test_sound_resources.pyalready models the table bytes, channel masks, delta-from-base behavior, clamps, sentinel handling, selector-2 adjustment, stop-silence bytes, PC-speaker divisor, and channel scheduling.
Documentation/spec updates:
spec/src/sound.mdnow defines the four-channel attenuation-envelope state fields, start/event initialization, channel-3 persistence, default envelope table, delta/clamp/sentinel behavior, global adjustment, selector-2 adjustment, and silent-base bypass.spec/src/conformance_matrix.mdno longer lists four-channel sound as a partial domain. Exact analog waveform synthesis remains outside the target; compatibility is defined at the resource, timing, tone/divisor, attenuation command, silence, active-state, and completion-flag boundary.PROGRESS.mdmarks sound resources and playback as covered for the current spec target and removes the sound envelope from the highest-value remaining work.tests/test_spec_book.pynow guards the clean sound chapter’s timing and envelope coverage.
2026-07-10: script-visible resource reference audit
Goal: follow up on the multi-game census record errors without treating unreadable directory-looking entries as valid AGI behavior unless decoded scripts can observe them.
Commands run from /Users/peter/ai/agi/reverse:
python3 -B tools/game_census.py --game-dir games/KQ1 --game-dir games/KQ2 --game-dir games/KQ3 --game-dir games/KQ4D --game-dir games/LSL1 --game-dir games/PQ1 --game-dir games/SQ2 --game-dir games/GRAGI_GAME_DIR=games/KQ1 python3 -B tools/disassemble_logic.py --statsAGI_GAME_DIR=games/KQ1 python3 -B tools/disassemble_logic.py --limit 200 | rg -n "load_sound|start_sound|stop_sound|sound|0x62|0x63|0x64"- Python local print of KQ1
SNDDIRentries30..39. xxd -g1 -s $((34*3)) -l 24 games/KQ1/SNDDIRAGI_GAME_DIR=games/KQ1 python3 -B tools/disassemble_logic.py --limit 100000 | python3 -c ...- Python local readable/unreadable scan for KQ1 and KQ4D sound resources.
AGI_GAME_DIR=games/KQ4D python3 -B tools/disassemble_logic.py --limit 100000 | python3 -c ...python3 -B -m py_compile tools/resource_reference_audit.py tests/test_resource_reference_audit.pypython3 -B -m unittest tests.test_resource_reference_auditpython3 -B tools/resource_reference_audit.py --game-dir games/KQ1 --game-dir games/KQ4D --output build/cross-version/resource_reference_audit_kq1_kq4d.json
Observations:
- KQ1
Version 2.917has four sound-directory entries that the generic v2 directory parser treats as present-like: sounds34,35,36, and37. Their directory bytes are immediately followed byff ff ffabsent entries, and the pointed-to data does not have a valid v2 volume header. - Decoded KQ1 logic uses immediate sound numbers
0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21. None of the unreadable sound entries34..37are immediately referenced by decoded scripts. - KQ4D
Version 3.002.102has clean readable sound records including70..79, plus a record-readable but malformed-family entry at198and many unreadable high sound-section entries. The earlier census also exposed entry435; later byte-domain analysis corrected that entry as unaddressable rather than as a resource. - Decoded KQ4D logic uses immediate sound numbers
70..79only. None of the unreadable high sound entries are immediately referenced by decoded scripts.
Implementation:
- Added
tools/resource_reference_audit.py. The tool scans decoded logic bytecode using the observed action/condition table signatures, collects immediate resource references for selected resource-loading/display actions, and compares those references with readable and unreadable directory entries. Variable-derived resource numbers are intentionally not counted as fixed immediate references. - Added
tests/test_resource_reference_audit.py, which builds a synthetic split-directory fixture with one readable sound and one unreadable but script-referenced sound.
Validation:
python3 -B -m py_compile tools/resource_reference_audit.py tests/test_resource_reference_audit.pypassed.python3 -B -m unittest tests.test_resource_reference_auditpassed.
Conclusion:
- KQ1’s bad sound entries and KQ4D’s suspect high sound-section entries remain planning evidence, not promoted valid-resource behavior. If a future script reaches such numbers through variables, or a future interpreter version has a source-backed path for them, inspect that local case directly before adding a clean-spec rule.
2026-07-10: text glyph conformance boundary
Goal: resolve the remaining policy question in PROGRESS.md about whether
exact platform glyph bitmaps belong in the current target profile.
Inputs reviewed:
spec/src/input_text_and_menus.mdspec/src/scope_and_conformance.mdspec/src/conformance_matrix.mdPROGRESS.md- Existing evidence notes and compatibility notes for prompt/status/input rows, text rectangles, configured modal-message geometry, and glyph-bearing QEMU probes.
Decision:
- The current clean-room core specifies text at the character-cell boundary: text bytes, attributes, rows, columns, rectangles, modal blocking, prompt and status behavior, and save/restore of covered regions.
- Exact 8-by-8 glyph bitmap shapes are a font-profile input, not an interpreter semantic for the portable full-EGA core. A bitmap-identical screenshot claim for text output must declare a font set in addition to the interpreter profile.
- This decision matches the existing test strategy: QEMU probes can use glyph pixels as evidence that text was drawn, but the clean spec should not require a particular BIOS or platform font unless a future conformance tier deliberately adds that font profile.
Documentation updates:
spec/src/scope_and_conformance.mdnow states the text cell/font boundary.spec/src/input_text_and_menus.mdnow has aFont boundarysection instead of treating glyphs as a remaining presentation gap.spec/src/conformance_matrix.mdremoves glyph output from partial domains and lists exact glyph bitmaps outside the current target unless a font profile is supplied.PROGRESS.mdmarks the text/parser/menu/inventory chapter as covered for the current portable target.
2026-07-10: save reserved-state classification
Goal: resolve whether the remaining profile 2.936 and 3.002.149 block-1 ranges represent valid-execution state or reserved serialization capacity.
Source-first checks:
- Rechecked the complete SQ2 and GR instruction listings for direct references to the two standalone saved words, the post-key-map tail, the string-bank tail, and the prompt/status gap.
- SQ2 action
0x79roots its key map atDS:0x0145and stops after0x27four-byte records. The serialized bytes immediately following those 39 records contain ten additional zeroed four-byte records plus four zero bytes. - GR keeps the same key-map root but raises the source loop bound to
0x31. Its 49 records consume exactly the ten-record SQ2 tail, while the same final four zero bytes remain before string storage. - Both builds root valid 40-byte string slots at
DS:0x020d. Source validation and local valid logic use only indexes0..11. SQ2 serializes another twelve zeroed 40-byte records after those slots; GR removes that exact 480-byte bank and relocates the following text-state words toDS:0x03ed. - The standalone saved words at SQ2
DS:0x012fandDS:0x013fhave no direct reference in the complete instruction listing. All 11 local SQ2 saves contain canonical bytes0000and0f00; the observed GR save has the same values at the same serialized positions. - The one-byte prompt/status gap is zero in all local SQ2 saves and the observed GR save. In both layouts it separates byte state from following word state.
Interpretation:
- The ranges are reserved serialization state, not unresolved game-visible fields. The post-key-map and post-string ranges are profile capacity/legacy storage demonstrated by their exact cross-version consumption or removal; the remaining words and bytes are reserved values or alignment storage.
- A newly initialized save uses the observed canonical values. Restoring and re-saving an existing save preserves its supplied reserved bytes. Valid game operations do not assign behavior to those bytes.
Implementation and specification updates:
tools/agi_save.pyreplaces theunknown_*block-1 regions with explicit reserved words, inactive key-map tail, pre-string padding, reserved string bank, and text padding.tests/test_save_resources.pychecks the exact reserved partition and canonical values across all 11 local SQ2 saves and the generated GR save.spec/src/session_and_persistence.mddefines canonical initialization and byte-preserving interchange for reserved state.spec/src/conformance_matrix.mdnow permits a binary save-interchange claim for the mapped Gold Rush/profile dimensions as well as profile 2.936.docs/src/versions.md,docs/src/symbolic_labels.md,PROGRESS.md, and the current handoff note record the cross-version classification.
2026-07-10: KQ4D 3.002.102 bounded profile
Goal: apply the source-first cross-version workflow to KQ4D / AGI 3.002.102 and promote only differences supported by the local executable and valid data.
Repeatable tooling:
- Added
tools/compare_interpreter_tables.py. It accepts two explicit game directories and executables, detects each action/condition table from the established argc/meta signatures, compares parser contracts, and triages normalized handler-entry differences. Its report warns that a differing snippet is not itself a behavioral conclusion. - Added
tests/test_compare_interpreter_tables.pyfor contract-versus-handler classification and local v3 table detection. - Generated:
build/cross-version/kq4d_gr_table_comparison.mdbuild/cross-version/kq4d_sq2_table_comparison.md
Table-level results:
- KQ4D and GR expose 182 actions
0x00..0xb5and 19 structured conditions0x00..0x12with zero parser-contract differences. - All 19 condition entry snippets normalize identically.
- KQ4D/GR shared action differences are exactly:
0x12,0x6f,0x73,0x76,0x77,0x78,0x79,0x89,0x8a,0xa3,0xa4, and0xa9. - KQ4D/SQ2 shared action differences are exactly
0x7c,0x7d,0x80,0x84, and0xad; all 19 condition entries again normalize identically.
Focused source conclusions:
- KQ4D action
0x12at image0x1a1bpasses its immediate byte directly to room switching. It does not call GR’s0x7e..0x80 -> 0x49remap helper. - KQ4D input actions retain SQ2’s display-mode/input-width branches. Action
0xa3at0x3cecsets word[0x0d58],0xa4at0x3cfeclears it, and close-window action0xa9at0x21faclears both[0x0d58]and active window word[0x0d66]. - KQ4D key mapping at
0x5081compares its slot index with0x27, preserving the 39-entry capacity. GR compares with0x31. - KQ4D action
0xadat0x6477sets byte[0x05e5]to one and v3 action0xb5at0x647fclears it. Action0xb1at0x9838writes menu gate word[0x05e3]; menu interaction at0x9851returns while it is zero. - The six v3-only slots have the same contracts and normalized handler bodies as GR. KQ4D also shares GR’s inventory temporary-state, restart marker, motion-mode-4 preservation, and save-XOR action variants.
Save-map source pass:
- Save action
0x7dis at image0x2a46. Its first-block write uses start0x0002and length0x05e6 - 0x0002 = 0x05e4. - The first
0x05e1positions match the complete profile 2.936 block-1 map. KQ4D appends menu gate word at block position0x05e1and release-gate byte at position0x05e3. - The valid intended decoded eight-byte inventory metadata is
03 00 0f 03 00 00 3f 00: item-table size 3, maximum object index 15, one runtime entry, and name pool?\0. - Therefore block 2 is
16 * 0x2b = 0x02b0bytes and decoded block 3 is five bytes. KQ4D logic 0 calls action0x8e(1), establishing one replay-pair slot and a two-byte block 4 for the selected demo data. Block 5 retains the common variable resume-record grammar. - The KQ4D save/restore paths and startup reader call XOR helper
0x07c3, whose body matches GR0x07bcand repeats theAvis Durgankey at data0x090c.
Local OBJECT packaging check:
- The selected local
games/KQ4D/OBJECTalready contains the intended decoded eight bytes, although the source reader applies the XOR transform to the on-disk file. This is inconsistent with the valid source contract. - Added
tools/v3_object_encoding_probe.pyandtests/test_v3_object_encoding_probe.py. The tool makes unchanged and XOR-encoded writable copies underbuild/; it never modifiesgames/. - The first QEMU attempt failed before launch because the sandbox denied the local VNC bind. The elevated retry exposed a fixture bug: copied read-only directory modes prevented replacement. The tool now makes copied directories and files writable, matching the other fixture builders.
- The initial successful captures were only the Sierra loader splash. A second
run used
--advance-loaderand captured after Enter. Both variants reached the same KQ4D intro frame. That does not establish dual valid encodings: the intro had not exercised inventory metadata. The clean profile follows the source-defined XOR-decoded valid representation and treats the unchanged local packaging as an evidence anomaly. - QEMU report:
build/kq4d-profile/object_encoding_after_loader_qemu_001.json.
Implementation/specification updates:
tools/agi_save.pynow defines KQ4D block-1, object-record, inventory, and replay dimensions plus split/decode helpers.tests/test_save_resources.pychecks the0x05e4block-1 partition, 16 object records, one?inventory entry, transformed five-byte block 3, and one replay pair.spec/src/version_profiles.mddefines a bounded 3.002.102 resource/logic/input/persistence profile.spec/src/session_and_persistence.mdadds the KQ4D demo block dimensions and makes the block-3 XOR rule common to the promoted v3 profiles.spec/src/conformance_matrix.mdexplicitly withholds a full 3.002.102 gameplay claim until picture/view and object-update roles are compared.
2026-07-10: KQ4D 3.002.102 renderer/object source comparison
Goal: close the renderer/object blocker without inferring behavior from version numbers. Candidate helpers were paired by symbolic role, normalized snippets were used only for triage, and every reported mismatch was read in direct 16-bit disassembly.
The generic comparator gained repeatable explicit role pairs:
python3 -B tools/compare_interpreter_tables.py \
--left-label KQ4D-3.002.102 --left-game-dir games/KQ4D \
--left-exe games/KQ4D/AGI \
--right-label GR-3.002.149 --right-game-dir games/GR \
--right-exe games/GR/AGI \
--role-pair LABEL=KQ4D_ADDRESS,GR_ADDRESS \
--output build/cross-version/kq4d_gr_subsystem_roles.md
Rizin was also tried as a function-matching aid. Raw 16-bit analysis retained
MZ-header-relative offsets, while rz-diff -t functions rejected the required
16-bit architecture configuration. It was therefore not used as evidence.
The completed pass covers 52 roles:
- six view roles: resource load/discard, object view binding, loop-table and loop selection, and cel selection;
- four picture lifecycle roles;
- picture decode completion, command scanner, all channel/pattern/path/fill command handlers, coordinate reading, and line drawing;
- buffer fill, horizontal/vertical line drawing, pixel writes, seed fill, and full refresh;
- update-list construction, insertion, draw/refresh order, active/inactive partitions, list rebuild/flush, root membership, collision, control acceptance, dirty rectangles, and placement; and
- frame timing/mode advance plus the object motion, pre-mode, mode-dispatch, and rectangle-boundary passes.
Exact KQ4D entries added to the symbolic map include:
code.object.update_dirty_rect 0x5b8c
code.object.place 0x5d64
code.motion.update_objects 0x1767
code.motion.pre_mode_and_boundary_update 0x065b
code.motion.dispatch_mode_step 0x0691
code.motion.rectangle_boundary_check 0x06f2
The first four above normalize exactly against GR 0x598f, 0x5cb3,
0x1720, and 0x0654. The mode dispatcher and boundary checker report a
normalized difference only inside embedded jump-table bytes. Manual
disassembly confirms that their selected branch bodies and post-table state
changes match GR 0x068a and 0x06eb. The same embedded-table issue explains
the earlier frame-mode report for KQ4D 0x4cf7 versus GR 0x4b0e.
The only renderer differences in the 52-role pass are KQ4D’s retained
display-mode-2 branches in picture decode completion (0x6891), buffer fill
(0x5673), and full refresh (0x5962). The underlying full-EGA command and
raster helpers match. Non-EGA presentation is outside the current conformance
target.
Conclusion: for valid expanded resources and the primary full-EGA target, KQ4D 3.002.102 shares the promoted picture, view, object, motion, animation, and refresh contracts. Combined with the previously mapped resource, logic, input, room, sound, and persistence behavior, this is sufficient to promote a 3.002.102 full-EGA gameplay profile. Binary save interchange remains scoped to the mapped KQ4D demo dimensions.
2026-07-10: KQ1 2.917 source-first profile
Goal: apply the same comparison workflow to an earlier v2 interpreter without assuming that its shared file layout implies shared behavior.
The first table report incorrectly used games/KQ1/AGI directly. Handler
addresses aligned with SQ2, but every instruction decoded as nonsense because
the file still had the loader transform applied. It was discarded. KQ1 was
decoded with its own local loader key:
python3 -B tools/decrypt_agi.py --game-dir games/KQ1 \
--output build/cross-version/kq1_agi.decrypted.exe
python3 -B tools/compare_interpreter_tables.py \
--left-label KQ1-2.917 --left-game-dir games/KQ1 \
--left-exe build/cross-version/kq1_agi.decrypted.exe \
--right-label SQ2-2.936 --right-game-dir games/SQ2 \
--right-exe build/cleanroom/SQ2_AGI.decrypted.exe \
--output build/cross-version/kq1_sq2_table_comparison.md
The decoded report finds zero parser-contract or normalized-handler
differences across 174 shared actions and 19 conditions. Direct disassembly of
code.logic.action_dispatch at image 0x02c4 shows KQ1 accepts action bytes
through 0xad; SQ2 accepts through 0xaf.
The v2 action count is source-independent table geometry: a fixed 0x20-byte
trailer lies between the action and condition tables. Therefore
(condition_base - action_base - 0x20) / 4 yields 170 actions for local
2.411/2.440, 174 for 2.917, and 176 for 2.936. The detector and tests now use
this rule rather than assuming every split-layout build has 176 actions.
Fifty-one of the 52 picture/view/object/motion roles normalize exactly after
accounting for the later linked-code displacement. The remaining role,
code.object.frame_timer_update at image 0x0563, differs in one branch:
2.917: cmp byte [object+0x0b],4 ; jnz skip_four_loop_table
2.936: cmp byte [object+0x0b],4 ; jc skip_four_loop_table
Thus 2.917 automatically direction-selects the four-loop table only for exactly four loops, while 2.936 accepts four or more. The two/three-loop paths, cel timer, movement, collision, placement, raster, update-list, and refresh roles match.
Save source and selected-game dimensions:
- writer image
0x2753and restore image0x2512use the common five-block grammar and a0x05e1first block; - decoded KQ1
OBJECTmetadata has item-table size0x51, maximum object index 17, 18 object records, 27 inventory entries, and a0x0148runtime inventory block; - logic 0 executes action
0x8e(100), defining 100 replay pairs; and - KQ1 block lengths are
0x05e1,0x0306,0x0148,0x00c8, and a variable common logic-resume block.
The promoted 2.917 profile is limited to valid resource records. Four KQ1 sound directory entries fail the generic header check, and the immediate resource-reference audit finds no script reference to them.
2026-07-10: full KQ4 / AGI 3.002.086 source-first profile
Goal: inspect the newly supplied full KQ4 independently of the existing KQ4D demo, determine its actual interpreter profile, and promote only source-backed externally observable differences.
Initial inventory and resource checks:
python3 -B tools/game_census.py \
--game-dir games/KQ4 --game-dir games/KQ4D
python3 -B tools/resource_reference_audit.py \
--game-dir games/KQ4 \
--output build/cross-version/resource_reference_audit_kq4.json
The full game embeds Version 3.002.086; KQ4D embeds Version 3.002.102.
KQ4 uses combined KQ4DIR and KQ4VOL.N files. The selected copy contains
177 present logic entries, 148 readable pictures, 243 readable views, and 96
readable sounds. Three logic records and one sound record are direct; the
remaining present readable records use v3 transforms.
Pictures 150 and 151 point into absent KQ4VOL.6; views 198 and 199 point into
absent KQ4VOL.7. No decoded immediate reference selects these entries. That
does not establish picture unreachability because KQ4 selects pictures through
variables. The missing-volume records are excluded from the valid-data profile.
The first generic table report assigned every v3 build 182 actions and treated
KQ4 trailer bytes as opcodes 0xb2..0xb5. Source geometry corrected this. A
fixed 0x4a-byte data trailer separates the v3 action and condition tables, so
action_count = (condition_base - action_base - 0x4a) / 4
KQ4’s bases 0x061d and 0x092f yield 0xb2 entries, actions
0x00..0xb1. KQ4D and GR yield 0xb6. Direct dispatcher disassembly at KQ4
image 0x02c4 independently confirms maximum action 0xb1.
The corrected comparisons were generated as:
python3 -B tools/compare_interpreter_tables.py \
--left-label KQ4-3.002.086 --left-game-dir games/KQ4 \
--left-exe games/KQ4/AGI \
--right-label KQ4D-3.002.102 --right-game-dir games/KQ4D \
--right-exe games/KQ4D/AGI \
--output build/cross-version/kq4_full_kq4d_demo_table_comparison.md
python3 -B tools/compare_interpreter_tables.py \
--left-label KQ4-3.002.086 --left-game-dir games/KQ4 \
--left-exe games/KQ4/AGI \
--right-label SQ2-2.936 --right-game-dir games/SQ2 \
--right-exe build/cleanroom/SQ2_AGI.decrypted.exe \
--output build/cross-version/kq4_full_sq2_table_comparison.md
All 178 shared KQ4/KQ4D action operand contracts and all 19 condition
contracts match. All condition handlers normalize identically. Shared action
differences are limited to 0xad and 0xb0:
- KQ4 action
0xad, image0x6468, increments byte[0x15a0]; KQ4D sets its relocated gate to one. - KQ4 action
0xb0consumes one ignored byte and reaches the no-op handler at0x5467; KQ4D’s slot consumes no operands. - KQ4 action
0xb1, image0x9812, consumes one byte and stores the menu interaction gate word[0x1d9e].
Every KQ4/SQ2 parser contract also matches over the 176 shared actions. Shared
handler differences are 0x7c, 0x7d, 0x80, and 0x84: KQ4 already has
the v3 inventory-selector temporary state, block-3 save XOR, restart
prompt-marker behavior, and motion-mode-4 preservation. KQ4 retains SQ2’s
direct room destination, display/input-width branches, 39-entry key map, and
increment-style release gate.
A normalized 52-role KQ4/KQ4D source comparison covered view loading and cel selection, picture lifecycle, every picture command and raster helper, update lists, composition, collision, control acceptance, dirty rectangles, placement, motion, and animation. Primary full-EGA roles match after relocation. Embedded jump-table bytes account for normalized reports in frame mode and motion dispatch; the selected branch bodies match.
Two genuine observable object differences remain:
- At
code.object.frame_timer_update, image0x0563, KQ4 applies the four-direction loop table when loop count is four or greater. KQ4D uses the later exactly-four-unconditional/more-than-four-requires-f20rule. - At
code.motion.update_objects, image0x1751, KQ4 uses a strict-positive branch for proposed X. Exact zero therefore enters the clamp and left-boundary-report path. KQ4D uses a nonnegative branch and accepts exact zero without reporting. Both clamp negative values and report code 4.
KQ4’s motion dispatcher at 0x067a already accepts modes 1 through 4. Its
remaining movement, collision, placement, picture, view, and refresh branch
bodies match KQ4D for the full-EGA target.
Save and metadata source observations:
- writer image
0x2a30and restore image0x27dduse the five-block grammar; - first block write length is
0x05e3 - 2 = 0x05e1, matching 2.936; - menu gate
[0x1d9e]and release gate[0x15a0]lie outside block 1; - XOR helper image
0x07adrepeats key data at0x08f9,Avis Durgan; - decoded
OBJECTmetadata has item table size0x87, 45 items, maximum drawable object index 25, 26 records, runtime block length0x02c6, and a 575-byte name pool; and - logic 0 executes action
0x8e(250), defining 250 replay pairs.
The selected full KQ4 block lengths are therefore 0x05e1, 0x045e,
0x02c6, 0x01f4, and a variable common logic-resume block. Block 3 is
XOR-transformed on disk. These are full-game data dimensions, not universal
3.002.086 constants.
Conclusion: full KQ4 3.002.086 is a separate promoted profile between the current 2.936 and 3.002.102 evidence points. The analysis required no dynamic experiment because the dispatch boundary, handler deltas, object branches, and save dimensions were directly resolved from local disassembly and decoded valid game data.
Validation:
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest \
tests.test_save_resources \
tests.test_disassemble_logic_tables \
tests.test_compare_interpreter_tables \
tests.test_spec_book
AGI_GAME_DIR=games/SQ2 python3 -B -m unittest discover -s tests
mdbook build docs
mdbook build spec
AGI_GAME_DIR=games/SQ2 python3 -B tools/compatibility_suite.py \
--report build/compatibility-suite/kq4_3002086_local.json
The focused run passed 58 tests. The full and aggregate runs each passed 362
tests. Both books built, and the aggregate opcode-evidence freshness check
passed. An initial aggregate invocation without AGI_GAME_DIR was rejected by
the intentional no-default-game guard; rerunning with the explicit SQ2 input
produced the successful report above.
2026-07-10: KQ2 2.411 and LSL1 2.440 source-first profiles
Goal: apply the same bounded cross-version workflow to the two earliest local interpreters and promote only disassembly-backed observable differences.
Input and executable preparation:
python3 -B tools/game_census.py \
--game-dir games/KQ2 --game-dir games/LSL1
python3 -B tools/decrypt_agi.py --game-dir games/KQ2 \
--output build/cross-version/kq2_agi.decrypted.exe
file games/KQ2/AGI games/KQ2/SIERRA.COM games/LSL1/LL.COM
KQ2 is Version 2.411 and uses the loader-managed AGI transform. LSL1 is
Version 2.440; LL.COM is already a full 38 KiB MZ executable. Both games
use split direct v2 resource records, and the census reports no record errors.
Static table reports:
python3 -B tools/compare_interpreter_tables.py \
--left-label KQ2-2.411 --left-game-dir games/KQ2 \
--left-exe build/cross-version/kq2_agi.decrypted.exe \
--right-label LSL1-2.440 --right-game-dir games/LSL1 \
--right-exe games/LSL1/LL.COM \
--output build/cross-version/kq2_lsl1_table_comparison.md
python3 -B tools/compare_interpreter_tables.py \
--left-label KQ2-2.411 --left-game-dir games/KQ2 \
--left-exe build/cross-version/kq2_agi.decrypted.exe \
--right-label KQ1-2.917 --right-game-dir games/KQ1 \
--right-exe build/cross-version/kq1_agi.decrypted.exe \
--output build/cross-version/kq2_kq1_table_comparison.md
Both early action tables start at data 0x061b; condition tables start at
0x08e3. The v2 geometry rule yields 170 actions. Dispatcher image 0x0291
compares with 0xa9, independently confirming valid range 0x00..0xa9.
All 170 KQ2/LSL1 table records and all 19 condition handlers match. Restart is
the only normalized action-handler difference: KQ2 0x241f always prompts,
while LSL1 0x2435 checks flag 16 first.
Against 2.917, shared handler triage is limited to screen shake, save/restore,
restart, heap diagnostics, and configured-message table records. The screen
shake difference is an alternate display-mode-4 branch outside the target.
The early heap format at data 0x09ff is:
heapsize: %u
now: %u max: %u
max script: %d
The later rm.0, etc. line is absent.
Configured-message correction: early table records for actions 0x97 and
0x98 contain argc 3, but the wrappers consume the message selector and then
helpers KQ2 0x1c43 / LSL1 0x1c59 consume row, column, and width. Valid
execution therefore advances four bytes. The original linear stats pass used
three and drifted into false opcodes. action_operand_count(...) now applies
the source-observed width, while stats preserve table_argc=3 as evidence.
Renderer/object report:
build/cross-version/kq2_lsl1_subsystem_comparison.md
The 55 role pairs cover resource loading, view binding/selection, picture
lifecycle and every command/raster helper, update lists, collision, control
acceptance, placement, animation, and motion. Forty-nine normalize outright.
The frame-mode report differs only in embedded jump-table bytes; selected
branches match. Four 0xf0..0xf3 reports differ because KQ2 updates the low
draw-state byte and LSL1 updates the containing word. The decoder initializes
the word to zero and all raster reads use only the low byte, so the upper-byte
difference is not observable in valid full-EGA output.
The two observable picture differences are source direct:
- KQ2
0xf9image0x61f5executeslodsb; ret, consuming and ignoring the mode byte. - KQ2
0xfaimage0x625eloops over coordinate pairs and calls ordinary pixel writer0x50fbonce per pair. - LSL1’s complete pattern block
0x6294..0x6397normalizes instruction for instruction with KQ1’s0x6438..0x653b, including mode, radius, shape, and stipple handling.
A version-aware local picture model now selects KQ2’s early behavior with
PictureRenderer(..., pattern_brushes=False). A focused test confirms ignored
mode and two single-pixel pair plots. A command-aware resource scan found no
0xf9/0xfa in selected KQ2 pictures. Selected LSL1 pictures 2, 20, 29, 30,
and 35 use 0xfa but never 0xf9, leaving the default radius zero.
Both early frame-timer helpers at 0x0530 select directional four-loop mapping
only when loop count equals four. Motion dispatch 0x0647 accepts modes 1..3.
Collision, placement, control scan, update lists, movement, and boundary bodies
match after relocation.
Sound source map:
| Role | KQ2 | LSL1 |
|---|---|---|
| driver start | 0x7bc9 | 0x7cec |
| tick | 0x7c2b | 0x7d4e |
| stop core | 0x7cca | 0x7ded |
| event/tone output | 0x7ce9 | 0x7e0c |
| timer hook | 0x809a | 0x81c3 |
The early start path initializes four stream pointers, four countdowns to one,
and four active words. It has none of the later envelope pointer/index/value
arrays. The tick decrements active countdowns and emits only when consuming an
event or terminator. Event attenuation is the control low nibble plus global
adjustment byte 0x0020, clamped to 15; there is no envelope or selector-2
attenuation adjustment. Selector zero advances one channel and uses the
PC-speaker path; every nonzero selector advances four channels.
KQ2 writes both high and low non-PC tone bytes unconditionally. LSL1 writes
the high byte, then omits the low byte when (high & 0xe0) == 0xe0. Local
sound model switches cover selector-8 channel behavior and KQ2’s unconditional
low byte; early_sound_attenuation_output(...) covers the direct attenuation.
Save source and existing-file confirmation:
- KQ2 writer
0x2693, restore0x24af; - LSL1 writer
0x26b9, restore0x24d5; - each writer loads
0x05e1, subtracts base address 2, and writes block-1 length0x05df; - that partition equals the first
0x05dfbytes of the 2.936 map and omits final two-bytesaved_replay_count; - KQ2 decoded
OBJECT: item table0x00ff, maximum object index 16, 17 object records, 85 items, runtime block0x0256; - LSL1 decoded
OBJECT: item table0x003f, maximum object index 16, 17 object records, 21 items, runtime block0x0134; - KQ2 logic 0 calls action
0x8e(60); LSL1 calls0x8e(144); and - two local KQ2 saves and three local LSL1 saves confirm first-four block
lengths
05df/02db/0256/0078and05df/02db/0134/0120respectively.
No QEMU experiment was needed: the opcode bounds, restart branch, picture commands, sound event path, and exact serialized lengths are direct source or existing valid-file observations. Focused graphics, sound, save, table, and spec tests passed after modeling these variants.
2026-07-10: same-version PQ1/2.917 and KQ3/2.936 cross-checks
Goal: test whether the promoted KQ1 and SQ2 behavior was version-shaped or accidentally game-specific.
KQ3 uses the loader transform and was decoded with its local key. PQ1 AGI is
already MZ. Reports:
build/cross-version/pq1_kq1_table_comparison.md
build/cross-version/kq3_sq2_table_comparison.md
PQ1 and KQ1 have the same action/condition table bases, all 174 action records, all 19 condition records, and zero normalized handler differences. After MZ headers are removed, both images are 38,912 bytes and differ only at:
0x5aa4: PQ1 50 / KQ1 4b
0x5aa6: PQ1 00 / KQ1 31
0x5aa7: PQ1 65 / KQ1 00
These bytes lie in the expected-game signature literal. All 55 same-address resource, renderer, object, and motion roles normalize identically.
KQ3 and SQ2 have the same 176 actions and 19 conditions with zero table or handler differences. Their loaded image lengths are also 38,912 bytes. The only differences are signature bytes:
0x5b6c: KQ3 4b / SQ2 53
0x5b6e: KQ3 33 / SQ2 32
All 55 same-address subsystem roles match. This strongly supports treating the current gameplay rules as version profiles while keeping signatures and save dimensions as game data.
Selected-game save derivation:
- PQ1 decoded
OBJECT: item table0x004b, maximum object index 19, 20 records, 25 inventory items, runtime block0x016e, 291-byte name pool; - PQ1 logic 0 executes
0x8e(250), giving block 4 length0x01f4; - valid local
PQSG.8andSQSG.9confirm blocks05e1/035c/016e/01f4; localSQSG.1is truncated before block 4 and is excluded from valid evidence; - KQ3 decoded
OBJECT: item table0x00a5, maximum object index 16, 17 records, 55 inventory items, runtime block0x0307, 610-byte name pool; and - KQ3 logic 0 executes
0x8e(127), giving block 4 length0x00fe.
Local save helpers and tests now encode both sets of dimensions. KQ3 has no selected local save file, so its binary layout is source/game-metadata backed rather than existing-file confirmed.
2026-07-10: reusable normalized symbolic-role matcher
Added tools/match_interpreter_roles.py to make the recurring relocation pass
repeatable. It requires explicit reference/target game directories and
executables. Candidate entry points come from direct call/jump targets,
action/condition handlers, same-address role anchors, and optional explicit
candidates. Each reference role is disassembled and normalized with the same
rules as the table comparator.
Validation runs:
python3 -B tools/match_interpreter_roles.py \
--reference-label SQ2-2.936 --reference-game-dir games/SQ2 \
--reference-exe build/cleanroom/SQ2_AGI.decrypted.exe \
--target-label KQ3-2.936 --target-game-dir games/KQ3 \
--target-exe build/cross-version/kq3_agi.decrypted.exe \
--sq2-subsystems \
--output build/cross-version/kq3_sq2_role_matches.md
python3 -B tools/match_interpreter_roles.py \
--reference-label SQ2-2.936 --reference-game-dir games/SQ2 \
--reference-exe build/cleanroom/SQ2_AGI.decrypted.exe \
--target-label KQ2-2.411 --target-game-dir games/KQ2 \
--target-exe build/cross-version/kq2_agi.decrypted.exe \
--sq2-subsystems \
--output build/cross-version/kq2_sq2_role_matches.md
KQ3 produced 48 unique and seven ambiguous exact matches, with zero unmatched roles. KQ2 produced 36 unique, five ambiguous, and 14 unmatched roles. The KQ2 unmatched set corresponds to known source variants or indirect picture-command entries, demonstrating that the report is useful triage rather than a false claim of equivalence. Four focused matcher tests cover role parsing, candidate collection, relocation normalization, and report classification.
2026-07-11: all-profile role audit and ordered resource discard
Goal: continue from the completed profile matrix, audit every promoted local interpreter with the normalized role matcher, and inspect a concrete valid resource-lifecycle edge from source before changing the portable spec.
Commands included:
python3 -B tools/match_interpreter_roles.py ... --sq2-subsystems
ndisasm -b 16 -e 0x40cd -o 0x3ecd build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -e 0x4daa -o 0x4baa build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -e 0x4be8 -o 0x49e8 build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -e 0x3b79 -o 0x3979 build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -e 0x163c -o 0x143c build/cleanroom/SQ2_AGI.decrypted.exe
Generated role reports under build/cross-version/ cover KQ1, PQ1, KQ2,
LSL1, KQ4, KQ4D, and GR against the SQ2 role set. The unmatched rows belong to
already documented categories: early pattern handlers, indirect short picture
command entries, exact-four-loop selection, motion-mode changes, v3 resource
containers, and alternate display refresh branches. No new version profile was
justified by this pass.
The discard source pass found a portable lifecycle rule that had been stated
too weakly. View lookup 0x3979 leaves in [0x1000] the address of the root or
preceding next-link word that points at the selected record. View discard
0x3f0d writes zero through that address. Picture lookup 0x49e8 and discard
0x4bce perform the same operation through [0x1214]. Therefore discard
truncates the family list at the selected record: the selected resource and all
later records in that family become unreachable, rather than deleting only one
entry.
Both discard paths flush object update lists, call code.heap.rewind_to
(0x143c) with the selected record, rebuild update lists, and refresh the
free-memory state. Since resource allocations share the bump heap, continuing
to use a discarded payload is not a portable valid-data behavior. The clean
spec now defines ordered same-family retention/truncation and requires a live
selection to be released before discard. tools/agi_resources.py contains a
narrow portable transition model with regression tests; no QEMU experiment was
needed because both the link mutation and heap rewind are direct source
observations.
A linear corpus audit then disassembled all present logic resources from KQ1,
KQ2, KQ3, KQ4, KQ4D, LSL1, PQ1, SQ2, and GR. Discard actions are used in every
profile: picture discard counts range from SQ2’s one to KQ4’s 125, and view
discard counts range from zero in SQ2 to 76 in KQ4. None of the selected game
scripts uses variable-view discard action 0x99.
The clearest allocator-discipline example is KQ2 logic 67. Its room-entry path
loads views in the order 53,59,51,52,57,60. Its cleanup branch discards them
in the exact reverse order 60,57,52,51,59,53.
Most picture cases use load_picture_var, prepare_picture_var, then
discard_picture_var immediately. View cases similarly unwind temporary
loads or stop/deactivate/rebind the relevant object around discard. These
shipped-script observations support the valid-use discipline while the more
general selected-and-later truncation rule remains direct executable evidence.
The corpus reports are disposable generated files under
build/cross-version/*_logic_disassembly.txt. SQ2 was limited to logic numbers
below 140 because its two known out-of-range trailing directory entries are not
valid resource records.
2026-07-11: expanded local interpreter census
The private input set added BC, MG, MH1, MH2, SQ1, SQ1.22, and XMAS. The
expanded read-only census was written to
build/cross-version/game_census_expanded.md. No file under games/ was
modified.
BC and SQ1.22 use the already observed loader-managed executable transform.
They were decoded with each selected game’s own loader key into generated MZ
files under build/cross-version/. The other five executables are direct MZ
images.
Dispatch-table discovery initially failed for SQ1 and XMAS. Their action and
condition signatures are separated by an early 0x26-byte trailer rather
than the later v2 0x20 bytes. The detector now evaluates both observed v2
geometries and requires a unique plausible result. Source dispatch bounds and
table geometry establish:
| Build | Action table/count | Condition table/count |
|---|---|---|
| SQ1 2.089 | 0x03e7, 155 (0x00..0x9a) | 0x0679, 19 |
| XMAS 2.272 | 0x0417, 161 (0x00..0xa0) | 0x06bb, 19 |
| BC 2.439 | 0x061b, 170 | 0x08e3, 19 |
| MG 2.915 | 0x061d, 174 | 0x08f5, 19 |
| MH1 3.002.107 | 0x0620, 182 | 0x0942, 19 |
| MH2 3.002.149 | 0x0440, 182 | 0x0762, 19 |
Generated table comparisons show exact action/condition contracts and normalized handlers for BC versus LSL1, MG versus KQ1, and MH1 versus KQ4D. Role matching plus manual disassembly confirms BC’s shaped/stippled pattern and early sound paths as relocated LSL1 matches, and MG’s mapped full-EGA core as a KQ1 match. MH1’s mapped full-EGA core matches KQ4D, though its whole image has unmapped startup/data differences.
SQ1.22 and KQ1 loaded images have equal lengths and differ at only image bytes
0x5aa4, 0x5aa6, and 0x5aa7, all in the embedded expected-signature
region. Every mapped role is an exact same-address match.
MH2 and Gold Rush loaded images have equal lengths and only 29 differing
bytes. They classify as the action-0x12 call site plus Gold Rush helper
0x0062, three startup allocation-size words (0x0b80 versus 0x0a00), and
the MH2/GR signature literal. MH2 0x19d4 reads its operand and calls
ordinary room switch directly. This proves the Gold Rush 0x7e..0x80 -> 0x49
mapping is a build-specific rule, not generic 3.002.149 behavior.
For SQ1/XMAS, direct source slices established two portable version changes.
SQ1 action 0x86 at image 0x01e8 has no operand and always performs sound
stop, shutdown cleanup, and DOS termination. XMAS uses the same entry address
but consumes one byte, exits immediately for 1, and otherwise shows a
confirmation before exit. XMAS menu slots 0x9c..0xa0 point into the tiny
stubs at 0x8400..0x8404, which advance the bytecode pointer and return with no
menu state. SQ1 lacks those slots.
SQ1 position actions 0x25/0x26 at 0x7141/0x7199 write current
coordinates, erase old drawn state when needed, and then update saved
coordinates. XMAS 0x7196/0x71d3 writes current and saved coordinates
together without that erase call. The XMAS string-equality entry delegates to
0x0d8b; that helper still normalizes both strings before comparison, so the
shorter entry is not a behavioral difference.
SQ1 and XMAS OBJECT files begin with valid plain headers 4e 00 11 and
03 00 11. They define 26/one inventory entries. SQ1 uses the third byte as a
17-record count; XMAS increments it and allocates 18 records. XOR decoding them
was the earlier analysis mistake. A transform-neutral parser and tests preserve
the plain form. BC, MG, and the later inputs retain the repeating-key encoded
form.
Original saves establish BC block lengths 1503,731,309,254,variable, MG
1503,903,5,220,variable, and SQ1.22
1505,774,328,100,variable. The later source trace below resolves why the MG
save must not be associated with the currently bundled executable.
XMAS’s active files match its .ORG variants. ORIGINAL.BAT, LOGMETH.BAT,
and VOLMETH.BAT show three selectable distribution methods. The original
directory retains three disk-number nibbles while the selected payload is
named VOL.0; generic installed-layout missing-volume errors are therefore a
packaging artifact.
The initial MH1/MH2 resource-reference audit aborted on the first unreadable logic, which made it unsuitable for incomplete distributions. The audit now records unreadable source logics and continues through every readable script; a focused synthetic test verifies that references found before/after a bad logic remain reported.
The tolerant report
build/cross-version/mh_resource_reference_audit.json finds MH1 logic 136
unreadable and direct references from readable scripts to unreadable views 7,
74, 75, 76, 77, and 85. MH1 is therefore incomplete for valid gameplay
analysis. MH2 has 31 unreadable logics; no readable script directly references
an unreadable resource, but skipped scripts prevent a complete reachability
claim.
Early 2.089/2.272 subsystem completion and MG save resolution
Full 16-bit disassemblies were generated locally as
build/cross-version/sq1_2089.ndisasm and
build/cross-version/xmas_2272.ndisasm. The picture scanners at SQ1 0x5baf
and XMAS 0x5c38 subtract 0xf0 and reject dispatch indices above 8. They
therefore recognize 0xf0..0xf8; neither build has the later 0xf9/0xfa
pattern slots. Command, coordinate, line, pixel, and fill bodies match after
relocation.
Post-logic object routines SQ1 0x05cc and XMAS 0x04c6 test the automatic
loop flags and loop counts but do not test movement countdown byte +0x01.
Both use the two-direction table for loop counts 2 and 3, the four-direction
table for exactly 4, and no automatic change for other counts. KQ2 0x0530
adds the later countdown-equals-1 gate.
The parse-string actions at SQ1 0x1817 and XMAS 0x17f7 both compare the
slot selector with 6. SQ1 word-sequence predicate 0x0965 first requires the
operand count to equal the parser count/error position, then compares every
word with exact or 0x0001 wildcard semantics. XMAS 0x087d has the later
remaining-count loop and explicit 0x270f tail terminator.
Sound start/tick/write entries are SQ1 0x74f5/0x7557/0x7615 and XMAS
0x7514/0x7576/0x7634. Both select only channel 0 for device zero, select
all four channels otherwise, initialize countdowns to 1, and lack attenuation
envelopes. Both call a device-2 helper (0x7657/0x7680) that adds 3 to a
control low nibble below 8. SQ1 then emits both tone bytes and the resulting
control byte. XMAS emits both tone bytes, adds the global adjustment to the
entire resulting control byte modulo 256, then uses signed <= 15 to retain
the result or emits 0x0f otherwise.
SQ1 save/restore at 0x2501/0x2335 writes and reads four blocks. The writer
uses lengths 0x03db, 17 * 0x2b = 0x02db, 0x0153, and twice replay
capacity word DS:0x0145, then closes the file. XMAS 0x24f5/0x2315 uses five blocks:
0x03db, 18 * 0x2b = 0x0306, 0x000f, twice the same capacity word, and
the variable result of its logic-resume serializer.
The replay writer uses DS:0x0147 as its active pair count, confirming that
the adjacent DS:0x0145 value serialized as block-4 length is capacity rather
than current occupancy. The common block-1 tail is now source-partitioned:
39 four-byte key mappings at DS:0x0149, four padding bytes, six live 40-byte
strings at DS:0x01e9, six reserved 40-byte records, and text/input/status
state at DS:0x03c9..0x03dc. These correspond to block-relative offsets
0x0147..0x03da.
The earlier prefix is now fully partitioned from script handlers and runtime
consumers. Relative to block start DS:0x0002, it contains signature,
variables, flags, and timer through offset 0x012a; display mode at
0x012b; horizon at 0x012d; previous navigation event at 0x012f; motion
rectangle bounds at 0x0131..0x0138; object-0 direction coupling, prepared
picture, coupled direction, and rectangle-enable words at 0x0139..0x0140;
a startup-only count at 0x0141; and replay capacity/count at
0x0143..0x0146. Action 0x3f writes horizon, 0x5a/0x5b configure the
rectangle, 0x83/0x84 control direction coupling, picture preparation
writes the prepared-picture word, and 0x8e sets replay capacity. The
startup-only count has no later script-visible consumer and remains a
preserved selected-build value rather than portable gameplay state.
For MG, tools/compare_interpreter_tables.py extracted the current MZ image to
build/cross-version/mg_2915_image.bin; its disassembly is
build/cross-version/mg_2915.ndisasm. Startup 0x0fa5 decodes OBJECT, reads
third header byte 0x5a, increments it at 0x1001, and multiplies 91 by the
43-byte record size at 0x100e. Save writer 0x2751 writes block 1 length
0x05e1, the resulting block 2 length 0x0f49, and metadata block length 5.
The bundled MGSG.1 instead contains 0x05df, 0x0387, and 0x0005 and is
dated 5 November 1987, while AGI is dated 16 November 1987. The save is an
older incompatible artifact, not evidence for current-header semantics.
Early object composition and movement comparison
The early builds use different temporary structures around object drawing.
SQ1 0x036c scans active records in object-table order. Records whose flag
mask 0x0011 equals 0x0011 receive a baseline/fixed-priority key and are
selection-sorted by the smallest key; the first table record wins a tie. SQ1
0x0615b separately scans records whose same mask equals 0x0001 and draws
them immediately in table order. Wrapper 0x0524 therefore draws the
0x0010-clear partition in object-number order, then draws the set partition
in key order.
XMAS 0x02c1 takes a membership predicate and applies the same stable
selection sort to either partition. Wrappers 0x060e9 and 0x06100 select
the set and clear partitions respectively, and 0x06151 draws the clear
partition followed by the set partition. The cross-partition order is the
same as SQ1, but XMAS key-sorts the clear partition too. Overlapping valid
objects in that partition can therefore produce different pixels when object
number order and baseline/fixed-priority order disagree.
The associated restore and dirty-refresh passes preserve those memberships.
SQ1 uses a temporary pointer array at 0x0653 plus a separate clear-partition
array at 0x12ab; XMAS uses two linked-list roots. This storage choice is not
itself portable behavior.
The SQ1 movement routine at 0x13c7 calls its restore helper before scanning
objects and its rebuild/refresh helper afterward. The movement body beginning
at 0x13d2 matches XMAS 0x13bf after relocation. The frame-mode updater
SQ1 0x48a8 / XMAS 0x487a, collision test 0x3f8b / 0x3f6d, control
acceptance 0x4e25 / 0x4df7, dirty-rectangle updater 0x4f4a / 0x4f23,
and placement helper 0x515c / 0x51c6 likewise match after relocation.
Thus the new portable delta is composition ordering, not movement, collision,
or control acceptance.
Early shared-action boundary against 2.411
The XMAS/KQ2 action-table triage was read at each differing entry rather than treated as behavior by itself. Three shared action families establish an early-profile boundary that SQ1 shares with XMAS because those entries match between 2.089 and 2.272.
SQ1/XMAS show-picture handlers 0x43e7/0x43cc call the full refresh and set
the picture-shown word. KQ2 0x4a4c first clears f15 and invokes the active
text-window close helper. The older show action therefore does neither of
those two prefix effects.
SQ1/XMAS object-distance handlers 0x4061/0x4043 calculate absolute
baseline-Y distance plus absolute center-X distance and store AL directly.
KQ2 0x46b9 retains the sum as a word, clamps values above 0xfe, then stores
the result. Inactive input objects still produce 0xff in both families.
SQ1/XMAS target-motion handlers 0x6358/0x63a7 and
0x63ce/0x641d initialize mode 3, targets, saved/override step, completion
flag, partition membership, and object-0 coupling state, then return. KQ2
0x6954/0x69d1 performs the same setup and additionally calls 0x1621,
which calculates direction immediately and completes immediately when the
target lies within the strict step bands. The old builds defer that helper’s
effects until their next eligible target-motion pass.
The SQ1 action 0x4d handler at 0x652e clears direction byte +0x21 but not
autonomous mode byte +0x22; XMAS 0x657d clears both. SQ1 action 0x4e at
0x6567 does not clear either object byte and only changes object-0 globals,
including clearing the remembered navigation event. XMAS 0x65ba clears mode
byte +0x22 for every object and applies the later object-0 coupling state.
SQ1/XMAS inventory handlers 0x2e89/0x2e87 implement the carried-item
display inline. They scan three-byte metadata entries in item order, select
only location byte 0xff, lay entries out in two columns, display the empty
inventory text when needed, and wait for one acknowledgement event. Neither
handler reads the later inventory-interaction flag or writes variable byte
v25. KQ2 0x30d6 instead calls the later list selector that branches on the
interaction flag and can write the selected item or 0xff to v25.
Early profile residual-edge closure
The remaining XMAS/KQ2 action-entry differences were reduced to their normal
full-EGA branches. Input configuration 0x6eb0/0x752e stores the same base,
bottom, row, and eight-pixel display offset outside KQ2’s alternate mode 2.
Number input 0x6872/0x6e2b uses the same four-character editor and decimal
low-byte result outside that mode. Input disable/enable and explicit
refresh/erase similarly reach the same ordinary text-line and prompt state;
the extra tests select alternate mode 2 paths.
KQ2 display-mode toggle 0x758a adds an interrupt-timing guard before the
same mode-bit toggle and redraw sequence used by XMAS 0x6eec. This does not
change the specified state transition or final pixels. The restart difference
adds open-resource/file cleanup before accepted reinitialization. Successful
view-description preview differs in temporary backing allocation and diagnostic
checks but uses the same selected cel, centered placement, description text,
modal wait, and restoration. Joystick calibration remains hardware-conditional
and outside the full-EGA gameplay target.
All shared condition contracts are covered. Successful valid-data view binding, loop selection, and cel selection share the same transitions; SQ1 adds upper bound diagnostics that do not alter valid selectors. Combined with the prior picture/raster, composition, frame, collision, control, placement, dirty-region, motion, sound, save, and accepted-action analysis, no residual valid full-EGA domain remains unclassified for the observed 2.089 and 2.272 builds.
2026-07-11: directory-tail addressability closure
Goal: classify KQ1 and KQ4D sound-directory anomalies without promoting garbage-memory or malformed-resource behavior.
Static evidence and local reads:
- KQ1’s 2.917
code.resource.read_volume_payload_onceis an exact normalized match for the 2.936 direct-record reader. There is no sound-specific alternate volume-header path. - KQ1
SNDDIRentries 34 through 37 are raw triples22 0e 2f,22 0e 8f,22 0e ed, and22 12 6b. They selectVOL.2offsets0x20e2f,0x20e8f,0x20eed, and0x2126b. - The selected KQ1
VOL.2is only0x1630bbytes. Every one of those offsets lies beyond the file by 43,812 to 44,896 bytes, so no record header exists to interpret. - KQ4D’s combined-directory sound section begins at
DMDIR:0x02d5. Resource selectors are unsigned bytes, so only entries 0 through 255 are reachable; directory-like triples later in the file cannot be selected. - Within that byte range, entries 70 through 79 select clean sound records.
Entry 198 points to
DMVOL.0:0x0778, whose v3 metadata selects picture-nibble expansion and whose expanded payload begins with picture commands. Parsing it as sound fails with a truncated channel event. - Entries 221 and 223 through 236 point into stored payload bytes rather than
to
12 34record headers. Decoded KQ4D scripts reference only sounds 70 through 79.
Implementation and validation:
tools/agi_resources.pynow reports an explicit directory-offset-beyond- volume diagnostic before checking a header.- Directory parsing now exposes at most 256 entries per family, matching the script-visible byte resource-number domain.
- Focused resource, census, reference-audit, and specification tests passed 34 tests. The regenerated census reports four KQ1 out-of-volume sound entries and 15 addressable KQ4D bad-header sound entries; no referenced unreadable entry remains in either game.
Conclusion: KQ1 entries 34 through 37 are stale or out-of-package locations, not an alternate sound format. KQ4D bytes beyond sound entry 255 are unaddressable file tail, while its reachable high anomalies are malformed or cross-family entries outside the valid-data contract.
2026-07-11: top-level cycle ordering closure
Goal: replace the provisional runtime-cycle summary with an instruction-backed ordering contract while keeping interrupt delivery details outside the portable specification.
The decrypted SQ2 2.936 executable was regenerated from the immutable local game input and its main loop was disassembled with:
python3 -B tools/decrypt_agi.py --game-dir games/SQ2 \
--output build/cleanroom/SQ2_AGI.decrypted.exe
ndisasm -b 16 -o 0x0100 -e 0x0300 \
build/cleanroom/SQ2_AGI.decrypted.exe
Direct instruction order at image 0x0150 establishes the following call and
state sequence:
0x015bcallscode.cycle.wait_for_pacingat image0x7f78;0x015ecallscode.input.clear_status_bytesat image0x4c23, which clears 50 bytes beginning at data0x1218, then the loop clears flags 2 and 4;0x0175callscode.input.update_timed_eventsat image0x61f2, followed bycode.input.process_cycle_eventsat image0x357c; that helper clears data bytes0x001c(v19) and0x0012(v9) before processing events;- the loop mirrors the object-0/global direction state, then calls the
pre-logic object pass at image
0x0644; - after snapshotting
v3and flag 9,0x01b4callscode.control.save_abort_contextat image0x7ee0, and0x01bdinvokes logic 0; - a zero logic return clears
v9,v5,v4, and flag 2, refreshes the savedv3value, and re-enters logic 0 without branching through the pacing, input, direction-mirror, or pre-motion calls; - normal return restores object 0’s direction and conditionally redraws the
status line when the saved
v3or flag-9 values differ; - the loop clears
v5,v4, and flags 5, 6, and 12; and - alternate text mode at data
0x1757suppresses the final object/frame update call at image0x0563; otherwise that call runs before the loop returns to pacing.
The separately examined timing path increments the pacing and elapsed-time counters independently of this call chain. Existing sound evidence likewise places sound progression in timer-driven work. The behavioral contract therefore models timer and sound ticks as asynchronous inputs observed at cycle boundaries, not as a fictitious ordered call inside the synchronous loop. It does not require an independent implementation to reproduce DOS interrupt delivery between individual bytecode instructions.
Conclusion: the 2.936 top-level cycle now has a complete portable phase order, including exact transient-state lifetimes, immediate logic-0 re-entry, and the alternate-text-mode update gate. Instruction addresses and helper boundaries remain evidence-only and are not part of the clean-room specification.
2026-07-11: QEMU inverse-text corruption and VGA BIOS patch
Goal: determine why SQ2 2.936 produces repeated glyphs in black-on-white text under QEMU, distinguish game behavior from emulator behavior, and produce a reproducible workaround that does not modify the immutable game input.
Font-address helper audit
The initial FIXAGI.COM experiment asked INT 10h/AX=1130h, BH=03h for the
active 8-by-8 font, enabled the i440FX F0000h shadow for writes, and copied
1024 bytes to F000:FA6E. Static inspection found that its original copy loop
changed DS before loading the saved source offset. The subsequent
mov si,[srcoff] therefore read from the VGA ROM segment rather than from the
COM program. Memory dumps confirmed that this version replaced a correct font
with unrelated bytes.
After moving the saved-offset load before the DS change and adding a
byte-for-byte verification pass, the helper copied the intended font. The
source and destination font SHA-256 was:
4cd5079f275e2544bd396e9acd38895f88cba007be4b7069e9e2628166801413
The installed QEMU system BIOS already contains that same 1024-byte table at
the ROM location corresponding to physical F000:FA6E. Leaving the i440FX
PAM0 region read-only (10h), write-only (20h), or read/write (30h) did
not change the corrupted dialog. The font remained intact while SQ2 ran.
Changing from QEMU standard VGA to QEMU Cirrus VGA also reproduced the same
failure. These observations reject missing font bytes, shadow write
protection, and the selected emulated VGA card as causes.
Interpreter instruction path
Disassembly of the decrypted SQ2 executable identified the graphics glyph
preparation helper at image 0x2c55. For ordinary characters it reads eight
bytes from F000:FA6E + character * 8 into a scratch glyph at data offset
0x0ea6. The inverse path complements the four copied words and returns
character byte 80h.
The caller around image 0x2aa5 saves interrupt vector 43h, temporarily
sets that vector so character 80h resolves to the scratch glyph, invokes BIOS
INT 10h/AH=09h, and restores the vector. This means the interpreter does not
directly write inverse glyph pixels in this path; it asks the video BIOS to
draw from a temporary font vector.
A QEMU GDB-stub/rizin trace stopped on the first inverse dialog character,
S. The source glyph at physical 0xffd06 was:
78 cc e0 70 1c cc 78 00
The copy at the runtime scratch address was identical, and the inversion produced:
87 33 1f 8f e3 33 87 ff
Immediately before INT 10h, vector 43h contained 103b:0aa6, the BIOS
character height was 8, and therefore character 80h selected exactly the
scratch glyph at 103b:0ea6. Registers included AX=ff80, BX=000f, and
CX=0001. A hardware read watchpoint on the scratch glyph did not trigger
during the BIOS call. The firmware did not dereference the vector SQ2 had
correctly installed.
Firmware confirmation and correction
The official LGPL VGABIOS 0.7a binary was tested as an alternate QEMU option
ROM. It reproduced the repeated glyph. Inspection of its corresponding source
showed why: its planar, CGA, and linear graphics character renderers select
private compiled-in vgafont8, vgafont14, or vgafont16 arrays rather than
the current interrupt-vector font.
tools/setup_vgabios.py now reads the tracked pristine 0.7a standard VGA ROM
from third_party/vgabios/, checks SHA-256
cd9fdd6a789dcd22b8a6b3b152788d43238de49cce674cff57bdeb94580246c6,
and applies an exact binary patch assembled from
tools/vgabios_int43_patch.asm. The patch redirects the planar EGA glyph-byte
fetch through the current vector 43h, occupies 44 bytes of verified unused
ROM padding at 0xa1a4, and updates the option-ROM checksum. It deliberately
leaves the firmware’s pixel mask, attribute, plane, and destination logic
unchanged.
The repository copy is byte-identical to the official upstream binary. Its
LGPL 2.1 license and provenance record include official binary and complete
source archive URLs, the binary digest above, and source-archive SHA-256
9d24c33d4bfb7831e2069cf3644936a53ef3de21d467872b54ce2ea30881b865.
The generated patched ROM has deterministic SHA-256
cfbbc5e3f97cb40cbc315b68e1e52d4488e6e27a47b339452a6a4ebf00f01247
and remains ignored disposable output.
The corrected ROM was run with:
qemu-system-i386 -m 16 -boot c \
-drive file=build/freedos/freedos.img,format=raw,if=ide,index=0,media=disk \
-vga none \
-device VGA,romfile=/absolute/path/build/vgabios/vgabios-0.7a-int43.bin \
-display vnc=127.0.0.1:6 -monitor stdio
SQ2 was advanced through its introduction, station scene, and default-name
screen without running FIXAGI.COM. The first playable-room dialog rendered
the expected distinct black glyphs on white, including the complete text
beginning Orbital Station 4 is one of many. This validates the diagnosis and
establishes the patched VGA BIOS as the preferred QEMU screenshot environment.
Local verification after integrating the generated ROM into shared harness launches passed 425 tests with four expected optional-fixture skips. The full compatibility runner, both mdBooks, opcode-evidence checks, deterministic ROM rebuild comparison, and whitespace validation also passed.
After promoting the pristine ROM to a tracked third-party input and integrating the builder into normal FreeDOS setup, a separate temporary FreeDOS image was created successfully without altering the populated manual-test image. Final verification passed 427 tests with the same four expected skips, rebuilt both books, and passed the complete compatibility and opcode-evidence gates.
Behavioral conclusion: the original interpreter consumes an external 8-by-8 font profile and can temporarily substitute glyph data for inverse graphics text. Exact glyph shapes remain a declared platform-font input in the clean specification. The firmware implementation details and QEMU workaround remain in the evidence book and harness documentation rather than becoming portable engine requirements.
2026-07-11: KQ4 route and v3 message storage
The full KQ4 playthrough index initially failed because
decode_logic_messages() applied the repeating-key XOR to every resource.
Scanning all present records showed that failures clustered across dictionary-
expanded logic, whose expanded message regions were already readable, while
KQ4’s three direct logic records retained encoded text.
Direct KQ4 source inspection established the selection rule:
code.resource.read_recordat image0x311breads expanded and stored lengths from the v3 header.- When the lengths match, the direct-read branch at
0x3304..0x3324sets word[0x0f5e]to one. - When lengths differ, dictionary expansion at
0x3326..0x334eclears that word. code.logic.load_recordat0x13d9constructs the logic record. At0x1458..0x148cit tests[0x0f5e]and calls the repeating-key helper over the message text only when the word is nonzero.
The disassembler and playthrough index now derive message decoding from the volume record transform. All 177 present KQ4 logics decode, yielding 5,148 parser predicates, 469 room transitions, 86 score-changing sites, and declared maximum score 230.
The game-resource pass reconstructs a complete static 230-point route. Its notable score constraint is the frog chain: taking the crown directly from the held frog awards two points and lets the frog return; the catch parser remains available, so recatching and kissing it enters the separate three-plus-two point transformation sequence. Three successive Lolotte errands each award seven points, and the first two board crossings each award two. With alternate room copies collapsed, the resulting ledger totals 230.
agi_graphics.py now obtains picture and view payloads through the generic
version-aware resource reader rather than opening v2 split directory names.
The SQ2 graphics baseline still passes all 91 focused tests. A KQ4 qualitative
sweep decoded all 146 locally readable pictures with patterned brushes
disabled because brush-table offsets remain interpreter-build-specific.
Pictures 150–151 remain unreadable because the selected private copy lacks
KQ4VOL.6.
2026-07-11: PQ1 static winning-route reconstruction
The immutable local games/PQ1 evidence set was indexed without modifying its
files. Its 118 present logic resources all decode. The resulting index contains
3,147 parser predicates, 108 explicit room transitions, 114 positive score
operations, and 25 inventory slots. Logic 101 assigns maximum score 245;
logics 79, 103, and 104 establish the successful Bains capture, trial, and
terminal parade sequence.
The decoded messages and transitions establish a complete story dependency chain: patrol briefing and equipment, Helen Hots citation, homicide scene, Blue Room and cafe encounters, DUI and Hoffman arrests, transfer to Narcotics, no-bail warrant, park drug bust, Cotton Cove identification, Hoffman/Taselli/ Bains research, Hotel Delphoria cover, back-room poker, Room 404 transmitter, Bains capture, conviction, and parade.
A syntax-only sum of positive score operations is 270. Direct branch analysis has proved 13 points of mutually exclusive or lower-valued alternatives across the traffic-stop proposition, gun-locker parser branches, phone-number parser branches, drug-source questioning, duplicate newspaper availability, and the late Room 404 fallback. The remaining 257 is an upper bound: another 12 points must be excluded by cross-phase reachability to reconcile the declared 245. This gap is documented rather than silently assigned to route actions.
Two bundled save files were parsed read-only. Both preserve score 156 of 245 near the hotel operation and agree on the selected game’s save dimensions and inventory names. They are consistency evidence, not route sources. A brush-disabled sweep decoded all 71 present pictures and qualitatively corroborated the station, patrol, investigation, jail, courthouse, hotel, poker, and ending geography.
2026-07-11: LSL1 static maximum-score route
The immutable local games/LSL1 evidence set was indexed without modifying
its files. All 46 present logic resources decode, yielding 2,274 parser
predicates, 16 explicit room switches, 49 positive score operations, and 21
inventory slots. Logic 51 assigns maximum score 222. The positive score
operations also sum to 222, with no negative score operation.
Direct control-flow and message analysis places every score site in one route. The route passes through Lefty’s restroom, hallway, television/pimp bypass, protected upstairs encounter, and fire escape; the convenience-store, telephone, wine, and pocket-knife chain; casino funding, disco pass, Fawn’s gifts, and the chapel; Fawn’s wine trap and rope escape; the return for the window pills; Faith and the penthouse control; Eve’s apple; the inflatable-doll side sequence; and logic 45’s final 25-point terminal encounter.
The independently grouped score equation is 3 + 55 + 29 + 53 + 82 = 222:
three general awards, 55 around Lefty’s, 29 from the store/telephone chain, 53
from casino/disco/chapel, and 82 from the hotel/penthouse chain. A qualitative
brush-disabled sweep decoded all 43 present pictures and corroborated the
compact geography. Random adult-verification questions, casino outcomes,
movement, and real-time sunrise remain original-interpreter replay work.
BC maximum-score route reconstruction
The immutable local games/BC input was indexed with
tools/logic_playthrough_index.py. All 85 present logic resources decoded.
Logic 107 assigns maximum score 230; the 40 positive award sites have a raw sum
of 463. Inspection of their enclosing conditions and state writes established
the shared alternatives described in
games/bc_playthrough_analysis.md. Selecting one member of each group reduces
the total exactly to 230. Logic 71 compares the final score with maximum minus
15 and displays its strongest congratulations at or above that threshold; the
selected terminal reward supplies the last 15 points.
The selected static route uses the Gwystyl Hen Wen rescue, wagon castle entry, one dungeon escape clue, one Eilonwy introduction, the hidden-passage and Dyrnwyn chains, the sword-for-cauldron bargain, the mirror resolution, and the returned-sword ending. A qualitative picture sweep rendered all 69 present pictures with patterned brushes disabled. No original-interpreter playthrough has yet established movement and timing reachability for the complete route.
MG complete-route reconstruction
The immutable local games/MG input was indexed and disassembled without
modification. All 73 present logic resources decode. The selected build is
Mixed-Up Mother Goose under interpreter 2.915, with 49 pictures, 100 views,
53 sounds, and 159 indexed room switches.
Logic 0 assigns 18 as the maximum, initializes the score variable to zero, randomly distributes twenty custom props among eligible rooms, and implements a one-prop carried state outside the ordinary inventory table. A successful recipient interaction calls one of eighteen rhyme-animation logics. Their common completion signal is converted by logic 0 into exactly one score/count increment after the animation and sound complete. At count 18, logic 0 enters logic 102, which runs the cast celebration and displays the final congratulations before disabling normal game-control menu entries.
The twenty props serve eighteen goals. Seventeen rhymes consume one prop each;
Old King Cole requires pipe, bowl, and fiddlers in that order and increments
the counter only after the fiddlers. The complete recipient/prop map, regular
35-room outdoor grid, nine attached rooms, terminal contract, and replay plan
are recorded in games/mg_playthrough_analysis.md. A qualitative
brush-disabled sweep decoded all 49 present pictures. Exact randomized
placement and movement remain original-interpreter replay work.
MH1 partial terminal-route reconstruction
The immutable local games/MH1 input identifies Manhunter: New York 1.22
under interpreter 3.002.107. Its v3 combined directory advertises 66 present
logics, but logic 136 selects MHVOL.3:0x1d323, beyond that file’s available
length of 0xfe00. The playthrough indexer’s error path was corrected to use
the structured directory-entry fields, so it now records that unreadable logic
and continues. Sixty-five logics decode, yielding 11 parser predicates, 138
room switches, 93 inventory mutations, and no score or maximum-score writes.
The readable logic establishes a four-phase assignment value: Bellevue
Hospital, the missing Grand Central maintenance robot, the dead Orb at
Greenwood Cemetery, and illegal Alliance-computer access. Logic 131 changes
phase 1 to 2 after its Data Card report sequence. Returning home with the common
completion flag advances phase 2 to 3 and phase 3 to 4. The late game collects
Modules A through D, records four installed-module states in logic 154, enters
the Alliance and ship controls, and records four distinct bomb-drop states in
logic 151. Only all four states together switch to logic 162, whose conclusion
displays To be continued.... No numeric score participates in this contract.
Resource completeness prevents a full route claim. The missing logic’s room
owns the Crowbar in OBJECT; readable scripts directly reference unavailable
views 7, 74, 75, 76, 77, and 85; and only 196 of 237 present picture entries
decoded from the available volumes. The proven phase skeleton, inventory
evidence, ending condition, retry/death observations, and exact remaining work
are recorded in games/mh1_playthrough_analysis.md.
MH2 partial route and missing-volume boundary
The immutable local games/MH2 input identifies Manhunter: San Francisco
3.02 under interpreter 3.002.149. Its combined directory advertises 96 present
logics, but 31 select absent file MH2VOL.3. The playthrough indexer was
extended to catch missing-file errors through the same explicit unreadable-
resource path used for malformed and out-of-range records. It now retains each
missing logic’s number, advertised volume, offset, and error while continuing
to index the other 65.
The readable set contains six parser predicates, 135 room switches, 32 inventory mutations, and no score or maximum-score writes. Logic 0 directly dispatches to many missing rooms, including 131-133 and numerous late rooms from 164 through 194. Logic 172 also switches to missing room 175. The gap is therefore on ordinary room flow rather than a group of proved-unused records. The same absent volume leaves 71 of 248 present pictures, 67 of 181 present views, and 73 of 195 present sounds unreadable.
Readable story evidence establishes the New York-to-San Francisco pursuit,
acquisition of the ID Card and MAD, and two assignments selected by the phase
value: a burning boat at Pier 5 and a human on the Ghirardelli Square sign. It
also maps the available travel network, 32 inventory entries, Three Aces prize
choices, hybrid Rat/Dog/Human experiment disclosures, and the planned release
near Fisherman’s Wharf. The words The End in logic 146 belong to an in-world
Orb presentation, while several YOU WON messages belong to local arcade
sequences; neither is a whole-game terminal contract. The actual ending and
complete winning route remain unavailable until the matching MH2VOL.3 is
provided. The bounded route and replay backlog are recorded in
games/mh2_playthrough_analysis.md.
Cross-interpreter picture brush audit
The brush audit began from each interpreter’s disassembled picture command
scanner, not from rendered output. For encrypted AGI files, the existing
local loader transform produced the loaded MZ image; direct MZ interpreters
were stripped to their load image. A structural scanner signature then yielded
the final accepted command, dispatch-table offset, and handlers. The brush
data was located independently through the eight observed column-mask words at
four-byte spacing, followed by the eight radius pointers.
The first audit invocation imported agi_graphics, whose global command-line
path parser consumed the first repeated --game-dir; that incomplete report
omitted SQ1. The audit tool was corrected to keep its pure structural scanner
local, and the all-game run then reported all 16 selected directories.
The command was:
python3 -B tools/brush_table_audit.py \
--game-dir games/SQ1 --game-dir games/XMAS \
--game-dir games/KQ2 --game-dir games/BC --game-dir games/LSL1 \
--game-dir games/MG --game-dir games/KQ1 --game-dir games/SQ1.22 \
--game-dir games/PQ1 --game-dir games/SQ2 --game-dir games/KQ3 \
--game-dir games/KQ4 --game-dir games/KQ4D --game-dir games/MH1 \
--game-dir games/GR --game-dir games/MH2 \
--output build/brush-audit/report.md
SQ1 2.089 and XMAS 2.272 accept commands only through 0xf8. KQ2 2.411
accepts 0xf9/0xfa but contains no shaped-brush table: source inspection
confirms that 0xf9 consumes and ignores its operand and 0xfa calls the
ordinary pixel writer for each coordinate pair. Every build from BC 2.439
onward contains the complete shaped/stippled routine and directly references
the structurally selected mask and pointer tables.
The full v2 builds share radius-one rows e000 e000 e000 and horizontal-clamp
immediate 0x0140. Every observed v3 build instead uses radius-one rows
4000 e000 4000 and immediate 0x013e. Since the 4000 rows overlap neither
examined logical-column mask, the normal masked output is two center-row
pixels rather than v2’s 2 by 3 block. All eight column masks and all other
radius rows are identical. Comparing a normalized 0x104-byte brush-routine
window found that clamp immediate to be the only code difference across the
full v2/v3 families. This corrects the earlier broad statement that GR’s
pattern plotter was only a relocation of SQ2’s: most of the routine is, but
these two observable brush-family differences are real.
The local renderer was changed to discover brush tables structurally. It now
uses the selected radius-one family to apply the corresponding edge clamp and
automatically falls back to KQ2’s point behavior when no complete brush table
exists. The games input directories were read only; all decoded images and the
generated report remained under disposable build/brush-audit/.
Replacement MH1/MH2 resource-completeness audit
The selected games/MH1 and games/MH2 inputs were replaced with alternate
copies. A fresh census found that both now include volume 3. The checks were:
python3 -B tools/game_census.py \
--game-dir games/MH1 --game-dir games/MH2 \
--format markdown \
--output build/cross-version/mh_replacement_census.md
python3 -B tools/resource_reference_audit.py \
--game-dir games/MH1 --game-dir games/MH2 \
--output build/cross-version/mh_replacement_reference_audit.json
AGI_GAME_DIR=games/MH1 python3 -B tools/logic_playthrough_index.py \
--output build/playthrough/mh1-replacement/index.json
AGI_GAME_DIR=games/MH2 python3 -B tools/logic_playthrough_index.py \
--output build/playthrough/mh2-replacement/index.json
MH1 now decodes all 66 present logics, 237 pictures, and 138 views. Its sound
directory contains 101 present-looking entries. Entries 136 and 138 point into
ordinary bytes in MHVOL.0 rather than v3 record headers and are not referenced
by decoded scripts. The remaining 99 sounds all decode, and the immediate
reference audit finds exactly those same 99 sound numbers. Every statically
addressable view also resolves.
MH2 now decodes all 96 present logics, 248 pictures, and 181 views. Sound
entries 215 and 216 select absent MH2VOL.6, but neither is referenced. The
other 193 sounds all decode and exactly match the script-referenced sound set.
No source logic is skipped in either replacement. The four sound-tail entries
are classified as inert directory debris rather than missing valid gameplay
resources.
The first closure check falsely reported view 0 as unavailable in both games.
Source and existing opcode evidence showed that
tools/resource_reference_audit.py had treated set.loop operand 1 as a view
number and had selected operand 2 instead of operand 0 from add.to.pic.
Correcting those roles removed the false view-0 dependency. The tool now also
reports referenced_unavailable, which compares all references with the
readable set and therefore catches absent entries as well as malformed present
ones. A synthetic test covers the corrected set.loop, add.to.pic, and
absent-reference cases.
The replacement MH1 interpreter is 3.002.102, not the former copy’s
3.002.107. Its AGIDATA.OVL is byte-identical to KQ4D’s; the equal-length AGI
executables differ at only two embedded signature bytes. Replacement MH2
retains the previously classified 3.002.149 relationship with Gold Rush:
equal-length AGIDATA files are byte-identical and the AGI files differ at the
same 29 classified bytes. No file under games/ was modified by the audit.
Replacement MH1/MH2 playthrough reconstruction
The former MH1 and MH2 playthrough chapters were deleted before analysis so the complete replacements would not be fitted into the earlier incomplete narratives. Fresh indexes were generated from the commands above, and terminal logic was identified by searching for continuation messages and outgoing room edges. Incoming transition predicates were then traced backward through the current scripts.
MH1 logic 162 has no outgoing transition. Its only normal incoming edge is
logic 151 offset 0x0616, guarded by the late-story flag, flight stage 12, and
four separate bomb-region flags. Logic 154 consumes Modules A, B, C, and D at
independent cockpit positions before entering logic 151. The phase variable
used by the assignment scripts progresses through Bellevue Hospital, Grand
Central Terminal, Greenwood Cemetery, and Alliance-computer orders. The shared
MAD parser recognizes six named records, but recognized-name flags and report
phase are separate; exact successful answer order remains a replay question.
MH2 logic 187 has no outgoing transition. Its only incoming edge is logic 183
offset 0x047f, guarded by maze state 4, X greater than 129 and less than 144,
and Y less than 24. Logic 183 wraps movement at all four edges and remaps its
direction state, so reaching the same screen boundary in another maze state is
not sufficient. Backward tracing found the complete late chain through the
Rat Mask 1 and Full Flask consumption, hatchet and ring branches,
Orb-on-a-Stick sequence, statue/Orb Card access, lava/slave controller,
helicopter, logic 186 control sequence, and final maze.
The terminal scripts reference multiple pictures rather than one static end
screen. Representative MH1 pictures 76, 79, 89, 90, 130, and 189 and MH2
pictures 19, 31, 35, 39, 205, 253, and 254 were rendered under
build/playthrough/ to corroborate scene identity. These renders were not used
to infer control flow. Neither game contains conventional score or
maximum-score mutations, so terminal reachability is the applicable winning
criterion.
SQ1.22 precise success-path reconstruction
The SQ1.22 work used only the immutable games/SQ1.22 resources and the
project’s locally inferred decoders. The locator index was regenerated with
AGI_GAME_DIR=games/SQ1.22 python3 -B tools/logic_playthrough_index.py, and all
101 readable logics were disassembled. Every positive and negative v3
mutation was traced through its enclosing branches and jumps; logic 104’s 202
assignment supplied the declared maximum.
WORDS.TOK decoding resolved accepted parser synonym groups, while OBJECT
decoding supplied inventory numbers. Visual and priority/control channels were
rendered for pictures 1 through 71 under build/playthrough/sq122/media/.
Movement conclusions use the control channel plus room-logic rectangle,
horizon, priority, and ignore-block operations. The control renders were
qualitative movement evidence; exact score predicates came from logic code.
The late static pass resolved three important non-parser transitions. Logic 65
maps walking rectangles to keypad values, so 6858 is entered by crossing digit
regions 6, 8, 5, and 8 and then the ENTER region. Logic 53 requires the player
width in x 60..74 with baseline y 107..111, an open washer state v72=3, and
no existing disguise before its five-point laundry branch. Slot logics 113-115
subtract the selected wager, choose result classes with random operations,
apply wager-scaled payouts, and saturate a winning total at 250. These facts
are represented by coordinate edges and retry loops in
docs/src/games/sq1_22_success_path.json.
The selected route contains 45 score nodes totaling 202: 61 Arcada, 49 Kerona, 43 Ulence Flats, and 49 Deltaur. Alternative positive sites for the combined Orat/spider resolution, discounted/credit ship path, second vent branch, and roaming-guard shot are excluded. Dynamic confirmation remains outstanding; no QEMU observation was used to fill a static-evidence gap.
2026-07-12: SQ1.22 persistent interpreter controller
Goal: replace timing-sensitive manual QEMU play with a persistent semantic controller that pauses the original interpreter at cycle boundaries and exposes coherent state without modifying the private game input.
Only local evidence was used. tools/interpreter_controller.py validates the
decoded SQ1.22 executable SHA-256 as
97dbc528ff4588b424d8c4e43035d619588c0c6b4376cc1ddea1fb83cea67656.
Focused ndisasm reads of the local decoded image established cycle image
0x015b, shared string-editor loop/call sites 0x0df2/0x0df5 with return
0x0df8, and modal loop/call sites 0x1d1b/0x1d22 with return 0x1d25.
The profile uses the separately mapped 2.917 data locations rather than
assuming the 2.936 offsets.
The disposable session disk was produced with:
python3 -B tools/interpreter_controller.py prepare \
--base-image build/freedos/freedos.img \
--game-dir games/SQ1.22 --dos-game-dir SQ122 \
--raw-output build/interpreter-controller/session/sq122.raw \
--output build/interpreter-controller/session/sq122.qcow2
The controller launched QEMU with QMP and GDB Unix sockets, -S, the generated
INT-43h-compatible VGA ROM, and -display cocoa,zoom-to-fit=on. Runtime
signature scanning found physical image base 0x63a0; stopped registers gave
DS 0x102f. A first state read decoded 18 object records. The visual and
priority endpoints read the live combined logical-buffer segment and produced
160-by-168 PPM images.
Initial input tests found two harness problems. QMP rejects key transitions while the VM is stopped, so a key press must be sent after debugger resume. An atomic press/release pair was not observed by the title logic because both transitions arrived before cycle input sampling; a semantic tap must keep the key down through one stop and release it through the next. More importantly, QEMU’s real-mode GDB path honored the cycle breakpoint but not simultaneous UI breakpoints. Reinstalling the set after a debugger single-step did not change that result.
A targeted run with only the string hook active stopped at image 0x0df2,
proving the static hook was correct. Interrupting a blocked all-hooks run gave
image-relative IP 0x4523; the aligned stack included the call chain ending in
return 0x0df8, again proving execution was inside the shared editor. This
isolated the failure as an emulator debugger limitation rather than a wrong
interpreter model.
The final controller keeps one breakpoint active. It normally selects cycle
0x015b. If a cycle does not return, it interrupts and reads 256 stack bytes;
aligned return 0x0df8 selects the string hook, while 0x1d25 selects the
modal hook. Accepting/dismissing switches back to the cycle hook. The hooks and
all mutations are runtime debugger state against the disposable VM; no file in
games/SQ1.22 changed.
Live confirmation followed this semantic chain:
- Stop at cycle 1 in room 67.
- Press Enter, interrupt the blocking cycle, and classify ordinary
prompt_string_to_slotinput in logic 69 from return0x0df8. - Submit
rogerone transition at a time and return to cycle 3 in room 2 with previous room 69. - Submit a normal
lookcommand, classify modal return0x1d25, and confirm one red-border/white-interior screenshot box plus the interpreter window-active word. - Dismiss with Enter, return to cycle 13, and confirm both modal indicators clear.
Conclusion: persistent cycle stepping, shared string input, modal interaction, state reads, logical-buffer rendering, and independent dialog detection now work together against the real SQ1.22 interpreter. The one-breakpoint limit is documented as a QEMU harness constraint and is not AGI behavior.
SQ1.22 checkpoint hook-state correction (2026-07-13)
During the dynamic completion playthrough, checkpoint score8_keycard was
created at the ordinary cycle hook. Roger later entered a fatal room-2 modal,
which made the controller select the modal hook. Loading the earlier QEMU
checkpoint restored the interpreter CPU/memory state but left the controller’s
live GDB hook selection associated with the later modal interaction. Subsequent
movement requests therefore failed to reach valid semantic stops and were
interrupted at image offset 0xe256.
This was a harness-state defect, not observed game behavior. Controller checkpoints now record their active semantic breakpoint reasons together with held and pending keys. Restore reinstalls the saved reason before reading the restored state or accepting another mutation. A focused unit test confirms that a saved cycle reason is selected and the transaction cache is cleared on restore.
SQ1.22 deterministic controller RNG patch (2026-07-13)
The completion playthrough needs reproducible game-visible random outcomes.
Static inspection of the locally decoded SQ1.22/2.917 image found the shared
generator at image 0x70f9. Its state is word DS:0x1707. When that word is
zero, image 0x7108..0x710f executes bytes
b4 00 cd 1a 89 16 07 17, reading BIOS ticks through interrupt 1a and
storing DX as the seed. The generator then advances the state with multiplier
0x7c4d and increment one and returns the XOR of the state bytes.
Direct calls to this generator occur at image 0x0c0b, 0x3f8e, 0x3fa8,
and 0x4f71. The surrounding reductions identify approach-motion recovery,
random-motion countdown, random direction, and the script action
random_range_to_var. Thus the shared state covers the original engine’s four
source-backed game-randomness paths. The image contains two other interrupt
1a calls at 0x4137 and 0x414c; source inspection shows that they bound a
startup display-adapter test and never feed DS:0x1707 or a game random result.
tools/interpreter_controller.py prepare now validates the pristine decoded
hash and exact seed bytes, then changes only the disposable generated AGI
copy. It replaces the eight bytes above with
ba ed 5e 90 89 16 07 17: load fixed seed 0x5eed, one padding nop, and
store it to the same state word. The original zero-state guard, generator
transition, and output mixing remain unchanged. The XOR transform was reapplied
to create the DOS payload; no file under games/SQ1.22 was modified.
Runtime discovery reads the loaded patch bytes and rejects an older play disk
instead of silently using BIOS time. GET /v1/profile reports fixed-seed mode
and seed 24301. A focused unit test verifies that no byte outside the eight-byte
seed sequence changes. The canonical disposable disk was rebuilt with:
python3 -B tools/interpreter_controller.py prepare \
--base-image build/freedos/freedos.img \
--game-dir games/SQ1.22 --dos-game-dir SQ122 \
--raw-output build/interpreter-controller/session/sq122.raw \
--output build/interpreter-controller/session/sq122.qcow2
Reading C:\\SQ122\\AGI back from the generated raw disk, applying the local
loader transform, and checking image 0x7108 produced
baed5e9089160717, exactly matching the expected patch. The focused controller
suite passed 38 tests.
Cold-build reproducibility was checked independently of the existing build/
tree. A new temporary directory under /tmp was used for an empty download
cache, a freshly downloaded and verified FreeDOS archive, a newly formatted
64 MiB base image, and new raw/qcow2 controller outputs. Running the ordinary
controller prepare command against those fresh paths and reading the DOS
AGI back again produced the same baed5e9089160717 bytes at image 0x7108.
Thus deleting build/ removes only generated artifacts: running
tools/setup_freedos_image.py --force followed by the documented controller
prepare command recreates the deterministic interpreter patch from tracked
tool code and immutable games/SQ1.22 input.
SQ1.22 live playthrough: room 1 corrections (2026-07-13)
A fresh fixed-seed controller run disproved two assumptions in the static
success path. After the room-69 name editor returned to room 2, the automatic
alarm dialog came from logic 94. Entering room 1 left f53 clear and the
scientist countdown inactive. Logic 1 sets f53 only on a room-1 exit. A
bounded room 1 -> 2 -> 1 loop therefore proved necessary: the exit set f53,
and the return initialized v32=250 (observed as 247 at the first stopped
state after room setup).
At v32=135, logic 1 opened the scientist-entry dialog, then the collapse
dialog and reached v51=2, v34=6. Waiting alone could not initialize
v33; its only relevant assignment is at logic-1 offset 0x04a8, after the
LOOK/EXAMINE scientist branch and its wound-description modal. A failed
TALK TO SCIENTIST experiment produced message 56 and left v33=0, proving
that the talk branch is not the trigger. LOOK AT SCIENTIST produced message
10; dismissing it advanced to v51=4 and a decreasing v33. When v33
reached one, dismissing messages 11 and 12 changed score v3 from 0 to 2.
At console position (82,108), LOOK AT SCREEN entered the shared string
editor. Submitting ASTRAL BODY set v50=1, v35=4; a bounded 205-cycle
wait reached v50=2 and f35=true. GET CARTRIDGE changed score from 2 to
7 and inventory object 1 from room marker 0 to carried marker 0xff. The
coherent post-dialog state at controller cycle 943 was saved as checkpoint
score7_cartridge and milestone capture sequence 3.
SQ1.22 live playthrough: Arcada routing and persistence (2026-07-13)
All local walking in this run used the controller’s breadth-first planner over the live priority channel. Room transitions, elevators, and airlock doors were separate guarded stages because the priority image alone does not describe those dynamic transitions.
The route from room 2’s upper doorway to the room-3 keycard is not a direct
crossing of room 3. The dynamically confirmed path is room 3 upper-left,
room-3 elevator down, room 3 lower-right, room 4 lower-left, room-4 elevator
up, then left into room 3 upper-right. GET KEYCARD at (152,67) changed the
score from 7 to 8 and carried inventory object 5. Room 3’s elevator stops near
baseline 67 and 151; its transfer requires a separate direction selection
through the open doorway rather than treating the whole shaft as one priority
path.
The common room-encounter logic also invalidated an unguarded route through
room 7. On entry it can set v67=1 and a random countdown in v138; the safe
hiding strip is only x 98..149, y 121..131. The fixed-seed run observed three
armed entries (v64 81, 57, and 97) before a fourth entry produced v64=34
and left v67=0. Leaving immediately for room 6 and re-entering on an armed
roll avoided the fatal projectile sequence without guessing at movement.
In room 6, PRESS OPEN BAY DOOR set v52=1 and raised the score to 10.
Room 7 required the dynamic elevator sequence after INSERT KEYCARD: approach
the reader, wait for its door state to reach v30=1, then cross its right/up
threshold into room 9. The insertion raised the score to 12.
Live room-9 behavior reversed the closet labels in the static guide. PRESS LEFT BUTTON opened the gadget closet (v70=3), while PRESS RIGHT BUTTON
opened the suit closet (v69=3). The GET GADGET predicate uses full ego
width in x 40..71, so left coordinate 71 is too far right for the seven-pixel
ego; x 61 succeeded and raised the score to 14. The suit pickup at x 79 raised
the score to 16 and set variable v81=1. This variable is distinct from flag
f81, which later records AutoNav.
The room-9 airlock required PRESS AIRLOCK BUTTON, a priority-planned walk to
the open doorway, and a separate leftward crossing into room 8. In room 8,
PRESS PLATFORM BUTTON exactly once set f54, left v52=1, and raised the
score to 17. After the platform animation and its modal, a newly planned path
to (53,101) reached the pod; ENTER SHIP switched to room 10.
Built-in F5/F7 persistence was tested before the long route. Slot 1 was first
saved as Score 7 cartridge. F7 restored room 3, score 7, position (73,151)
and the expected inventory after the controller handled the directory editor,
slot selector, and confirmation as distinct semantic stops. F5 later
overwrote slot 1 as Score 19 prelaunch. The save/restore selector and final
confirmation are host-time input loops not covered by the normal cycle hook;
the run temporarily selected the cycle breakpoint again after the confirming
Enter and then reconciled held/released keys.
SQ1.22 live playthrough: launch and first Kerona state (2026-07-13)
Room-10 command effects were verified independently. CLOSE DOOR set f155,
FASTEN BELT set f44, PRESS POWER set f188, and PRESS AUTONAV set
f81 and raised the score from 17 to 19. Input must be reconciled after a
modal or animation before submitting the next command: the first throttle
attempt began while input was still settling and only the suffix hrottle
reached the prompt. Submitting that partial text produced the expected parser
error and no state change. After dismissal and key reconciliation, PULL THROTTLE set f80 and began the launch.
The observed automatic sequence is more detailed than the static route: room 10 -> room 8 (pod exits the bay) -> room 12 -> room 10 (in-flight pod) -> room 13 -> room 30 (landing animation) -> room 14. Logic 12 raised the score from 19 to 34 after its animation, then displayed the narrow-escape message. The in-flight room-10 logic waited 225 countdown cycles before its monitor message, then used a 27-cycle transition and 150-cycle exterior interval before room 13. These waits were bounded from the local disassembly rather than polling one cycle at a time.
The first room-13 visit did not award 25 points. Logic 13’s 25-point action
is guarded by previous room 37, whereas the first approach arrived from room
10. The live score therefore remained 34 through rooms 13 and 30. Room 14’s
GET SURVIVAL KIT raised it to 36 and carried object 11. UNBUCKLE BELT set
f31, and EXIT returned to room 30.
The global OPEN SURVIVAL KIT branch was then verified outside the pod. It
cleared object 11, carried object 12 (Dehydrated Water), object 19 (Xenon Army
Knife), and the opened-kit marker object 22. A live-priority plan moved the ego
from (40,106) to (40,111) so its full baseline footprint satisfied room
30’s glass rectangle. GET GLASS carried object 6 and raised the score from 36
to 39. Thus the old guide’s claimed pre-Kerona score 61 is 25 points too high;
that conditional award belongs to the later room-37 return path.
2026-07-15: XMAS.230 / AGI 2.230 profile
Goal: identify the previously unobserved interpreter in games/XMAS.230,
compare it with the neighboring local 2.089 and 2.272 builds without external
AGI material, and promote every source-backed valid full-EGA difference as a
new behavioral profile.
Read-only input inventory
The investigation began with git status --short, focused file listings, MZ
inspection, hashes, and local string extraction. No file under games/ was
modified. AGIDATA.OVL contains Version 2.230; AGI.EXE is an MZ executable
whose loaded image is 33,792 bytes. Selected hashes are:
AGI.EXE:875547deb3d83b7ed2dd7578d357f43b6a5843a9ac8de1332245df9f60ff4570;AGIDATA.OVL:33a327736deb611df48d0d3232c1958b00af1b1cae5fba45ed3aadf88871af8a;EGA_GRAF.OVL:cc0402c7dcea0c9b9e51262a8d455abbe0b3e86c0a39dd8f7ecb3e57ffe8ec2c;OBJECT:10701142dc86bb5b19fadf812e4096ed5206ea51b10f826786872a9d07ee2477; andWORDS.TOK:4f816ed3917dc129e84a7620afd69448893d56ab8a47f99ebf10874da8294710.
OBJECT and WORDS.TOK are byte-identical to the local XMAS 2.272 copies.
The split directories and VOL.0 are not byte-identical. The selected 2.230
copy contains only VOL.0, although present directory entries also name
volumes 1 and 2. The read-only census therefore reports 15 readable logics, 9
pictures, 100 views, and 10 sounds, all direct v2 records, plus missing-file
errors for resources on the absent volumes. Those missing components are a
selected-package limitation, not a new resource transform.
Commands and generated reports used for this pass included:
python3 -B tools/game_census.py --game-dir games/XMAS.230 \
--game-dir games/SQ1 --game-dir games/XMAS \
--output build/cross-version/xmas_2230_selected_census.json
python3 -B tools/compare_interpreter_tables.py \
--left-label XMAS-2.230 --left-game-dir games/XMAS.230 \
--left-exe games/XMAS.230/AGI.EXE \
--right-label SQ1-2.089 --right-game-dir games/SQ1 \
--right-exe games/SQ1/SQ.EXE \
--output build/cross-version/xmas_2230_sq1_2089_tables.md
python3 -B tools/compare_interpreter_tables.py \
--left-label XMAS-2.230 --left-game-dir games/XMAS.230 \
--left-exe games/XMAS.230/AGI.EXE \
--right-label XMAS-2.272 --right-game-dir games/XMAS \
--right-exe games/XMAS/AGI.EXE \
--output build/cross-version/xmas_2230_xmas_2272_tables.md
python3 -B tools/match_interpreter_roles.py \
--reference-label XMAS-2.272 --reference-game-dir games/XMAS \
--reference-exe games/XMAS/AGI.EXE \
--target-label XMAS-2.230 --target-game-dir games/XMAS.230 \
--target-exe games/XMAS.230/AGI.EXE --sq2-subsystems \
--output build/cross-version/xmas_2230_early_role_matches.md
Loaded-image slices were written only under build/cross-version/ and
disassembled with ndisasm -b 16 -o 0. Resource payloads were read through
the local agi_resources decoder. Focused Python comparisons enumerated view
loop and cel offsets, compared corresponding 2.230/2.272 payload bytes, and
normalized all seven EGA overlay entry routines.
Dispatch-table boundary and hybrid profile
The 2.230 action table is at data offset 0x03e7 and contains 155 entries
0x00..0x9a. Its condition table is at 0x0673 and contains the common 19
entries 0x00..0x12. The 0x20 bytes between those table geometries match
the later early-v2 trailer, whereas SQ1 2.089 uses a 0x26-byte trailer.
Against SQ1 2.089, all 155 action parser contracts match; normalized action
entries differ only at 0x25, 0x26, 0x31, 0x3c, 0x80, and 0x81.
Against XMAS 2.272, the 155 shared entries have one parser-contract difference,
the operand on 0x86, and seven normalized entry differences: 0x31, 0x4d,
0x4e, 0x77, 0x81, 0x86, and 0x8b. All 19 2.230/2.272 condition
handlers match after relocation. The six unshared 2.272 actions are
0x9b..0xa0.
Manual source inspection classifies the portable choices:
0x86is the zero-operand unconditional exit used by 2.089. Actions0x9band later do not exist.- Position actions
0x25/0x26at0x70fa/0x7137match 2.272: current and previous coordinates are written together without first erasing drawn state. - Action
0x3cand list roles0x6081,0x60bb, and0x60d8restore, build/sort, and refresh both drawing-key partitions as in 2.272. - Motion-clear actions
0x4d/0x4eat0x64e7/0x6520are exact relocated SQ1 matches. They retain autonomous mode;0x4dclears direction and0x4echanges only object-0 coupling/navigation state. - The 2.230 word-sequence predicate
0x0851matches 2.272 and implements the0x270ftail terminator. String equality is also a relocated match. - Action
0x77omits a conditional alternate-display helper added in 2.272; its ordinary full-EGA input-line clear is unchanged. The differing restart, successful view-preview, and joystick entries reduce to cleanup, temporary allocation/diagnostic, and hardware paths without another valid full-EGA output rule.
Packed view-loop orientation
The unique valid-data difference is the 2.230 view-loop header. Loop selector
0x350d reads the first loop byte, masks it with 0x0f, and stores that low
nibble as the object’s cel count. If header bit 0x80 is set, it compares bits
0x30 with the selected loop. On a change, bit 0x40 requests helper
0x50af, which iterates header & 0x0f cels and mirrors every encoded row in
place. The selector then replaces header bits 0x30 with the new orientation.
The header and shared row streams are therefore mutable loaded-resource state.
Action 0x31 at image 0x36e2 independently reads the selected loop header,
executes and ax,0x000f, subtracts one, and stores the highest valid cel index.
The neighboring 2.089 and 2.272 handlers subtract one from the unmasked byte.
Direct selected-resource comparison confirms the intended format migration:
- views 10, 11, and 14 have two directory loops sharing one loop structure
whose 2.230 header is
0xc4; and - views 16, 17, and 18 similarly use header
0xc6.
The corresponding 2.272 payloads have ordinary headers 0x04/0x06 and set
bit 0x80 in each cel’s control byte instead. The 2.230 cels leave that bit
clear. Resource lengths, loop/cel offsets, dimensions, and row streams remain
aligned, so the orientation marker moved from the loop header to the cel
control byte between these two builds.
Other subsystem classification
Exact normalized role matches relocate the 2.272 frame timer (0x049a),
movement core (0x1317), parser (0x1759), save/restore
(0x2447/0x2279), acknowledgement-only inventory (0x2dcf), collision
(0x3f0f), wrapped distance (0x3fe5), retained-state show-picture
(0x436e), picture decode/scanner/line/fill roles, control acceptance,
placement, dirty rectangles, deferred target motion, and direct position
actions. Manual call-site inspection resolves the short list wrappers as
0x6081 restore, 0x60bb rebuild/sort, and 0x60d8 refresh.
Sound start 0x7478 and tick 0x74da share the early one-versus-four-channel
scheduler. Output 0x7598 is an exact relocated SQ1 match: it emits both tone
bytes and applies only the device-2 low-nibble-plus-three rule, without 2.272’s
global whole-byte addition and signed clamp.
The picture scanner 0x5baa accepts only 0xf0..0xf8. All seven EGA overlay
entry routines normalize exactly against both 2.089 and 2.272; their byte
differences relocate interpreter globals and calls rather than changing the
full-EGA output contract.
The five-block save writer matches 2.272. Startup role 0x0e50 reads the third
plain OBJECT header byte at 0x0e82, increments the local record count at
0x0e9d, and multiplies it by the 0x2b record size at 0x0eaa. The
normalized 2.272 role at 0x0e7c has the same sequence. Thus header byte
0x11 defines 18 runtime object records rather than merely suggesting that
interpretation from the save-block constant. The metadata block is 0x000f.
Readable logic 0 calls action 0x8e(200), so selected block 4 contains 200
replay pairs and is 0x0190 bytes. The resulting fixed lengths are 0x03db,
0x0306, 0x000f, and 0x0190, followed by the variable logic-resume
block. Canonical pristine values for reserved early block-1 bytes remain
unknown, as for the neighboring early profiles.
Conclusion: AGI 2.230 is neither an alias of 2.089 nor 2.272. It is a source-backed hybrid with a distinct packed view-loop format, so it is promoted as a separate valid full-EGA behavioral profile.