Eleven bytes that recognize a Videx
Detail for Part 1 — Why Microsoft CP/M Didn’t Recognize an 80-Column Card.
The 6502 boot stub loads sectors 0, 2, 4, 6, 8, A, C, E, 1, 3, 5 of track 0 to memory $0800, $0A00..$1300 and jumps to $1000. That’s the stage-2 loader. Reconstructed both the 2.20 and 2.23 versions of this 3 KB image from their respective disks, ran them through nibbler’s 6502 disassembler, and read.
Both loaders begin identically at $1000 with LDA $C081 / LDA $C081 — the Apple II language card switch, double-write to enable RAM at the high addresses. They diverge by byte 6: 2.23 inlines the next operation, 2.20 calls a subroutine. Different instruction sequences, but same intent.
The interesting region is the slot scanner around $1060-$10D6. Both versions: walk slots 7 → 1, set $3C/$3D = $Cn00 (pointer to the slot ROM page), read four signature bytes, compare against a small fixed table, tag the slot at $02F8+$Cn with a per-device code byte.
The signature data table is byte-identical across versions: F2 03 18 38 / 48 3C 38 18. Read column-wise, four 2-byte signatures. One of them — $Cn05=$38, $Cn07=$18 — is the standard Apple Pascal 1.0 firmware ID (per Apple II Technical Note Misc #8). The other three are Microsoft-specific cards (probably the Microsoft serial card and similar). That table didn’t change between versions.
The change is what 2.23 does after matching the Pascal signature. Inserted between the signature loop and the per-slot store:
$10BD: E0 04 CPX #$04 ; matched Pascal 1.0 ID bytes?
$10BF: D0 0A BNE skip
$10C1: A0 0B LDY #$0B ; Pascal 1.1 signature byte offset
$10C3: B1 3C LDA ($3C),Y ; A = byte at $Cn0B
$10C5: C9 01 CMP #$01 ; Pascal 1.1 signature?
$10C7: D0 02 BNE skip
$10C9: A2 06 LDX #$06 ; tag as Pascal 1.1: device code $06
Eleven bytes. 2.20 has nothing in this region; the matched-signature index is stored as-is.
Cross-checked the meaning of $Cn0B == $01 against Apple II Technical Note Misc #8 (“Pascal 1.1 Firmware Protocol ID Bytes” — the only canonical Apple reference for this protocol). The byte at $Cn0B is the Pascal 1.1 signature byte — a fixed value $01 that distinguishes Pascal-1.1-compliant cards from Pascal-1.0-only cards. The actual device-type nibble lives at $Cn0C, which 2.23 doesn’t read. From the Videx Videoterm ROM 2.4 disassembly:
$CB05 = $38 Pascal 1.0 ID byte 1
$CB07 = $18 Pascal 1.0 ID byte 2
$CB0B = $01 Pascal 1.1 signature
$CB0C = $82 device type $8 (informally: display), instance $2
The Videx exposes exactly the bytes 2.23 looks for. When its expansion ROM is paged in, those bytes are mirrored at $Cn05/$Cn07/$Cn0B/$Cn0C for whichever slot the card sits in. 2.23 sees a Pascal 1.1 card and tags it $06; 2.20 sees a Pascal-compatible card (1.0 or 1.1, indistinguishable to it), tags it $04 like any other Pascal card, and downstream code presumably tries to drive it through the wrong path.
So 2.23 is really just “Pascal-1.1-aware” where 2.20 is only “Pascal-1.0-aware.” It’s not Videx-specific; the Videx is the most common Pascal 1.1 card and benefits accordingly. Notably 2.23 doesn’t read the device-type byte at $Cn0C either — it treats every Pascal 1.1 card identically as device code $06, regardless of whether the card declares itself a display, serial port, or anything else.
Wrote up a side-by-side annotated diff in docs/CPM_Videx_Difference.md of the Orchard repo using the symbolic names from the Videx ROM disassembly.
Status: detection mechanism identified, characterized, and cross-referenced against the Videx ROM. Next: trace what 2.23 does with device code $06 downstream — the consumer code that programs the 6845 CRTC at $C0B0/$C0B1 and writes characters into the $CC00-$CDFF VRAM window. That code lives further into the loader and likely also in the Z-80 BIOS’s CONOUT path.