Files

17 KiB
Raw Permalink Blame History

First contact with the real VT1200

Date: 2026-10-18. Terminal at 100.103.100.103 (static IP, no BOOTP), reached from host 100.103.255.254 on enp197s0f3u1.

Read-only throughout this note — no poke, no jump.

Results

check result
TCP 771 open
opcode 7 challenge (flag 0x7F) sent, computed reply accepted, Done\n\0
peek 0x3F200000 (8 B) e9 7a e2 f5 aa aa 55 55 — matches vt1200_v22.bin
peek 0xAECE0 (1 B) bd — message classes 0,1,2 admitted (emulator: 05)
peek 0x1E8BA0 (4 B) 0x018BF1B0 bits = 3,251,638 bytes free in the heap
screenshot over peek dwt/evidence/real-unit-canvas.png

The screenshot shows the live Terminal Manager V2.2: Ethernet 08-00-2B-16-4B-5A, IP 100.103.100.103, LAT LAT_08002B164B5A, and five Debug connection from IP host 100.103.255.254 lines in Messages — one per connection made here. That is the read primitive working on real hardware.

Two differences from the emulator, worth knowing

  1. The real unit challenges opcode 7. The emulator only challenges opcodes ≥ 2. A plain liveness check therefore exercises the challenge too, and its success is additional evidence that dwt/challenge.py matches the shipped image.
  2. Message filter is 0xBD, not 0x05. Levels 0, 1 and 2 are admitted on the real unit; the emulator only admitted 0 and 3.

Geometry

The raw VRAM canvas is 1024×1024, captured correctly. The visible screen is 1280×1024, with the canvas at x=192; dwt/screenshot.py --screen uses the emulator's 1280×901/offset-192×36 defaults and will misplace the bottom on the real unit. The raw canvas is the authoritative artifact; for a faithful screen image use --screen-width 1280 --screen-height 1024 --offset-x 192 --offset-y 0 once the vertical origin is measured.

Next

The remaining unproven step on this unit is the write path: poke/jump and then the COFF loader + create_process. The loader already refuses a target outside a free-list block (status 8), so a first attempt with marker.coff will tell us whether the linked ranges are free before anything runs.

Display geometry and background (measured)

  • Framebuffer 1024×1024, 128 bytes/row, LSB-first. A 160-byte pitch shears; 128 bytes is coherent. The 160-byte figure is the total raster (1280/8), not the memory pitch (RENDER_REPORT.md).
  • Visible raster 1280×1024 (monitor OSD: 77.2 kHz / 73 Hz; Samsung SyncMaster 770 native 1280×1024). The 1024-wide canvas is displayed 5:4, so dwt/screenshot.py --stretch 1280x1024 matches the screen. --screen's emulator 1280×901 geometry pads/crops and looks line-doubled.
  • The 101010 background is real data, not a decode error. It is a 50% stipple: rows alternate 55 (01010101) and ff. The root background has a setting; setting it to 100% white changed the root to solid ff (verified by a fresh grab). The Terminal Manager window kept its own stipple, so its window background is a separate setting (or was taken at creation).
  • The bottom message box Ethernet Port Error - 24 is present in VRAM; it is a real terminal error, not a capture artifact.

The TM stipple survives the root-background change

A fresh grab after the root was set to 100% white confirms: root 0xff, TM body still alternating 0x55/0xff rows. So the TM pattern is not the desktop background setting and the manager has no option for it.

The likely explanation is the display fetch, not the data: if the video mode shows only one set of rows (line doubling / interlace), the 0xff rows are what reaches the glass and the 0x55 rows are never shown — which is why the TM looks white while the raw framebuffer looks striped. That also explains why the capture looked "line doubled": it is showing both row sets.

The emulator forces the VR150 monitor position (IP4-IP6 high, 0x70). The real unit's rear switch selects the mode; if it is at another position the firmware may use a different fetch/pattern. Test: set the switch to position 1 (VR150), power-cycle, grab again.

Line-interleaved framebuffer — the "line doubled" answer

The real framebuffer is line-interleaved: even rows carry the image, odd rows carry a 0x55 mask in window areas (and 0xff in the root). The display doubles the even field, so the odd rows are never shown. A linear peek reads both fields, which is why the first captures looked striped and "line doubled".

Evidence: in the Messages area every even row is 0xff/text and every odd row is a uniform 0x55; the root is 0xff on both. After the owner set the background to 100% white and rebooted, the root is 0xff everywhere and the TM's odd rows are still 0x55 — yet the screen is solid white. That proves the odd rows are a non-displayed mask.

Correct capture:

python3 dwt/screenshot.py --host 100.103.100.103 \
    --field even --stretch 1280x1024 --out screen.png

dwt/evidence/real-unit-white2.png is that capture and matches the glass.

This is a display-fetch difference, not a CPU-emulation difference. MAME's driver models a linear 1bpp framebuffer, so the emulator never shows the interleave; the real TMS34010 display controller is programmed for the doubled even field.

Correction: the framebuffer is 1024x2048, not line-doubled

The earlier "the display doubles the even field" reading was wrong. A 256 KiB read (2048 rows x 128 bytes) shows the 1024 image lines sitting on even rows at 1:1, with the odd rows carrying the mask. The earlier 128 KiB read held only the top 512 image lines; doubling them stretched the top half and cut the bottom — which is exactly the "missing left/bottom" and "line doubled" appearance.

Correct 1:1 capture:

python3 dwt/screenshot.py --host 100.103.100.103 --real --out screen.png

--real means --rows 0:2048 --field even and outputs 1024x1024 with no resampling. dwt/evidence/real-unit-1to1.png is that capture: Icon Box, Terminal Manager, Memory bar, and the Ethernet Port Error - 24 box at the bottom — the whole screen.

--stretch 1280x1024 is only the monitor's 5:4 scaling of the 1024-wide framebuffer; it is not 1:1 and the nearest-neighbour version produces the uneven-pixel artefacts. Use it only for an approximate visual match, not as evidence.

Final geometry: 1280x1024, 256-byte row stride

The real unit is 1280x1024 with a 256-byte row stride. Each row's first 160 bytes (1280 px) are the image; the remaining 96 bytes are a 0x55 mask. The earlier "1024x2048, even rows" reading was an artifact of treating that 96-byte gap as an alternate row.

This is why the 1024-wide reads lost the right quarter: the Icon Box really is 1280 px wide and its resize buttons are at the far right, outside a 1024-px crop. The mask bytes are what looked like "line doubling".

Correct 1:1 capture:

python3 dwt/screenshot.py --host 100.103.100.103 --real --out screen.png

--real = width 1280, stride 256, 1024 rows, no resampling. dwt/evidence/real-unit-1280-1to1.png is the full 1280x1024 screen, including the Icon Box resize buttons and the Ethernet Port Error - 24 box.

The emulator geometry is unchanged: 1024x1024 with a 128-byte stride.

Background references

Both backgrounds are captured at the correct geometry: dwt/evidence/real-unit-1280-1to1.png (100% white root) and dwt/evidence/real-unit-1280-50pct.png (50% 1010 root, the owner's preference). Only the root's image bytes change; the 256-byte stride and the 1280x1024 size are identical.

X11 app on the real unit (2026-10-18)

The host-side loader ran end to end against the terminal:

python3 dwt/loader/hostload.py --host 100.103.100.103 \
    --coff dwt/loader/build/xhello.coff --real
jump(malloc) -> 0x007e7bf0
COFF 774 B poked          (0 differing)
loader 1220 B @ 0x01880000 (0 differing)
loader A8 = 0; entry 0x01840000; pcb 0x003f6510
xhello stage=6 strlen=4

Evidence: dwt/evidence/real-unit-xhello.png — a 400x200 WM-framed black window over the Terminal Manager, on the real screen.

Caveat: the loader and app are linked at fixed addresses (0x01880000, 0x01840000). Re-running while that app is live overwrites the running code. Power-cycle (or choose new addresses) before loading again.

MOP console on the real unit

dwt/mop_console.py --iface enp197s0f3u1 --mac 08:00:2b:16:4b:5a works with no password. SHOW returned:

Firmware Revision  V2.2
Option Memory      Present (SIMM 1/2/3 installed)
Image Option       Not Present
Display Type       Monochrome
Monitor Type       VR319

Monitor Type VR319 is the answer to the geometry question: the unit is in the VR319 (19", 100 dpi) mode, not the emulator's forced VR150. That is why the real framebuffer is 1280x1024 with the 256-byte stride.

TEST 9 9 8 (Ethernet port test, LANCE loopback bit 3) was refused:

?ILLEGAL COMMAND - DWT IN USE

The console refuses diagnostics while the DWT has active work — our injected xhello window is the blocker. The service guide requires no open windows before remote tests: power-cycle (or close all session windows) and retry.

Diagnostic results are not observable through rdebug

dwt/mailbox_watch.py polled the mailbox pointer (0x27C190) and the level-1 dispatcher slot (0x16F0) across a power cycle. The rdebug service was only connectable after the OS was up, at which point:

mailbox=0x00000000   slot=0x8ac00000   (OS data)

During the boot it was unusable (timeouts / AssertionError). So the level-1 result is consumed before rdebug exists; the mailbox is a dialog/debug structure, not the boot handoff. The only durable record is the screen: the boot alert, and the Diagnostics dialog's on-screen results.

Menu-driven Ethernet Address ROM test: PASS

After a clean power cycle, selecting Ethernet Address ROM Test in the Diagnostics dialog completed, the dialog closed, and the Terminal Manager's bottom notification reads:

All Diagnostics Passed

Evidence: dwt/evidence/real-unit-romtest-result.png.

This is the ROM checksum test (service-guide error 23 would be the failure), and it passes on the real unit, consistent with MAME. The boot-level Ethernet Port Error - 24 is the separate AUI/transceiver termination test, not the ROM.

The first attempt hung the Terminal Manager; this one completed. The hang is not deterministic. The mailbox watcher's reconnect storm during boot is one possible co-factor (it spawns a debug process per accepted connection), but the diagnostic reset is the other; a single success does not settle which.

NVRAM and LANCE address ROM, read over rdebug

dwt/nvram_dump.py reads 4 KiB at bit 0x6000000 and extracts the 0x800 settings bytes (low byte of each 16-bit word). Evidence: real-unit-nvram-settings.bin, real-unit-nvram-raw.bin.

Printable settings runs:

0x26f 100.103.0.10      0x28f 172.20.51.0
0x3cf 1.1.1.1           0x3ef 8.8.8.8
0x40f retro.k-space.ee
0x50f 100.103.0.1
0x597 ROM               0x5e7 ROM
0x5eb 103.255.254:69
0x637 100.103.100.103   0x657 255.255.0.0
0x677 Messages          0x6ca / 0x6ea VTE 1

Both font-path fields persist: the selected ROM and the old TFTP 103.255.254:69.

The LANCE address ROM at bit 0x6800000 reads (low bytes of the words):

08 00 2b 16 4b 5a c1 86

byte-identical to the MAME model. The Ethernet Address ROM Test passing is consistent with this.

Self-contained TFTP loader on the real unit

Retargeted tftpc.asm to the LAN (100.103.255.254:69) as dwt/tcpip/tftpl.asm; the RRQ name stays dwt/tcpip/probe.bin because the server serves the chosen COFF for any requested name. The DOS 8.3 build name must be <= 8 characters: tftpc_lan silently truncates and GSPLNK produces no TFTPC_LAN.OUT; tftpl is safe.

server: dwt/tcpip/serve_probe.py 100.103.255.254 --file dwt/loader/build/xhello.coff
RRQ:    100.103.100.103:1036 requested 'dwt/tcpip/probe.bin' (774 bytes)
fetch:  sha256 a8b3014d27deb97adcebb418a5d14da51fc3c11559e84923ede72e260d3e7271
loader: status 0, entry 0x01840000, pcb 0x00849a10
app:    xhello stage=6 strlen=4

Evidence: dwt/loader/evidence/real-unit-tftp/hostload_tftp_xhello.png.

This is the first fully self-contained delivery on the real unit: the terminal fetched the COFF over TFTP into malloc memory and ran it in a created process, with only the rdebug jump used to start the fetch.

Full ROM dump: byte-identical to the reference image

dwt/rom_dump.py read 1,835,008 bytes at bit 0x3F200000 (1792 x 1 KiB chunks, 2:06, ~14.6 KB/s) and compared the md5:

b46a3e442ccb86134d2356bd70d22fd2   (real unit)
b46a3e442ccb86134d2356bd70d22fd2   (vt1200_v22.bin)
MATCH

So the image the project has analysed and emulated is the ROM on the machine; no revision or bank difference. The dump is reproducible with:

python3 dwt/rom_dump.py --host 100.103.100.103

X11 text app on the real unit (xtext)

dwt/gui/xtext.asm is xhello plus the text path: XOpenDisplay("0::0"), XCreateSimpleWindow (400x200 at 100,100, black client area), XLoadFont, XCreateGC, XSetForeground(1), XMapWindow, then four iterations of XSync + XDrawString("HELLO VT1200") + XDrawString("text works") and a final XSync. Linked at bit 0x01840000; loaded with dwt/loader/hostload.py --real and with --tftp (below).

Two real-unit loads (pcb 0x00846410, 0x00843950) and two TFTP runs (pcb 0x008458d0, 0x0083f5d0) all returned loader A8=0, entry 0x01840000, and left stage=9, strlen=4, draw1=0x0c, draw2=0x0a (the two XDrawString return values are the string lengths). Text is visible in dwt/evidence/real-unit-xtext.png, real-unit-xtext-2.png, real-unit-xtext-tftp.png, and real-unit-xtext-tftp2.png; the same app renders in MAME (dwt/loader/evidence/real-unit-xtext/emulator-xtext-run{1,2}.png). Several windows are visible in the later screenshots because the owner moved windows around; the app window is identified by its content (HELLO VT1200 / text works) and its stage=9 markers, not by counting windows.

What worked, exactly:

  • Font: "fixed" returns 0 from XLoadFont on this server (the string exists in ROM as the default-font name, but is not an openable resource). The ROM's built-in DEC font works: -DEC-Terminal-Medium-R-Normal--14-140-75-75-C-8-ISO8859-1 (font marker 0x00850e90). XLoadFont is called for evidence; the GC does not need the id.
  • GC: XCreateGC(d,win,0,NULL) works. Passing mask=GCFont (0x4000) with an XGCValues block (font at byte +60) hung the connection: the app reached stage=7 (mapped) but the first XSync never returned and draw1 stayed 0. With mask 0 the server fills the GC from the screen's default GC, whose font is usable, and the draws return 12/10.
  • Window timing: one early draw can be lost when the WM reparents the window. The working shape draws repeatedly, with an XSync before each pair; four iterations were enough (48 also ran, but the first was still inside XSync at the inspection frame).
  • Saved SP/A14 in RAM is a trap. The first version saved entry SP/A14 at 0x01894500/20 and reloaded them after every call. The OS heap overwrote those cells (a string Exiting\n landed on them), the next call restored SP from garbage and the app died at XCreateGC. The callee's rets 2 already restores SP and A14, so the working version does not restore them from RAM at all. Diagnostic markers were moved into zero padding at the end of the app's own .text (0x01842000); the earlier cells at 0x01895000 were also overwritten by the heap.
  • ROM unchanged: md5 b46a3e442ccb86134d2356bd70d22fd2.

Self-contained TFTP delivery of the text app: dwt/tcpip/tftpr.asm is tftpc.asm retargeted to server 100.103.255.254:69 and RRQ xtext.coff (20-byte request: xtext.coff\0octet\0). Served with dwt/tcpip/serve_probe.py 100.103.255.254 --file dwt/loader/build/xtext.coff and loaded with hostload.py --tftp --real. Two runs, both verified byte-for-byte (sha256 4f713d33be8a36dfa14be8a3e4a0451bcabc1a12b1b40ddca385697104a63b26):

17:26:19  RRQ 100.103.100.103:1071 requested 'xtext.coff' (1494 bytes)
17:26:55  RRQ 100.103.100.103:1081 requested 'xtext.coff' (1494 bytes)

The build name must be <= 8 characters: tftpc_real silently fails (no TFTPC_REAL.OUT); tftpr is safe.

Evidence: dwt/loader/evidence/real-unit-xtext/ (loads, markers, server logs, screenshots, emulator runs, xtext_probe.lua).

Bottom diagnostics box: live state (2026-10-18, read-only)

The separate bottom X window is owned by the Terminal Manager; its window id is the global at bit 0xB7C60. The text is selected from the status mask at bit 0xCB640 and a pointer list at bit 0x19F150 (20-byte entries, string pointer at +8 bytes). Live reads, two samples 2 s apart:

peek 0xCB640 4   -> 00 00 00 00      mask 0 -> "All Diagnostics Passed"
peek 0xB7C60 4   -> 2d 00 10 00      box window 0x0010002d
peek 0x19F190 4  -> a0 b9 4a 3f      list[0] -> 0x3F4AB9A0
peek 0x19F230 4  -> 00 00 00 00      null terminator
peek 0x19FF10 2  -> 00 00            language 0 (English)
peek 0x19E8D0 4  -> 90 b0 4a 3f      record 8: "Ethernet Port Error - 24"
peek 0x27C190 4  -> 00 00 00 00      DIAGNOSTIC MAILBOX pointer is NULL
peek 0x3F4AB9A0  -> "All Diagnostics Passed"

The mask bit mapping (bit 15 = Ethernet Port Error - 24, bit 8 = Video RAM Error - 22, ...) was verified dynamically in MAME by setting @0xCB640 before the builder at frame 569. Full trace: dwt/notes/MESSAGEBOX.md.