Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Flash emulator comparison

TI-84 Plus OS 2.55MP — pinned TilEm, Wabbitemu, MAME, and jsTIfied Flash behavior beside the ROM.

The four inspected emulators agree on the command bytes and top-boot sector boundaries used by the ROM. They differ at the points most useful for negative tests: illegal bit transitions, completion timing, status reads, and ASIC access control. [standard]

BehaviorTilEm f56ad63Wabbitemu 48c2dc0MAME 0.287jsTIfied 20170706a
Unlock addresseslow 12 bits 0xAAA, 0x555low 12 bits 0xAAA, 0x555accepts several AMD address conventions, including the ROM’s low-12-bit formlow 12 bits 0xAAA, 0x555
Byte mutationold &= requestedold &= requestedold = requestedold &= requested
Successful program7 µs real-time timer; 42 clocks at the 6 MHz reset speedimmediate array dataimmediate array dataimmediate array data
Illegal 0→1 requesterror stateone transient error readwrites the requested one bitleaves the zero bit unchanged without an error state
Sector erase50 µs command window, then 200 ms erase timer; 300 and 1,200,000 clocks at 6 MHzimmediateimmediate data mutation followed by a timerimmediate; protected sector-table entries are skipped
Autoselectincompletemodeled AMD manufacturer 0x01, device 0xDAIDs at offsets 0/1; no compatible protection readmanufacturer 0xC2 and device 0xDA; each recognized read exits ID mode
Chip erasewritable sectors only; final status follows the last sectorimmediate full-array fill, including bootimmediate full-array fill; stale/default busy rangeimmediate erase of unprotected sector-table entries
Fast programcommand flow present; fidelity unresolvedimplemented for TI-84 Plus Flash version 3entry accepted, but A0 excludes the AMD maker IDabsent
Erase suspend/resumeabsentabsentabsentabsent
CFI queryabsentabsentabsent for AMD_29F800Tabsent
Sector-protection autoselect readunavailable with missing autoselectoffset 4 always returns zerono data-sheet-compatible protection readabsent
ASIC write gateprotected-byte sequence, lock, and sector groupsprivileged-page port-0x14 gate and boot-page flagsno effective Flash-write gateprotected-byte port-0x14 gate; sector flag affects erase but not program

The table combines source results with guarded runtime checks described below. MAME marks the complete TI-84 Plus driver MACHINE_NOT_WORKING. None of the divergences resolves physical behavior. [standard] for the source models; [confirmed] for the pinned runtime observations.

The emulator autoselect rows differ from the photographed Fujitsu part’s data sheet, which specifies 0x04/0xDA. Matching device code 0xDA establishes a compatible top-boot command family; manufacturer 0x01 does not identify the photographed package. [standard]

TilEm behavior and limits

TilEm implements the same command progression used by the ROM: AA, 55, then A0 for program, or 80, AA, 55, 30 for sector erase. It matches command addresses by physical low 12 bits 0xAAA and 0x555. [standard]

Its program operation computes stored_byte &= requested_byte. A requested 0→1 transition leaves the zero bit unchanged and enters the emulator’s error state. During program busy, DQ7 is complemented and DQ6 toggles. The delay argument is 7 µs, not seven CPU cycles. TilEm’s real-time scheduler converts it to 42 clocks at the 6 MHz reset speed. [standard]

During erase, DQ6 and DQ2 toggle. DQ3 distinguishes a 50 µs command window from the modeled 200 ms erase operation. Those deadlines are 300 and 1,200,000 clocks at 6 MHz. The ROM’s erase worker polls DQ7 and DQ5 rather than those toggle bits. [standard]

TilEm’s source comment lists fast program among unfinished work, but the state machine implements part of it. AA 55 20 enters fast mode, A0 selects one program operation, and the next write calls the ordinary byte-program helper. The state then returns to fast mode. 90, followed by F0, exits. This is an implemented command flow with unresolved hardware fidelity. Autoselect logs that it is unimplemented; erase suspend and CFI have no states. [standard]

TilEm’s chip-erase path iterates over the sector table and calls erase only for sectors accepted by its protection model. On a TI-84 Plus with default override group zero, this skips physical 0xB00000xBFFFF and 0xFC0000xFFFFF. Override group one admits those sectors. Each sector erase resets the recorded program address and timer, so the final busy status describes only the last writable sector. This differs from a single physical chip-erase operation. [standard]

A guarded direct-core run exercises these states through tilem_flash_write_byte and tilem_flash_read_byte. It seeds synthetic memory and enables TilEm’s delay model. The timer deadlines come from the scheduler; the fixture invokes the registered Flash callback directly to cross each deadline without executing TI-OS. [confirmed]

Program caseState after writeBusy readsState after callbackLater reads
legal FF → 50array read, program busy, 42-clock deadline80, C0array read, idle50
illegal 50 → D0error, program busy, 42-clock deadline00, 40error, idle20, 60 repeatedly

The illegal request stores 0x50. Program-busy status takes priority over the error state until the callback runs. The persistent error reads then set DQ5 and toggle DQ6. A following F0 write returns to array mode and reads 0x50. This differs from Wabbitemu’s one-read error lifetime. [confirmed]

The sector case seeds all 65,536 bytes at physical 0x200000x2FFFF to zero. The command changes all of them to 0xFF immediately and changes no byte outside that range. Erase-window reads are 00, 44; erase-busy reads after the first callback are 08, 4C; the second callback exposes array byte 0xFF. [confirmed]

Chip-erase overrideChanged bytesBytes left non-FFLast program address
group 0966,65681,9200xFA000
group 11,048,57600xFC000

Group 0 leaves 0xB00000xBFFFF and 0xFC0000xFFFFF unchanged. Both runs finish in array state with one 300-clock erase-window deadline for the last admitted sector. The native matrix also confirms the partial fast-program flow and its 90 F0 exit. Autoselect logs an unimplemented-command warning; CFI query does nothing; B0 in the erase-command window logs an undefined command and returns to array state without changing memory. [confirmed]

The native binary SHA-256 is 31f8e15a348d15f876f103b8452340484893987e458023fd913280365db5c51d. The build requires clean TilEm commit f56ad637d0524ee841dd381be6ecbaf5b8975600 and Git tree 58316afe35d69e69353f0f743698144153051d4a. These results describe the pinned emulator core, not the retail ROM worker or physical Flash. Build and run commands are under “Flash command and status matrix” in the repository’s tools/notes/emulator-probes.md. [confirmed]

TilEm’s full calculator reset clears the Flash unlock gate, command state, and busy flag. It retains the last program address and byte, toggle state, protection-override group, and delay-emulation flags. An execution-protection exception reaches this reset only after the forbidden opcode completes. A guarded direct-core fixture executes LD (0x8000),A from restricted Flash page 08; its RAM write of 0x5A survives the reset. This ordering is TilEm behavior, not evidence that the ASIC executes a denied instruction. [standard] for source; [confirmed] for the pinned run. See TilEm reset and exception scope.

Wabbitemu behavior and limits

Wabbitemu recognizes byte program, sector erase, chip erase, autoselect, and fast-program commands. It applies program data with stored &= requested. Successful programming returns to array mode immediately. [standard]

An illegal 0→1 request sets an error flag. The next read returns complemented DQ7, set DQ5, and its current DQ6 toggle bit. That same read clears the error flag, so later reads return array data. This one-read lifetime is Wabbitemu behavior, not the hardware data-sheet polling contract. The ROM worker tests DQ7 and DQ5 in that same first byte. [standard] for Wabbitemu source; [confirmed] for the ROM worker.

Wabbitemu’s CPU_reset does not reset the Flash command step, error flag, toggle bit, write byte, delay, lock, or bounds. Its opcode-fetch path separately ends most non-read command states after an execution violation. A seeded FLASH_PROGRAM violation therefore returns to array mode before executing one boot instruction. A seeded FLASH_ERROR violation retains that command step; the boot instruction’s immediate-byte read consumes status 0xE0 and clears only the error flag. Both cases finish the same CPU_step at PC=0x0002. [standard] for the source paths; [confirmed] for the guarded native run. See Wabbitemu reset scope.

A guarded native run exercises seven byte pairs through Wabbitemu’s CPU_mem_write and CPU_mem_read entry points. Each case issues AA 55 A0, programs page 08 offset 0x0100, and reads the target twice. The harness unlocks the in-memory ASIC gate directly and replaces the target’s initial byte before the command. It does not execute the retail ROM worker. [confirmed] for the pinned Wabbitemu run.

InitialRequestedInitial DQ6StoredFirst readSecond read
FF5000505050
504000404040
800000000000
50D000502050
50D040506050
008000002000
00010000A000

The first three requests are legal and expose array data immediately. The four illegal requests set the error flag after programming initial & requested. Their first read clears that flag and flips DQ6. Their second read exposes the stored byte. All seven cases return to FLASH_READ; the initialized adapter adds zero T-states for these accesses, so this run provides no timing evidence. [confirmed] for the pinned Wabbitemu run.

The native binary SHA-256 is 67077107b604e97cfb751cadf4392dca53d00d5bbc417b2f48c422eebb9ac560. It uses pinned commit 48c2dc0e6d1d87bb5cf9611efbeb0d048b19c422 and the exact OS 2.55MP image. The guarded CLI checks every native field against the fixed launch expectations and the independent Python source model before writing its manifest.

The pinned Wabbitemu source and the ROM worker produce three paths. The first ROM read consumes Wabbitemu’s error status. The worker tests both DQ7 and DQ5 in that byte. Since Wabbitemu sets DQ5, every illegal request proceeds directly to one final array read. This table models emulator source combined with the byte-confirmed ROM poll logic. It does not describe physical Flash behavior. [standard] for Wabbitemu; [confirmed] for the ROM poll logic.

Program requestFinal stored DQ7ROM result
legalmatches requested DQ7succeeds on the first array read
illegal 0→1 outside DQ7matches requested DQ7succeeds after the final read even though lower requested bits remain zero
illegal DQ7 0→1differs from requested DQ7fails after the final read

Exhaustive enumeration of all 65,536 old/requested byte pairs gives 49,152 successes and 16,384 failures. No pair is nonterminating under this composition. The successes contain 6,561 legal pairs and 42,591 illegal requests that the ROM reports as successful. These exhaustive counts are deterministic consequences of the pinned source model, not an exhaustive Wabbitemu run or hardware observation. [standard]

A second guarded mode boots the exact retail ROM, injects a four-byte rst 28h/8087h harness into RAM page 1, and sets the documented _WriteFlashUnsafe ABI registers. The bcall copies the 124 bytes beginning at flash_program_worker_code to ramCode and executes them. The harness directly opens Wabbitemu’s in-memory ASIC gate, so it does not test the protected port-0x14 unlock sequence or an OS/UI caller. [confirmed] for the pinned native run.

InitialRequestedInitial DQ6Worker readsStoredResultAF
FF50005050success0044
000100A0, 0000success0044
20A00020, 2020failure3F2C
50D00020, 5050failure3F2C
50D04060, 5050failure3F2C

All five cases enter the copied worker once and issue one program write. The legal request takes the success reset at ram:816B. The illegal lower-bit request also takes that path after its final DQ7 read, despite leaving bit 0 clear. Both illegal DQ7 requests take the failure reset at ram:8175, regardless of stored DQ5. DQ6 changes the first status byte but not the return path. [confirmed] for the pinned native run.

The cold-recovery runner exercises a separate retail path without opening the gate through emulator state. Startup at 00:0D73 calls the bjump stub at 00:3EEB, which resolves to 3D:6098. The bytes at 3D:609C3D:60A8 write 1 to port 0x14; Wabbitemu changes flash_locked from true to false at the OUT at 3D:60A6. The wrapper calls 00:2BAD at 3D:6101 to enter gc_check_interrupted at 3C:7BC7. Its return path jumps to the lock sequence at 3D:5CE6, and the OUT at 3D:5CEF changes flash_locked from false to true. The static gate scanner classifies both sequences and finds no unclassified port-0x14 candidate on page 3D. [confirmed]

All six reconstructed recovery images take that protected unlock → recovery → relock path. The observer identifies the public block-program worker by comparing all 124 bytes at ramCode with flash_program_worker_code. Each _WriteFlashUnsafe visit reaches one matching worker entry and one success tail at ram:816B; no run reaches the failure tail at ram:8175. [confirmed]

Input phase_WriteFlashUnsafe / worker entriesData writes at ramCode + 0x49 (ram:8149)_EraseFlash entries
0xFF33483
0xFE32473
0xFC20204
0xF819193
0xF030465,5603
0xE017172

These counts cover the exact public block-program worker. Other internal RAM workers can issue additional Flash commands during certificate rebuilding. The run is Wabbitemu evidence for the retail control path, not physical ASIC or Flash evidence. The observer binary SHA-256 is 242ca0d3ecab861ce1048285258d1e13ebc18a175bccf016397692fbe0f150db. It uses pinned Wabbitemu commit 48c2dc0e6d1d87bb5cf9611efbeb0d048b19c422. [confirmed]

Sector erase changes the complete sector to 0xFF before the next instruction and exposes no erase-busy interval. Its sector arithmetic matches the physical 64, 32, 8, 8, and 16 KiB top-boot layout. The two 8 KiB sectors are the halves of page 3E. [standard]

Wabbitemu also implements chip erase by filling the complete Flash array with 0xFF, including page 3F, without consulting its per-write boot-page gates. Its TI-84 Plus profile sets Flash version 3, which enables AA 55 20, repeated A0 program operations, and 90 F0 exit. It has no erase-suspend or CFI state. Autoselect offset 4 always returns zero, so it reports every sector as unprotected. [standard]

A guarded native command-family run checks these source claims through CPU_mem_write and CPU_mem_read. The adapter loads the exact OS 2.55MP image, opens Wabbitemu’s in-memory gate, and keeps every mutation in the allocated Flash array. It does not execute a retail-ROM Flash routine. [confirmed] for the pinned Wabbitemu run.

Command pathNative observation
AutoselectAA 55 90 enters FLASH_AUTOSELECT; offsets 0, 2, and 4 return 01, DA, and 00
Array resetF0 returns autoselect and a partial AA sequence to FLASH_READ
Fast programAA 55 20 enters FLASH_FASTMODE; two A0 operations store F0 & 50 = 50 and AA & A0 = A0, returning to fast mode after each
Fast-mode exit90 enters FLASH_FASTMODE_EXIT; F0 returns to FLASH_READ
Sector eraseAA 55 80 AA 55 30 changes all 65,536 seeded bytes at physical 0x200000x2FFFF to FF; no byte outside the range changes
Chip eraseAA 55 80 AA 55 10 reduces 322,043 non-FF bytes to zero and changes a seeded byte at physical 0xFFFFF from 00 to FF
CFI query98 from array mode returns to FLASH_READ and changes no byte
Erase suspend/resumeB0 in FLASH_ERASE_55 returns to FLASH_READ; the following 30 also leaves the array unchanged

The sector test seeds the complete 64 KiB sector and two adjacent boundary bytes before issuing the command. The chip test counts the complete 1 MiB array and explicitly seeds its final boot-page byte. The adapter records zero T-states for the direct calls, so the run provides no command-timing evidence. Its binary SHA-256 is 41304b9a760438440f60cbfeca394cd37252c929ef2043e692c0254b8d1cb52d. [confirmed] for the pinned Wabbitemu run.

Wabbitemu accepts a port-0x14 write only while the current Flash page passes its privileged-page predicate. Its source names pages 2F, 3C, 3D, and 3F for the TI-84 Plus path; page 3E does not pass that predicate. A separate write-validity check requires the resulting unlocked state and applies model flags to boot-page writes. This approximates the ASIC gates but does not model TilEm’s byte-fetch recognizer. [standard]

MAME behavior and limits

MAME 0.287 instantiates its generic AMD_29F800T device for the TI-84 Plus. The device has a one-megabyte array, AMD manufacturer ID 0x01, device ID 0xDA, and top-boot sector geometry. [standard]

Its AMD autoselect path returns the configured maker ID at offset 0, device ID at offset 1, and a fixed zero at offset 2. It does not implement the Fujitsu byte-mode offsets 0, 2, and 4, including the sector-protection read at offset 4. It has no CFI query state for AMD_29F800T and no AMD erase-suspend state. The 0xB0 case in this source is a Sanyo-specific bank-select command, not erase suspend. [standard]

Its 8-bit byte-program path assigns stored = requested. It does not apply NOR AND semantics. A request to change a stored zero to one therefore succeeds in MAME, and the first ROM poll reads the assigned byte with matching DQ7. The program path has no timed busy mode or DQ5 failure state. [standard]

Sector erase fills the selected sector with 0xFF immediately, then enters a timed status mode. MAME uses 1,000 ms for a 64 KiB sector, 500 ms for the 32 KiB and 16 KiB sectors, and 250 ms for either 8 KiB sector. In-sector reads alternate 0x4C and 0x08, toggling DQ6 and DQ2 around a base DQ3 value. DQ5 stays clear. When the timer expires, reads return the already-erased array. [standard]

The busy-read range has a separate geometry bug. MAME tests a 64 KiB interval from m_erase_sector even when it erased a 32, 16, or 8 KiB top-boot sector. Erasing the first page-3E half at 0xF8000, for example, returns erase status for every read through 0xFFFFF until the 250 ms timer ends. Only the selected 8 KiB array region is changed. [standard]

Chip erase fills the complete array with 0xFF immediately and starts the generic 16-second AMD_29F800T erase timer. The chip-erase branch does not set m_erase_sector. Busy reads consequently use its stale or initial value and a 64 KiB interval even though the complete array has already changed. [standard]

MAME accepts AA 55 20 and sets its fast-mode flag. Its subsequent 0xA0 handler permits fast programming only for Fujitsu and selected ST maker IDs. The 0x90 fast-exit transition uses the same maker-ID gate. The TI-84 Plus instance uses maker ID 0x01 for AMD, so A0 logs an unknown mode byte and 90 F0 returns to normal reads without clearing the fast-mode flag. MAME therefore has partial, not working, unlock-bypass support for this device configuration. [standard]

The TI driver maps every Flash bank directly to the generic device’s read and write methods. Port 0x14 stores m_flash_unlocked and updates paging, but no memory-write path consults that value. The driver also omits ports 0x220x28. MAME therefore accepts command writes without the protected byte sequence, sector override, or execution-protection state used by the ROM. [standard]

The stored gate is a raw byte rather than a Boolean. A guarded sweep of writes 00 01 02 3F 40 FF makes port 0x02 return C3 C7 CB FF C3 FF, following the driver’s truncated 0xC3 | (value << 2) expression. Port 0x14 itself reads zero. A scheduled soft reset retains write one and consequently returns 0xC7; this is MAME reset behavior, not a physical lock-retention result. [standard]

A separate guarded run maps Flash page 08 into the CPU’s 0x4000 window and issues commands through CPU program space while reading the gate state through I/O port 0x02. A complete program while locked reports C3 and changes the target from FF to 50. A prefix started while locked and completed after an unlock reports C7 and changes it to D0. A prefix started while unlocked and completed after relocking reports C3 and changes it to 20. CPU reads and direct generic-device reads agree after every case. [confirmed]

The saved image differs from the source only at 0x20100 (FF → 20) and has SHA-256 2fd21a6b139a641d40a71a0e68df492e4555e79c6f1cf44858b4dcfd9158bbeb. This CPU/I/O-space result confirms that MAME stores and exposes the port-0x14 state without applying it to mapped Flash writes. It describes MAME 0.287, not the ASIC gate or physical Flash. [confirmed]

A guarded MAME 0.287 run exercises the ti84pv3 machine’s mapped :membank0 Flash interface through Lua. It uses the exact OS 2.55MP image but does not execute TI-OS Flash code. The report oracle checks every field against the pinned source model, and the image oracle compares the complete saved 1 MiB array against the expected command mutations. [confirmed]

Command or readRuntime observation
Autoselectoffsets 0, 1, 2, and 4 return 01, DA, 00, and 00
Byte programFF → 50 stores 50; the illegal 50 → D0 request stores D0
Array reset and CFIF0 after a partial unlock restores array reads; 98 leaves the programmed D0 visible
Unlock bypassAA 55 20 accepts the entry, but its A0 program does not change D0; 90 exposes manufacturer ID 01, and F0 restores array byte D0
8 KiB top-sector erasethe selected 0xF80000xF9FFF array range changes immediately; reads at 0xF8000, 0xFA000, and 0xFC000 expose busy status, while 0xE0000 remains an array read
Timer completionselected and adjacent reads return FF, boot Flash returns 3E, and 0xE0000 returns 9F at frame 20

The saved Flash differs from the source ROM only at 0x20100 (FF → D0), 0xF8000 (00 → FF), and 0xF9FE00xF9FE1 (00 → FF). Its SHA-256 is 1dc4eec678252588df24118e96603b6c80806b8b9ea8e0e12b2169ac6aae3935. The MAME executable SHA-256 is fc5f4aba1aa6eb115d66decad13bb3f5313b9f3be9cff7c785d8d88e3fca0b91. These identities and the retained manifest scope the result to MAME 0.287, not the retail worker or physical Flash. [confirmed]

A separate guarded run seeds each sector boundary and its adjacent probes with 00 before issuing five sector erases. Each command changes only its selected array range. Busy reads cover 64 KiB from the selected start, including bytes past the 32 KiB and 8 KiB sectors. [confirmed]

Selected rangeSource timerCompletion frameOut-of-sector read while busy
0xE00000xEFFFF1,000 ms500xF0000 = 00
0xF00000xF7FFF500 ms750xF8000 = 08
0xF80000xF9FFF250 ms880xFA000 = 08
0xFA0000xFBFFF250 ms1010xFC000 = 08
0xFC0000xFFFFF500 ms1260xFBFFE = 00

The frame deltas are 50, 25, 13, 13, and 25. At 50 frames/s, they match the source timers. The 250 ms cases appear on the next whole frame. Every seeded byte immediately outside the selected array range remains 00 after completion. [confirmed]

Chip erase starts at emulated second 2 after the sector matrix. It immediately changes the complete array to FF, while reads in the last sector’s stale busy range return 4C and 08. The periodic state probe observes array reads at second 18, exactly 16 emulated seconds later. The saved image contains no non-FF byte and has SHA-256 f5fb04aa5b882706b9309e885f19477261336ef76a150c3b4d3489dfac3953ec. [confirmed]

Reproducing the comparison

tools/ti84re/flash/hardware.py contains the photographed-device specification, reported compatible families, sector table, source-modeled program rules, MAME erase status, and the ROM worker’s DQ7/DQ5 decision. The focused CLI separates physical and emulator identities and exposes negative cases without modifying an emulator:

$ python3 -m ti84re.flash.describe_hardware parts
photographed part: Fujitsu MBM29LV800TA-70PFTN
  package marking: 29LV800TA-70PFTN
  board evidence: Datamath March 2004 TI-84 Plus PCB photograph
  data-sheet autoselect: manufacturer=0x04 device=0xDA
  rated byte program: 8 us typical, 300 us maximum
  rated sector erase: 1 s typical, 10 s maximum
reported compatible families: AMIC A29L800A, Fujitsu 29LV800, Spansion S29AL008D, Macronix MX29LV800
$ python3 -m ti84re.flash.describe_hardware program --old 0x00 --data 0xFF
program old=0x00 requested=0xFF
  TilEm: stored=0x00 poll=error state
  Wabbitemu: stored=0x00 poll=one transient error-status read
  MAME: stored=0xFF poll=array data
  Wabbitemu error-read values (DQ6=0/1): 0x20 0x60

The Wabbitemu/ROM composition is available for one pair or as an exhaustive summary. The single-pair model defaults the persistent DQ6 toggle bit to clear; --dq6 selects a set bit. DQ6 changes the first read value but not the ROM’s DQ7/DQ5 decision.

$ python3 -m ti84re.flash.describe_hardware wabbitemu-poll --old 0x50 --data 0xD0
Wabbitemu/ROM old=0x50 requested=0xD0 stored=0x50
  read 0: DQ7/DQ5 poll=0x20 -> need-final-read
  read 1: final DQ7 poll=0x50 -> failure
  outcome: failure
$ python3 -m ti84re.flash.describe_hardware wabbitemu-poll
all byte pairs: 65536
  outcomes: success=49152 failure=16384
  legal successes: 6561
  illegal requests reported successful: 42591
$ python3 -m ti84re.flash.describe_hardware mame-erase 0xF9000 --reads 4
sector 0x0F8000-0x0F9FFF, timer=250 ms
busy reads 0x0F8000-0x0FFFFF
status: 0x4C 0x08 0x4C 0x08

The command-capability matrix and structural ROM scan are available as JSON:

python3 -m ti84re.flash.describe_hardware --json commands
nix develop -c python3 -m ti84re.flash.analyze_rom_commands --json

The guarded MAME runtime probe requires the exact MAME binary hash and writes its command, input identities, report, complete NVRAM image comparison, and captured logs to a new output directory:

mame_flash_parent=$(mktemp -d /tmp/ti84-mame-flash.XXXXXX)
nix shell nixpkgs#mame --command python3 -m ti84re.emulators.mame.run_flash_probe \
  --expected-mame-sha256 \
    fc5f4aba1aa6eb115d66decad13bb3f5313b9f3be9cff7c785d8d88e3fca0b91 \
  --output-dir "$mame_flash_parent/run" --json

The CPU-visible gate probe uses the same guarded runtime and changes the gate between AMD command phases:

mame_gate_parent=$(mktemp -d /tmp/ti84-mame-gate.XXXXXX)
nix shell nixpkgs#mame --command python3 -m ti84re.emulators.mame.run_flash_gate_probe \
  --expected-mame-sha256 \
    fc5f4aba1aa6eb115d66decad13bb3f5313b9f3be9cff7c785d8d88e3fca0b91 \
  --output-dir "$mame_gate_parent/run" --json

The independent erase matrix uses the same guards and output contract:

mame_erase_parent=$(mktemp -d /tmp/ti84-mame-erase.XXXXXX)
nix shell nixpkgs#mame --command python3 -m ti84re.emulators.mame.run_flash_erase_probe \
  --expected-mame-sha256 \
    fc5f4aba1aa6eb115d66decad13bb3f5313b9f3be9cff7c785d8d88e3fca0b91 \
  --output-dir "$mame_erase_parent/run" --json

The parts, geometry, profiles, commands, poll, and wabbitemu-poll subcommands support --json for scripts. tools/ti84re/flash/trace.py imports the same geometry library, so dynamic trace reports and emulator comparisons use one sector definition.