The real VT1200 board has a DUART2 (SCN2681) at 0x0620000 for the keyboard (ch A) and mouse (ch B) with INT2 wired. Map it unconditionally instead of opting in via VT1200_DUART2; the OS's DUART interrupt dispatcher needs a real read-only ISR distinct from the write-only IMR. Verified with frame dumps: with ONLY VT1200_SKIP_LEVEL1=1 the Terminal Manager V2.2 dialog now appears (frame 3000; VRAM md5 73c3c9a1..., identical to the old skip+DUART2 run). This does NOT keep the faithful no-env path: level 0's I/O data path test (L3FDCE470) transmits 0xE1 to DUART2-A and drains the receiver until it has read 10 bytes (SRA RxRDY). The board write-back latch used to alias CSRA/RHRA and faked that (baseline first divergence: R 06200001 pc=3FDCE670 = 0x0099 vs the real chip's 0x000C), so the firmware dropped into the interactive diagnostics menu. No faithful device we have (LK401 sends at most 4 bytes) supplies the 10 bytes; an external TX->RX loopback does not change the transmit count. See OS_BRINGUP_REPORT.md section 8 (DONE_WITH_CONCERNS). The OS keyboard/mouse driver does initialise: skip-phase OS code programs DUART2 (MR1A=0x13, CSRA=0x99, IMR=0x33, ...) and sends FD to the keyboard plus the documented LED/click/bell/auto-repeat commands, and 'T' to the mouse.
23 KiB
VT1200 OS bring-up: is the skip run progressing or stuck?
Date: 2026-10-16. Scope: name the wall the VT1200_SKIP_LEVEL1=1 run hits after
the X11 root weave, with evidence that spans emulated time. ROM untouched
(md5 b46a3e442ccb86134d2356bd70d22fd2).
Verdict: STUCK, and the wall is named. It is not slow — the framebuffer is
byte-identical across millions of emulated cycles, while a single RAM byte
oscillates. The wall is the unmodelled DUART2 (keyboard/mouse, bit
0x0620000): the OS's DUART interrupt dispatcher reads DUART2's read-only
interrupt-status register (ISR) and gets back the value the OS itself wrote to
the write-only interrupt-mask register (IMR) — same chip register address,
0x06200005 — so it services a phantom interrupt that can never clear. Mapping MAME's already-present SCN2681 there clears it, and the
GUI's Terminal Manager dialog box — the reference video's ~0:48 target —
appears.
2026-10-16 update (§8): DUART2 is now modelled by default. The
VT1200_DUART2gate is gone; a bareVT1200_SKIP_LEVEL1=1run now reaches the Terminal Manager (§8.1). This does break the faithful no-env path: level 0's I/O data path test needs received data on DUART2-A that the old board write-back latch faked (§8.2). Read §8 before relying on the "no-env → HOST PORT - 15" baseline elsewhere in this file.
1. Progressing or stuck — evidence across time
One skip run, dumps at increasing frames (72 Hz: 3000 ≈ 42 s, 10000 ≈ 2.3 min, 30000 ≈ 7 min, 60000 ≈ 14 min). A frame (rendered PPM) and the video RAM and main RAM were written at each sample.
| frame | ≈ emulated | frame PPM md5 | video RAM md5 | main RAM md5 |
|---|---|---|---|---|
| 3000 | 42 s | 511743ace0999c5fa60cfffcd8e7c130 |
5ce8ad80d78efc26146b95eeb214a785 |
95dccfd3ae6c43a9f61e8caa22c6f3ea |
| 10000 | 2.3 m | 511743ace0999c5fa60cfffcd8e7c130 |
5ce8ad80d78efc26146b95eeb214a785 |
600f29d7d74dc3cbd139708be71bd77d |
| 30000 | 7 m | 511743ace0999c5fa60cfffcd8e7c130 |
5ce8ad80d78efc26146b95eeb214a785 |
95dccfd3ae6c43a9f61e8caa22c6f3ea |
| 45000 | 10.4 m | 511743ace0999c5fa60cfffcd8e7c130 |
5ce8ad80d78efc26146b95eeb214a785 |
95dccfd3ae6c43a9f61e8caa22c6f3ea |
| 60000 | 14 m | 511743ace0999c5fa60cfffcd8e7c130 |
5ce8ad80d78efc26146b95eeb214a785 |
95dccfd3ae6c43a9f61e8caa22c6f3ea |
- The framebuffer is byte-identical at 3000, 10000, 30000, 45000 and 60000: 58,720,256 bytes of video RAM, same md5, five times, spanning ~14 minutes of emulated time. The rendered frame is identical too (19.3158% lit — the root weave).
- The 4 MiB of main RAM differs by exactly one byte between 3000 and 10000,
at offset
0x829e2:0x50→0x52. That is the only moving thing in all of memory. At 30000 and again at 45000/60000 the whole 4 MiB is byte-identical to frame 3000 — the byte is back to0x50. So the machine is in a limit cycle, not advancing: a counter toggling inside a dead loop. - Net: no drawing, no new window, no changed state over ~14 emulated minutes.
This is a deadlock, not slow progress. (
BRINGUP_LOG.mdalready retired one false "it never paints" conclusion from a single sample; this conclusion is from five samples, the last 14 minutes after the first.)
PC distribution over the whole run (progress.lua, 60000 frames)
143 distinct PCs sampled once per frame. The distribution is tight, and the loop begins at frame ~611 (≈ 8.7 s) and never leaves:
PC=3FB30390 samples=10020 first_frame=614
PC=3FB30110 samples=9777 first_frame=611
PC=3FB30370 samples=1834 first_frame=629
PC=3FB42E50 samples=1589 first_frame=628
PC=3FB42EF0 samples=1223 first_frame=618
PC=3FB301F0 samples=1223 first_frame=615
PC=3FB42710 samples=1223 first_frame=622
PC=3FB30180 samples=1223 first_frame=620
PC=3FB42F30 samples=1223 first_frame=613
PC=3FB30240 samples=1222 first_frame=634
PC=3FB42610 samples=1222 first_frame=632
PC=3FB42760 samples=1160 first_frame=670
All of 0x3FB30110–0x3FB30390 and 0x3FB42610–0x3FB42F30 is either the
OS serial-channel interrupt handler or the queue routine it calls (see §3).
2. The wall, named
PC / routine. The hot code is the OS's DUART interrupt service:
| bit address | what |
|---|---|
0x3FB43800 |
DUART interrupt dispatcher (ISR). Reads DUART1 and DUART2 status, masks 0x66, calls the channel handler for each pending channel. |
0x3FB42410 |
the per-channel handler it calls (calla at 0x3FB439C0 and 0x3FB43AF0). |
0x3FB300F0 |
the lock-protected byte-queue routine the handler calls (calla at 0x3FB42CD0, 0x3FB42E20); its tail rets at 0x3FB30390 is the single hottest sampled PC. |
The address it polls and the value it gets. VT1200_BOARD_LOG over the run
records the distinct accesses of the stuck phase:
W 06200005 pc=3F6F3C50 val=0033 ; OS writes DUART2 IMR = 0x33 (interrupt mask)
R 06200005 pc=3FB43A20 val=0033 ; ISR read returns the SAME value
R 06200001 pc=3FB42610 val=0098 ; DUART2 status
W 06200002 pc=3FB426F0 val=0040 ; DUART2 control write
R 06400005 pc=3FB43900 val=0009 ; DUART1 ISR (real chip: legitimate)
On an SCN2681/MC68681, register 5 is the ISR when read and the IMR when
written — two different registers at one address. DUART2 is not modelled, so
the address falls through to the board-window write-back latch, which returns
the value last written. The dispatcher therefore reads 0x0033 as the ISR on
every dispatch; it masks it with 0x66:
0x33 & 0x66 = 0x22 != 0 -> "DUART2 has a pending interrupt" -> handler runs
and the condition can never clear, because the "ISR" always reads back the
IMR. That is the deadlock: the CPU spins in the channel handler and its queue
routine, at 0x3FB300F0–0x3FB30390, forever.
What is missing. A real DUART2 — the SCN2681/MC68681 at bit 0x0620000
(channel A keyboard, channel B mouse) — whose read-only ISR is distinct from its
write-only IMR, and whose interrupts are delivered on INT2. Without it, the OS
enables DUART2 interrupts it can never receive (§4.1) and sees a permanent
phantom pending bit (§2).
MAME already contains that part, and the driver already wires it:
VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1 <mame> vt1200 ...
With VT1200_DUART2=1 the deadlock clears and the screen changes between
3000 and 10000 (39.99% → 39.90% lit; video-RAM md5 differs) — the machine is
progressing.
Causal proof: it is that one register value
Mapping the whole chip could have fixed many things at once. It did not need
to: forcing only the read of 0x06200005 to 0 — nothing else changed —
reproduces the DUART2 run byte-for-byte:
env VT1200_SKIP_LEVEL1=1 VT1200_BOARD_MAP="0x06200005=0" <mame> vt1200 ...
frame 3000 39.9879% lit, video-RAM md5 73c3c9a1f13378d3ccf1e092aa47fa60
frame 10000 39.8986% lit, video-RAM md5 4ff38c8c782684c562369dc47fb8a781
identical to the VT1200_DUART2=1 run's md5s. So the wall is exactly this:
the value returned by the read at bit 0x06200005 (DUART2 register 5, ISR),
0x0033 instead of a real ISR clear. The OS does not need DUART2 interrupts
to reach the GUI — it only needs to stop seeing the phantom one.
3. The disassembly that pins it
dispatcher 0x3FB438E0..0x3FB43AF0
3fb438e0: move *a11(0x50),a7,0 ; a11 = 0x0640000 (DUART1); offset 0x50 bits = reg 5
3fb43910: andi 0x66,a7 / jreq ; skip if no pending channel-A/B
3fb439c0: calla 0x3fb42410 ; -> channel handler
3fb43a00: move *a12(0x50),a7,0 ; a12 = 0x0620000 (DUART2); reg 5 again
3fb43a40: andi 0x66,a7 / jreq ; <- (0x33 & 0x66) != 0, never skips here
3fb43af0: calla 0x3fb42410 ; -> channel handler, every dispatch
The channel handler 0x3FB42410 reads *a6(0x10) and *a6(0x30) (the DUART
SRA/CSR) and calls the queue routine 0x3FB300F0, whose body opens with a
test-and-set lock (move a12,*a7,0 on the global at @0x1b3900+0x1c1) and a
ring-buffer head/tail compare — i.e. it is draining a serial channel's queue.
4. Prime suspects
4.1 Interrupts enabled but never received — this is the wall, via DUART2
The OS writes IMR = 0x33 to DUART2 (0x06200005), enabling channel-A and
channel-B TxRDY/Rx interrupts. DUART2 is unmapped, so no DUART2 interrupt is
ever delivered (the SCN2681 object exists in the machine config but is never
addressed, so it never raises its INT2 line). The write-back latch makes it
worse than "silent": the read-only ISR aliases the value just written to the
write-only IMR, so the dispatcher sees a pending interrupt that never clears.
Everything else (DUART1 on INT2, the display interrupt) is healthy — which is
exactly the shape the brief predicted: a task that never runs because its
interrupt never fires, with everything else looking fine.
4.2 LANCE Ethernet — not modelled, but not the wall
Not modelled; the window 0x0580000/0x0590000 serves it and reads return the
board latch. The OS does drive it — a cluster of writes/reads at
0x3F9D2880–0x3F9D46D0 (the board log shows W 05900000 … val=0000/0001/…,
R 05800000 … val=0001), and LANCE/Ethernet Port Error are in the ROM. But
the OS gives up after a bounded number of retries and continues: in the
DUART2 run the dialog reports
Could not determine IP address after 5 retries
TCP/IP is now down.
which is the emulated analogue of the real terminal's Ethernet Port Error - 24
banner. So LANCE degrades gracefully; it does not hang the boot.
4.3 DUART2 keyboard/mouse — the wall (see §2)
Note on the earlier, untrusted lead ("a queue scan reading DUART2 registers"):
that part is real and reproducible — R 06200005 pc=3FB43A20,
R 06200001 pc=3FB42610 — but the same investigation's "no framebuffer
updates" claim was wrong, so its conclusion (that this queue scan was benign)
is the part to discard. This report treats the registers as data and lets the
mapped-vs-unmapped experiment settle it, which it does.
4.4 NVRAM — read and used, not a wall
The dialog itself proves NVRAM is reached: it renders Secure Reset Count: 0,
User settings invalid., System defaults loaded.. No stall there.
5. Does the dialog box from the reference video appear?
Yes — with DUART2 modelled. This is an independent, checkable target and it is reached.
reference/vt1200-boot-video/real_t52.png shows the real machine's Terminal
Manager V2.2 dialog (Ethernet Address / IP Address / LAT Host Name / Memory
Remaining bar / Secure Reset Count / a banner at the bottom). The emulated
VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1 run renders the same box:
- frame 3000 (≈ 42 s) — "Terminal Manager V2.2 Copyright © 1991 Digital
Equipment Corporation", Icon Box / TM icons, Messages "Using local fonts.
User settings invalid. Using local fonts. System defaults loaded.",
Ethernet Address
00-00-00-00-00-00, LAT Host NameLAT_00000000000, Memory Remaining, Kilobytes bar (3104), Secure Reset Count: 0, plus the terminal-manager mouse cursor over the root weave. Bottom banner: "All Diagnostics Passed". - frame 10000 (≈ 2.3 min) — the same dialog, and the message area has advanced to "Could not determine IP address after 5 retries / TCP/IP is now down." — i.e. the machine is still running and reporting the (expected) absence of a LANCE.
- frame 30000 (≈ 7 min) — identical to frame 10000 (same PPM and video-RAM md5). The machine is now idle at the GUI, which is the correct state: no keyboard/mouse device is attached to feed it events, so it just waits. Contrast the no-DUART2 case, which is stalled before the GUI and never changes at all.
So the boot path is healthy once DUART2 is present; the only difference from the real machine is the network (no LANCE) and the banner text (level 1 was skipped by the gate). Note the two "stable at 10000→30000" results are not the same thing: the DUART2 run made it to the interactive GUI and then idled, whereas the no-DUART2 run is byte-frozen at the root weave from frame ~611 onward.
6. Reproduce
cd /home/arti/code/k-space/vt1200
M=/home/arti/code/k-space/mame0289/vt1200
R="-rompath harness/mame/roms -sound none -skip_gameinfo -video none -nothrottle"
# A. stuck, with time-samples (the §1 table). Do NOT attach progress.lua here:
# its exit at frame N would race the frame-N dump. 60000 frames ≈ 860 s.
env VT1200_SKIP_LEVEL1=1 \
VT1200_DUMP_FRAMES=3000,10000,30000,45000,60000 \
VT1200_DUMP_VRAM_FRAMES=3000,10000,30000,45000,60000 \
VT1200_DUMP_RAM_FRAMES=3000,10000,30000,45000,60000 \
VT1200_DUMP_DIR=/tmp/vt1200 $M vt1200 $R -seconds_to_run 950
# B. progressing, dialog appears
env VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1 \
VT1200_DUMP_FRAMES=3000,10000 \
VT1200_DUMP_DIR=/tmp/vt1200d2 $M vt1200 $R -seconds_to_run 160
# C. console transcript (firmware's own printer), resolved to strings
env VT1200_SKIP_LEVEL1=1 $M vt1200 $R -debug -debugger none \
-autoboot_script harness/mame/cap.lua -autoboot_delay 0 -debuglog
python3 harness/mame/resolve_console.py debug.log vt1200_v22.bin
New instrumentation added to vt1200.cpp for A/B (all opt-in, default off, so
neither the no-env nor the skip baseline changes):
VT1200_DUMP_FRAMES, VT1200_DUMP_RAM_FRAMES, VT1200_DUMP_VRAM_FRAMES —
comma-separated lists, dumped at exactly those frames in one run.
resolve_console.py resolves cap.lua's a9 message pointers to strings.
Baselines (unchanged)
- no env → POST + level 1 to
LEVEL1/2 ERROR -> HOST PORT - 15(verified this session withcap.lua; thenTYPE 'GO' TO CONTINUE). VT1200_SKIP_LEVEL1=1→ the X11 root weave (skip_frame3000.png), byte-identical at 3000/10000/30000.progress.luawas not changed; its no-env 200-frame baseline is intact.
Not-changed on purpose
VT1200_DUART2 is not promoted to a default. A no-env run with
VT1200_DUART2=1 leaves the faithful path entirely and drops into the level-0
interactive self-test menu (VERSION 2.08 - SELECTED TEST TYPE …), which would
break "no-env runs POST + level 1 to HOST PORT - 15". The GUI is therefore the
documented combination VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1.
7. What I did NOT establish
- The exact micro-dynamics of the loop (whether the handler is re-entered purely by a DUART1 TxRDY interrupt storm, or by the main loop re-polling). The mechanism — an ISR read that cannot clear — is proven by the board log and by the mapped/unmapped experiment; the cycle-accounting is not.
- Whether MAME's
SCN2681reproduces the real VT1200 DUART2 exactly (it is the right part family and the OS accepts it, but no keyboard/mouse device is attached, so RX behaviour is untested). - What happens after the dialog on real hardware ("light screen ~1:08"). With DUART2 the emulated run reaches the interactive GUI and then idles (identical 10000→30000), so I did not chase a post-dialog sequence; the "light screen" in the reference is most likely the video camera's CRT auto-exposure, not content.
- NVRAM contents/initialisation policy — only that the OS reads it and falls back to system defaults.
8. DUART2 promoted to a default — what it fixes and what it breaks
Date: 2026-10-16 (later). Change: harness/mame/vt1200.cpp maps MAME's
SCN2681 at 0x0620000 (keyboard ch A / mouse ch B) unconditionally; the
VT1200_DUART2 gate is deleted. ROM untouched (md5 b46a3e442ccb86134d2356bd70d22fd2).
8.1 The OS now reaches the GUI with only VT1200_SKIP_LEVEL1=1
One run, no VT1200_DUART2 anywhere:
env VT1200_SKIP_LEVEL1=1 VT1200_DUMP_FRAMES=3000,10000 \
VT1200_DUMP_VRAM_FRAMES=3000,10000 VT1200_DUMP_DIR=<dir> <mame> vt1200 ...
| frame | lit | video-RAM md5 |
|---|---|---|
| 3000 | 461172 (39.9879%) | 73c3c9a1f13378d3ccf1e092aa47fa60 |
| 10000 | 460143 (39.8986%) | 4ff38c8c782684c562369dc47fb8a781 |
Video-RAM md5s identical to the report's VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1
run — a bare skip run now is that run. The dumped frame (I rendered
vt1200-frame-3000.ppm to duart2_default_tm_frame3000.png) shows the
Terminal Manager V2.2 box — title bar "Terminal Manager V2.2: Copyright ©
1991 Digital Equipment Corporation", Session/Create/Customize/Print Screen,
Messages ("Using local fonts. / User settings invalid. / System defaults
loaded."), Ethernet Address 00-00-00-00-00-00, LAT Host Name
LAT_00000000000, the Memory Remaining bar (3104), Secure Reset Count: 0, the
mouse cursor, and the bottom banner "All Diagnostics Passed". This matches
reference/vt1200-boot-video/real_t52.png. Frame 10000 differs, so the machine
is still running (not frozen).
8.2 Level 0: the earlier claim is REAL — and the exact test is named
The claim "a real DUART2 diverts level 0 to the interactive diagnostics menu"
holds. The inference that it "looks wrong" came from the skip run, but
VT1200_SKIP_LEVEL1 skips level 0 entirely, so that run says nothing about it.
- Console, no env at all now (
cap.lua): the transcript is no longerLEVEL1/2 TESTING/LEVEL1/2 ERROR -> HOST PORT - 15; it is the interactive diagnostics menu —VERSION 2.08 - SELECTED TEST TYPE:and the A–Z test list (D - KEYBOARD TEST,H - DUART2, CHANNEL A (KEYBOARD) TEST, …). So the faithful path is broken by this change. - Exact test. Level-0's I/O data path test, routine
L3FDCE470, called from3F3DFA80with window base0x0620000(DUART2 ch A). It runs atcyc≈1.494 M, before the baseline'sLEVEL1/2 TESTINGprint atcyc=2.119 M(confirmed with a cycle-stamped printer breakpoint), i.e. it is a level-0 test, not level 1. - What it waits for. It initialises the channel (
MR1A=0x13, MR2A=0x07, CSRA=0x99, CRA enable, normal mode — not local loopback), transmits0xE1to THRA (pc=3FDCE8D0), and then drains the receiver: loop3FDCE6C0/3FDCE930reads RHRA, delays 100, and repeats while SRA bit 0 (RXRDY) is set, up to 10 bytes; the pass branch is taken at3FDCEAE0only when the count is exactly 10, otherwise it falls through to the menu display. With a real chip and nothing on the wire, RXRDY never sets (SRA=0x000C= TXRDY|TXEMT, bit 0 clear) and the test fails. - Why the baseline passed. The first differing access is
R 06200001 pc=3FDCE670: baseline (board model) returned0x0099— the value last written to CSRA, whose bit 0 is set — so the driver drained 10 copies of the latched RHRA0x008Cand hit count 10. A real chip returns0x000C. The write-back latch was an accidental loopback that faked the test; modelling the real device removes the fake and exposes the gap. - The LK401 model would not fix it. The firmware transmits only three bytes
before this test (
0x11 0x8C= LEDs off, then0xE1= disable auto-repeat); none of these has a documented LK401 reply, and the keyboard's power-up /FDself-test report is 4 bytes (reference/keyboard-mouse/PROTOCOL.md§3.5;vt100net-lk401.txt§3.3.4.6). The test drains until it has read 10. I also wired an external DUART2-A TX→RX loopback (a_tx_cb→rx_a_w, the oldVT1200_DUART_LB2) as a diagnostic: the firmware's transmit count is still 3 and the menu still appears. So no faithful device/protocol model we have supplies the 10 bytes; restoring the no-env path would require re-introducing a read-back fake, which is exactly what this change removes.
8.3 Does the OS keyboard/mouse driver initialise? — Yes
VT1200_SKIP_LEVEL1=1 with VT1200_DUARTLOG, first 40 s: OS code (pc in the
OS range, e.g. 3F6F3990…3F6F3CA0, then 3FB43550) drives 0x0620xxx:
W 0620000D=0000 OP, W 0620000E=000C, W 0620000F=0004 ; output port setup
W 06200002=002A/003A/005A/0010 (CRA) , W 0620000A … ; channel resets
W 06200000=0013 MR1A , W 06200008=0007 MR1B
W 06200004=0060 ACR
W 06200001=0099 CSRA , W 06200009=0099 CSRB
W 06200000=0007 MR2A , W 06200008=0007 MR2B
W 06200005=0033 IMR (both channels Rx/Tx)
W 06200002=0009 CRA , W 0620000A=0009 CRB ; enable Tx+Rx+A
… then the driver's command traffic (pc=3FB43550):
W 06200003=00FD FD -> keyboard: jump to power-up / self-test
W 06200003=0013 0080, 001B 0086, 0023 0084, 00E3 ; LED / click / bell / auto-repeat
W 0620000B=0054 'T' -> mouse: self-test & identify
Every one of those keyboard bytes is a documented LK401 command and 'T' is
the documented VSXXX mouse self-test (PROTOCOL.md §3.3, §4.2) — so the
driver initialises and speaks the real protocol. Input still does not
work (no keyboard/mouse is attached, and nothing is faked); the chip receives
no reply, which is the honest state.
8.4 Baselines
- no env → CHANGED: no longer POST + level 1 to
HOST PORT - 15; now level-0 I/O data path fails and the machine sits in the diagnostics menu (§8.2).progress.lua200 frames: 3 distinct PCs, hotPC=3FDCE400 samples=198(the menu's key-wait delay loop). This replaces thePC=3FDCE400 samples=53 / 78 distinct PCsbaseline recorded inGATE_CLEANUP_REPORT.md; the baseline is a change detector, and this change is deliberate. VT1200_SKIP_LEVEL1=1→ intact and extended: the X11 weave still paints first and the run now progresses through it to the Terminal Manager (§8.1). Frame/VRAM md5s unchanged from the old skip+DUART2 run.progress.luaunchanged.
8.5 Reproduce
M=/home/arti/code/k-space/mame0289/vt1200
R="-rompath harness/mame/roms -sound none -skip_gameinfo -video none -nothrottle"
# GUI with ONLY the skip gate (<dir> must exist):
env VT1200_SKIP_LEVEL1=1 VT1200_DUMP_FRAMES=3000 \
VT1200_DUMP_DIR=<dir> $M vt1200 $R -seconds_to_run 60
# Level-0 divert, with no env at all:
env CAP_MAX=2500 $M vt1200 -rompath harness/mame/roms -sound none \
-skip_gameinfo -video none -nothrottle -debug -debugger none -debuglog \
-autoboot_script harness/mame/cap.lua -autoboot_delay 0 -seconds_to_run 60
python3 harness/mame/resolve_console.py debug.log harness/mame/roms/vt1200/vt1200_v22.bin
# OS keyboard/mouse driver init:
env VT1200_SKIP_LEVEL1=1 VT1200_DUARTLOG=harness/mame/scratch/duart_skip.log \
$M vt1200 $R -seconds_to_run 40
Bottom line. The faithfulness fix works and the GUI is now reachable with
one env var — but it invalidates the "no env reaches HOST PORT - 15"
constraint, for a real, named reason (level-0 I/O data path test needs 10
received bytes on DUART2-A). The previous no-env baseline only reached level 1
because the board latch faked that test. I did not fake it back.