TRS-80 DOS - VTOS v4.0.2 for the Model I - SYS0/SYS Disassembled

Page Customization

Introduction/Summary:

VTOS 4.0 SYS0/SYS Disassembly - Resident DOS Core (Model I)

SYS0/SYS is the memory-resident core of VTOS 4.0 (Virtual Technology Operating System), the Model I DOS written by Randolph Cook. VTOS 4.0 was released in 1980 as version 4.0.2 and served as the direct ancestor of LDOS. The SYS0 module remains in memory at all times and provides the fundamental services that all other SYS overlay files depend upon: the RST 28H overlay dispatcher, disk I/O engine, file management primitives, keyboard interrupt service routine, real-time clock, program loader, and cold boot initialization.

The file spans from 3C00H through 50F8H in the disassembly listing. The 3C00H-3CDCH range contains the boot banner and serial number prefix strings, which are stored in video RAM address space during the boot process and referenced by the cold boot display routine. The main resident code occupies 400CH through 50F8H, with the cold boot entry point at 4E00H (as indicated by the END directive). SYS overlay files load into memory starting at 4E00H and extending through approximately 5200H, overlapping the boot code which is no longer needed after initialization completes.

VTOS 4.0 SYS0 shares significant architectural DNA with TRSDOS 2.3 SYS0, which is unsurprising given that Randolph Cook authored both the original Model I TRSDOS (versions 2.0-2.1) and VTOS. The jump table layout at 400CH-4478H is nearly identical in structure, the RST 28H overlay dispatch mechanism uses the same SVC code bit-field encoding, and the disk I/O core employs the same inline parameter block technique with EX (SP),HL. However, VTOS introduces several refinements: the IY register is used as a pointer to drive configuration parameter blocks (enabling hardware abstraction for non-standard controllers like the Lobo LX-80), the CONFIG/SYS auto-load mechanism provides automatic system configuration at boot, and the date/calendar system includes full day-of-week calculation with month name display.

The keyboard interrupt service routine at 4518H is triggered by RST 38H and performs dual duty: scanning the keyboard matrix for changes and dispatching timer callback routines. The timer system supports up to 12 callback slots (at 4500H-4517H), with the real-time clock cascade routine connected as callback slot 7. The clock maintains seconds, minutes, and hours at 4041H-4043H, with a 5-tick prescaler at (IX+02H) dividing the ~25Hz interrupt rate down to 5 ticks per second.

The disk I/O core at 4600H-46FFH handles drive selection, FDC command issuance, and sector data transfer. It uses the IY register to index into drive configuration parameter blocks at 4700H, where each 10-byte block contains the drive select command byte, track cache, FDC stepping rate, and sector/track geometry. The inline parameter block pattern (CALL 46A4H followed by embedded data bytes) allows compact encoding of complex multi-step FDC operations.

The program loader at 4CF8H-4DA4H processes /CMD-format files with support for type 01H (data block), type 02H (entry point), type 08H (ISAM directory), type 0AH (end-of-ISAM/patch), and type 20H+ (comment/header) records. It includes write-verify protection that detects ROM writes and reports appropriate error codes (63H for non-writable memory, 64H for verify failure).

Key System Variables

AddressBytesPurpose
3C00H-3C7FH128VTOS boot banner: "VTOS - VIRTUAL TECHNOLOGY'S/OPERATING SYSTEM - VER 4.0.2 ... COPYRIGHT (C) 1980 RANDOLPH COOK, ALL RIGHTS RESERVED"
3CD1H-3CDCH12Serial number display prefix: "VTOS SERIAL #"
4020H-4021H2Video RAM cursor position (low byte = column, high byte = 3CH base)
4023H-4024H2ASCII "DO" - purpose TBD (possibly part of "DOS" identification)
4025H-4026H2Printer device control: 06H 00H
4028H1System configuration byte (42H)
402BH-402CH2ASCII "PR" - printer device name prefix
4036H-403CH7Keyboard image buffer (7 bytes, one per keyboard row, initialized to FFH)
403EH1System flags byte (40H)
403FH-4042H4System parameter area (initialized during boot)
4040H1Master tick counter (heartbeat, incremented by ISR)
4041H-4043H3Real-time clock: seconds (4041H), minutes (4042H), hours (4043H)
4044H-4046H3Date: day (4044H), month (4045H), year (4046H)
4047H-4048H2High memory limit pointer (initialized to 5200H by cold boot)
4049H-404AH2Detected RAM top address (found during memory probe)
404BH1Timer callback enable mask
404CH1System capability flags (bit 7 = keyboard active, bit 6 = disk active)
404DH-4058H12Timer callback slot pair table (6 x 2-byte entries: threshold + current)
4059H-405AH2Disk I/O vector
405BH-405CH2Character output vector
4300H1Drive 0 parameter entry: RET instruction (C9H) as default "no-op" handler
4303H-4305H3CALL 4030H - default error handler for unconfigured drive access
4308H-4309H2Last-selected drive identifier (FFH = none selected) and drive select command byte
430AH-430BH2Saved overlay return address (FCB pointer)
430CH-430DH2Saved overlay return address
430EH1Currently loaded overlay ID (SVC code)
430FH1System mode flags (bit 0=command mode, bit 1=?, bit 2=load in progress, bit 3=RS-232 active, bit 4=SYS6 cached)
4310H-4311H2Overlay cache return address
4312H-4314H3BREAK key handler vector (JP or NOP instruction)
4315H1BREAK key enable flag (C9H=RET=disabled, C3H=JP=enabled)
4316H-4317H2BREAK handler target address
4318H-4337H32DOS command buffer
4338H-4346H15CONFIG/SYS filename template: "CONFIG/SYS.CC:0" + 03H terminator
43B8H-43BFH8Boot sector parameter copies (from 4016H, 401EH, 4026H areas)
43C0H-43D7H24Drive configuration table (3 entries x 8 bytes for drives 0-2)
44A0H1Overlay parameter byte (80H)
44A1H1Overlay execution state flag
44A4H1Overlay page marker (42H = 4200H load area)
44A7H1Overlay directory entry info byte
44AAH-44ABH2Overlay cache markers
44AEH-44AFH2Overlay load address
4500H-4517H24Timer callback address table (12 x 2-byte entries, initialized to 45BBH=dequeue stub)
4700H-474FH80Drive configuration parameter block table (up to 8 drives x 10 bytes each)

Major Routines

AddressNamePurpose
400CHRST 28H VectorJP 4C31H - overlay loader dispatcher
400FHRST 30H VectorJP 44B4H - DEBUG/error entry
4012HRST 38H VectorJP 4518H - keyboard/timer ISR
402DHDOS READYLD A,93H / RST 28H - invoke DOS command processor overlay
4030HError ExitLD A,A3H / RST 28H - error-already-displayed exit to DOS READY
4033HI/O DispatcherJP 44C4H - device-level I/O dispatch
4378HCONFIG/SYS ProcessorReads and processes CONFIG/SYS file for system configuration
439CHKeyboard ScanScans keyboard matrix, compares against image buffer, dispatches callbacks
4400HDOS READY EntryLD A,93H / RST 28H - SVC stub for command processor
4405HDOS Error ExitLD A,B3H / RST 28H - SVC stub for error handler
44B0HError DisplayPUSH AF / LD A,86H / RST 28H - error display overlay (SVC 86H)
44B4HDEBUG EntryPUSH AF / LD A,87H / RST 28H - DEBUG monitor overlay (SVC 87H)
44C4HI/O Dispatch CoreReads device block via IX, chains through device list
44DDHDisplay Message (Screen)Prints string at HL to screen until 03H or 0DH terminator
44F2HDisplay Message (Printer)Prints string at HL to printer
4518HKeyboard/Timer ISRRST 38H handler: keyboard matrix scan + timer callback dispatch
4578H-458FHTimer Tick CoreDispatches timer callbacks for slots 8-11, increments master tick counter
4593HTimer Callback DispatchLooks up and executes timer callback by slot number in A
45A6HDOS-CALLExecute a DOS command and return
45ABHDequeue TimerRemove timer callback (A=slot, replaces with dequeue stub)
45AEHEnqueue TimerInstall timer callback (A=slot, DE=routine address)
45BDHKeep Drives SpinningReads drive select latch and reissues to maintain motor activity
45C7HClock PrescalerTimer callback: decrements 5-tick prescaler, calls clock cascade
45D4HClock CascadeIncrements seconds/minutes/hours with rollover, increments day on 24H rollover
45F1HBREAK Key to READYIf BREAK+SHIFT pressed, invoke DOS READY (SVC 93H)
4600HDisk I/O DispatcherMaster disk I/O entry: selects drive, dispatches read/write/seek by function code in B
4646HWait for FDC ReadyPolls FDC status register bit 0 (BUSY) until clear
4654HSet Track and SectorWrites track and sector registers, sets up seek
4666HIssue FDC CommandBuilds FDC command from B + drive parameters, writes to command register
4678HSector Read/WriteHandles sector data transfer with FDC via DRQ polling loop
46A4HInline Parameter BlockEX (SP),HL pattern: reads embedded bytes after CALL as FDC parameters
46F7HDrive Number DecodeConverts drive select latch to drive number (0-3)
4750HDrive SelectSelects drive by number in C, loads IY with parameter block pointer
47A0HParameter Block LookupComputes drive parameter block address from drive number
47B0HPosition to RecordPositions FCB to specified record number in BC
47F5HPosition to NextAdvances FCB position by one record
47FEHPosition Back OneMoves FCB position back one record
4826HRewind FilePositions FCB to start of file (sector 0, offset 0)
482DHPosition to EOFPositions FCB to end-of-file position
4883HWrite EOF to DirectoryWrites current FCB position as EOF to directory entry
48A4HRead RecordReads record from file into user buffer via FCB
48C0HWrite RecordWrites record from user buffer to file via FCB
48DBHWrite with VerifyWrites record and reads back to verify
4949HAllocate SectorAllocates next sector for file write, handles sector boundary crossing
4988HCheck Flush NeededTests FCB flags to determine if sector buffer needs writing to disk
49EBHFCB ValidatorValidates FCB, saves context, sets up overlay return
4A15HCheck EOF PositionCompares current file position against EOF
4A3CHRead Sector to BufferReads the current sector for the file position into the FCB buffer
4A8FHExtent Chain ExhaustedHandles overflow past extent table, invokes overlay for allocation
4BA9H16-Bit DivideHL = HL / A, remainder in A
4B8FH24-Bit MultiplyHL:C = DE x C (24-bit result)
4B6CH8-Bit MultiplyA = D x E (8-bit multiply)
4B7BH8-Bit DivideA = E / C, remainder in E
4BBCHKeyboard InputFull keyboard input handler with scan, decode, and SHIFT/BREAK processing
4C31HRST 28H HandlerOverlay loader/dispatcher: checks cache, loads from disk, dispatches
4C53HOverlay Loader CoreReads overlay directory entry, loads sectors into 4E00H+, caches metadata
4CF8HProgram Load CoreProcesses /CMD file records (type 01H/02H/04H/08H/0AH/20H+)
4D8EHGet Next CMD ByteReads next byte from program file, auto-reads sectors as needed
4DB9HTime to HH:MM:SSConverts clock bytes at 4041H-4043H to "HH:MM:SS" display at video RAM 3C35H
4DD4HDate to MM/DD/YYConverts date bytes at 4044H-4046H to display format
4DECHHex Byte DisplayDisplays byte in A as two hex digits at (HL), advances HL
4E00HCold Boot EntryPROGRAM ENTRY: DI, IM 1, SP=41E0H, initialization sequence
4E09HBanner DisplayNEG-decode loop: displays boot banner from video RAM 3C00H area
4E14HBoot InitMemory probe, ISR setup, drive config copy, clock/date initialization
50E0HDrive TimeoutTimer callback: monitors drive activity, deselects after timeout

Self-Modifying Code Locations

WriterTargetPurpose
4593H45BEHCurrent timer callback slot pointer (LD (45BEH),HL stores slot address)
46A7H46F9HFDC data transfer opcode (inline parameter block writes I/O instruction)
46AEH46D4H-46D5HDRQ polling address (inline parameter block writes polling target)
48C3H49C7HWrite verify flag (LD A,00H operand patched to non-zero for verify)
4A50H4B0CHExtent sector accumulator (sector count stored as ADD A operand)
4A92H4AB1HOverlay extent accumulator (saved for cache comparison)
4A9BH4AA3HOverlay ID comparison (current overlay ID stored as LD A operand)
4C3DH4C4DHBREAK key state save (saved during overlay load, restored after)
4C56H4C99HDrive number save (B register saved for overlay load error reporting)
4CADH4AECHExtent offset accumulator
4CF9H4D67HProgram load verify flag (B register saved as comparison operand)
50D2H5005HPatch: writes C3H (JP opcode) at 5005H during CONFIG/SYS processing

Cross-Reference Notes

SYS0 is the foundation module. All SYS overlay files (SYS1 through SYS6+) call into SYS0 routines via the jump table at 400CH-4478H or by direct CALL to internal routines. The RST 28H mechanism at 4C31H loads overlay files from disk on demand. SYS0 calls into the Model I ROM at 001BH (character output), 002BH (keyboard wait), 0033H (character display), 0040H (line input), 0049H (keyboard scan), 0060H (delay/wait), and 03FAH (keyboard decode). The cold boot code copies drive configuration data from the boot sector parameter area (loaded by BOOT/SYS) into the resident drive parameter table at 4700H. After initialization, the boot code area from 4E00H-50FFH is overwritten by the first overlay load.

Disassembly:

3C00H - Boot Banner String

VTOS 4.0 boot banner text stored as plain ASCII. This 128-byte block is displayed during cold boot by the NEG-decode display loop at 4E09H. The text occupies two full lines of the 64-column video display (64 bytes per line). The first line contains the product name and version; the second line contains the copyright notice. The final byte at 3C7FH is 00H (null terminator), which signals the display loop to stop.

3C00-3C3F
DEFM "VTOS - VIRTUAL TECHNOLOGY'S/OPERATING SYSTEM - VER 4.0.2 " 56 54 4F 53 20 2D 20 56 49 52 54 55 41 4C 20 54 45 43 48 4E 4F 4C 4F 47 59 27 53 20 4F 50 45 52 41 54 49 4E 47 20 53 59 53 54 45 4D 20 2D 20 56 45 52 20 34 2E 30 2E 32 20 20 20 20 20 20 20 20
Line 1 of the boot banner. 64 ASCII characters filling one complete video row. Product identification: "VTOS - VIRTUAL TECHNOLOGY'S/OPERATING SYSTEM - VER 4.0.2" followed by spaces to pad to 64 columns. Note the version is 4.0.2, not 4.0 - this is the revision level within the 4.0 release. The apostrophe in "TECHNOLOGY'S" is byte 27H (ASCII single quote).
3C40-3C7F
DEFM "COPYRIGHT (C) 1980 RANDOLPH COOK, ALL RIGHTS RESERVED " 00H 43 4F 50 59 52 49 47 48 54 20 28 43 29 20 31 39 38 30 20 52 41 4E 44 4F 4C 50 48 20 43 4F 4F 4B 2C 20 41 4C 4C 20 52 49 47 48 54 53 20 52 45 53 45 52 56 45 44 20 20 20 20 20 20 20 20 20 20 00
Line 2 of the boot banner. Copyright notice naming Randolph Cook as the author and 1980 as the copyright year. Padded with spaces to 63 characters, followed by a 00H null terminator at 3C7FH. The null byte signals the end of the banner string to the display loop at 4E09H. Note the full first name "RANDOLPH" rather than the shortened "Randy" commonly used in reference materials.

3CD1H - Serial Number Display Prefix

ASCII string "VTOS SERIAL #" used during cold boot to display the disk serial number. This prefix is written to video RAM followed by the actual serial number read from the boot sector directory area.

3CD1-3CDC
DEFM "VTOS SERIAL #" 56 54 4F 53 20 53 45 52 49 41 4C 20 23
12-byte ASCII string "VTOS SERIAL #" displayed during boot at the video RAM position set by the boot initialization code. The '#' character (23H) at the end serves as the visual separator before the serial number digits.

400CH - RST Vector Table and Boot Parameters

The three RST interrupt vectors used by VTOS: RST 28H (overlay dispatcher), RST 30H (DEBUG/error), and RST 38H (keyboard/timer ISR). These are followed by boot-time parameter data copied from the boot sector.

400C
RST 28H Vector
JUMP to the RST 28H overlay loader/dispatcher at 4C31H. This is the primary mechanism for invoking SYS overlay functions. Caller loads Register A with an SVC code, then executes RST 28H. The handler at 4C31H decodes the SVC code, checks the overlay cache, loads the overlay from disk if needed, and dispatches to the appropriate routine.
400F
RST 30H Vector
JUMP to the DEBUG/error entry point at 44B4H. RST 30H is used for entering the DEBUG monitor and for error handling dispatch. The Go command in the DEBUG overlay patches the BREAK key handler to JP 400FH, allowing BREAK to re-enter the debugger during program execution.
4012
RST 38H Vector
JUMP to the keyboard/timer interrupt service routine at 4518H. The Model I generates RST 38H interrupts at approximately 25Hz (driven by the video frame rate). The ISR scans the keyboard matrix, detects key changes, dispatches timer callbacks, and manages the real-time clock.
4015
LD BC,0000H 01 00 00
Boot Parameter Area
3-byte area at 4015H-4017H loaded from the boot sector during BOOT/SYS execution. Contains drive configuration parameters. The value 0000H shown is the initial/default state; the boot code overwrites this with actual disk parameters. Byte 4015H is used as a boot sector data source at 4E48H during cold boot initialization.
4018
NOP x 3 00 00 00
3 bytes of padding/reserved space in the boot parameter area (4018H-401AH). Initialized to zero.
401B
DEFB 4BH,49H,07H 4B 49 07
Boot parameter data at 401BH-401DH. Values 4BH ('K'), 49H ('I'), 07H are boot-time configuration bytes. These values are copied to the drive parameter table during cold boot initialization.

4020H - System Variable Area (Cursor, Device Control)

System variables for video cursor position and device control. These are initialized during cold boot and maintained by the display and I/O routines.

4020
DEFW 3C00H 00 3C
Cursor Position
Current video RAM cursor position stored as a 16-bit address (low byte at 4020H, high byte at 4021H). Initial value 3C00H points to the top-left corner of the video display. Updated by the character output routine and display message functions. The cold boot code at 4EF2H and 4F02H writes computed banner display positions here.
4022
DEFB 00H 00
Padding/reserved byte between cursor position and device name area.
4023
DEFM "DO" 44 4F
ASCII "DO" at 4023H-4024H. Part of device identification data. When combined with surrounding context, this identifies the display output device.
4025
DEFB 06H,00H 06 00
Printer Device Control
Printer device configuration bytes at 4025H-4026H. Value 06H is the device type/mode byte. The display message (printer) routine at 44F2H loads DE from 4025H (LD DE,4025H) to point to this printer control block.
4028
DEFB 42H 42
System configuration byte (42H). Referenced by the overlay loader which checks this area during parameter block setup.
4029
DEFB 00H,00H 00 00
Two zero bytes at 4029H-402AH. Reserved space.
402B
DEFM "PR" 50 52
ASCII "PR" at 402BH-402CH. Printer device name prefix, used in device identification during I/O dispatch.

402DH - DOS READY and Error Exit SVC Stubs

Two compact SVC (Supervisor Call) stubs that invoke overlay functions via RST 28H. These are the most commonly called DOS entry points, used by all overlay files to transfer control back to the command processor or to signal an error.

402D
LD A,93H 3E 93
No-Error Exit / DOS READY
Load Register A with SVC code 93H. This is the DOS READY function: SVC code 93H decodes as function 4 (bits 7-5 = 100), file 2 (bits 4-3 = 10), directory sector 5 (bits 2-0 = 011). This dispatches to the command processor overlay (SYS1), which displays the "VTOS READY" prompt and waits for user input.
402F
RST 28H EF
Invoke the RST 28H overlay dispatcher with SVC code 93H in Register A. This is a dead-end call - the command processor overlay does not return here; it loops waiting for commands and dispatches them directly.
4030
LD A,0A3H 3E A3
Error-Already-Displayed Exit
Load Register A with SVC code A3H. This is the error-already-displayed exit: the error message has already been printed, so the overlay just needs to return control to the DOS READY prompt. SVC code A3H decodes as function 5 (bits 7-5 = 101), file 0 (bits 4-3 = 00), directory sector 5 (bits 2-0 = 011).
4032
RST 28H EF
Invoke the RST 28H overlay dispatcher with SVC code A3H in Register A. Like 402DH, this is a dead-end call that transfers to the command processor after the error has been displayed.
4033
I/O Dispatcher Vector
JUMP to the device I/O dispatcher at 44C4H. This entry point is called with IX pointing to an FCB (File Control Block), HL pointing to a data buffer, and B containing the operation code. The dispatcher at 44C4H reads the device block from the FCB and chains through the device list to find the appropriate handler.

4036H - Keyboard Image Buffer

7-byte keyboard image buffer, one byte per keyboard row. The ISR at 4518H compares current keyboard state against these stored values to detect key changes. Initialized to FFH (all keys released, since keyboard is active-low).

4036-403C
DEFB FFH x 7 FF FF FF FF FF FF FF
Keyboard Image Buffer
7 bytes at 4036H-403CH, one per keyboard matrix row (rows 0-6, memory addresses 3801H-3840H). Each byte stores the last-known state of that row. The keyboard ISR at 439CH-43B4H reads the current state of each row and XORs it against this buffer to detect changes. Value FFH means all bits high = no keys pressed (keyboard is active-low on the Model I). Row 7 (3880H, SHIFT key) is handled separately and is not stored here.

403EH - System State Variables

System state flags and initialization parameters. These bytes are set during cold boot and modified during normal DOS operation.

403E
DEFB 40H 40
System Flags
System state flags byte. Value 40H = bit 6 set = disk subsystem active. This byte is tested by various routines to determine available hardware capabilities.
403F
LD BC,0000H 01 00 00
Three bytes at 403FH-4041H. Initial value 0000H in BC. Byte 4040H is the master tick counter (heartbeat), incremented by the ISR at 458FH on each RST 38H interrupt. Byte 4041H is the seconds counter for the real-time clock, incremented by the clock cascade routine at 45D4H.

404BH - Timer Callback Slot Table and System Capability Flags

Timer callback enable/state table and system capability flags. The timer system uses paired entries (threshold at even offset, counter at odd offset) for managing periodic callbacks.

404B
DEFB 00H,00H 00 00
Timer callback enable mask (404BH) and callback state byte (404CH). The ISR at 4518H reads 404BH to determine which callback slots are active, then reads 404CH to check the slot mask. Value 00H = no active callbacks at initialization.
404D
DEFW 453CH,453CH, 453CH,453CH, 453CH,453CH 3C 45 3C 45 3C 45 3C 45 3C 45 3C 45
Timer Callback Slot Pairs
6 entries x 2 bytes (404DH-4058H). Each pair contains a threshold address (low byte) and the callback handler address (high byte). Default value 453CH points to the ISR's clean exit point (POP AF / POP HL / EI / RET at 4539H-453CH). These are overwritten by the Enqueue Timer routine at 45AEH when callbacks are installed. The cold boot connects the clock prescaler (45C7H) to slot 7 via CALL 4410H.
4059
DEFW 4645H 46 45
Disk I/O Return Vector
2-byte address at 4059H-405AH. Value 4645H points to the end of the Wait for FDC Ready routine (the RET at 4645H). This vector is used as a callback/return address by the disk I/O subsystem.
405B
DEFW 4578H 78 45
Character Output Vector
2-byte address at 405BH-405CH. Value 4578H points to the timer tick core routine. This vector is referenced during I/O operations.

4300H - Drive Parameter Cache and State

Drive parameter cache entries and DOS operational state variables. The drive parameter cache stores the last-known FDC Track Register value for each drive, avoiding unnecessary Restore-to-track-0 operations during drive switches. The state variables track the currently loaded overlay, saved FCB pointers, and BREAK key handling.

4300
RET C9
Drive 0 Default Handler
A RET instruction (C9H) at the start of the drive parameter cache area. This serves as a no-op handler for drive 0 when no drive configuration has been loaded. If code attempts to call through the drive handler table before initialization, the RET harmlessly returns to the caller.
4301
DEFB 00H,00H 00 00
Two zero bytes at 4301H-4302H. Part of the drive 0 parameter cache entry. These are overwritten during boot with drive configuration data.
4303
Unconfigured Drive Error Handler
GOSUB to the error-already-displayed exit at 4030H. This CALL instruction at 4303H-4305H serves as the default error handler for drives that have not been configured. If code attempts disk I/O on an unconfigured drive, execution reaches this CALL which transfers to the error exit. The 3-byte CALL instruction occupies what would be the remaining drive parameter cache bytes for drive 0.

4308H - Drive Selection and Overlay State Variables

Variables tracking the currently selected drive, saved overlay context (return addresses and FCB pointers), the currently loaded overlay ID, and system mode flags.

4308
DEFB FFH FF
Last-Selected Drive
Drive select latch value for the most recently selected drive. Initial value FFH means no drive has been selected yet. The drive select routine at 4621H saves the FDC Track Register to the old drive's cache entry (indexed by this value) before switching to the new drive.
4309
DEFB FFH FF
Drive Select Command Byte
The FDC-format drive select command byte (written to 37E1H). Initial value FFH. Updated by the drive select routine at 462BH when a new drive is activated. The format includes the drive number bits plus stepping rate and motor-on flags.
430A
DEFW 0000H 00 00
Saved FCB Pointer
2-byte saved DE register value (FCB pointer) at 430AH-430BH. The FCB validator at 49F1H stores DE here (LD (430AH),DE) to preserve the caller's FCB pointer across overlay calls. Restored at 4A0CH-4A0EH when the overlay returns.
430C
DEFW 0000H 00 00
Saved Overlay Return Address
2-byte saved HL value at 430CH-430DH. The FCB validator at 49EEH stores HL here (LD (430CH),HL) to preserve the caller's return address. This is the address the overlay should return to after completing its operation.
430E
DEFB 02H 02
Currently Loaded Overlay ID
The SVC code (low 4 bits = overlay file number) of the most recently loaded overlay. Value 02H indicates overlay file 2 is currently loaded. The overlay dispatcher at 4C5EH-4C6BH compares the requested overlay against this value; if they match and the cache is valid, it skips the disk load.
430F
DEFB 00H 00
System Mode Flags
Bit field controlling system behavior. Bit 0: command mode active (01H = in command loop). Bit 1: return-to-caller flag. Bit 2: program load in progress. Bit 3: RS-232/external I/O active. Bit 4: SYS6 (library commands) currently cached. Bit 7: return to DEBUG after error. The cold boot sets this to 01H at 4E90H.
4310
DEFW 4C2FH 2F 4C
Overlay Cache Return
2-byte address at 4310H-4311H used by the overlay cache mechanism. Value 4C2FH points into the overlay dispatcher code. Referenced at 4C28H where LD HL,(4310H) retrieves this value for the cached overlay path.
4312
NOP x 3 00 00 00
BREAK Key Handler Area
3 bytes at 4312H-4314H forming a BREAK key handler instruction. Initial value NOP/NOP/NOP (disabled). When DEBUG is active, this area is patched to contain a JP instruction to the DEBUG entry point, allowing BREAK to re-enter the debugger.

4315H - BREAK Key Enable and Command Buffer

BREAK key enable flag and the DOS command input buffer. The BREAK key flag controls whether pressing BREAK during program execution causes a jump to the BREAK handler or is ignored.

4315
DEFB 00H 00
BREAK Key Enable
BREAK key opcode flag at 4315H. Value 00H = NOP = BREAK handling disabled. Value C3H = JP opcode = BREAK handling enabled (the JP target address is at 4316H-4317H). The ISR at 456BH-4577H tests this byte: if it equals C3H (JP), BREAK triggers a jump to the BREAK handler. The cold boot sets this to C9H (RET) at 4E82H, which effectively disables BREAK by returning immediately.
4316
DEFW 0000H 00 00
BREAK Handler Target
When 4315H contains C3H (JP), bytes 4316H-4317H form the target address for the BREAK handler jump. Set to 400FH (RST 30H vector) when DEBUG is active, causing BREAK to re-enter the debugger.
4318
DEFB 00H 00
Command Buffer Start
First byte of the 32-byte DOS command input buffer (4318H-4337H). The command processor overlay stores user input here. Initial value 00H. The cold boot startup code at 4FCDH-4FD3H copies data into this buffer (LDIR 32 bytes from CONFIG/SYS loading area).
4319
DEFB FFH,C7H FF C7
Bytes 4319H-431AH within the command buffer. Initial values FFH and C7H. These are overwritten during command input. Value C7H could also represent a drive configuration byte (stepping rate + motor flags).

4338H - CONFIG/SYS Filename Template

Filename template for the CONFIG/SYS system configuration file. VTOS attempts to open and process this file during cold boot to apply user-defined system settings automatically.

4338-4346
DEFM "CONFIG/SYS.CC:0" 03H 43 4F 4E 46 49 47 2F 53 59 53 2E 43 43 3A 30 03
CONFIG/SYS Filename
15-byte ASCII filename template "CONFIG/SYS.CC:0" followed by 03H terminator. This is the full VTOS filespec format: 6-character name "CONFIG", "/" separator, 3-character extension "SYS", "." separator, 2-character password "CC", ":" separator, drive specifier "0" (drive 0). The password "CC" provides minimal access protection. The 03H byte is the ETX (End of Text) terminator used by VTOS display routines. This string is passed to the file open routine at 4424H during cold boot.

4378H - CONFIG/SYS Processor

Processes the CONFIG/SYS system configuration file after it has been loaded. Reads clock/date settings from the configuration data and applies them to the system clock variables. This routine is called during cold boot after CONFIG/SYS has been successfully opened and read.

4378
LD A,(43C0H) 3A C0 43
Fetch the first byte of the drive configuration table at 43C0H into Register A. This byte (08H in the disk image) contains drive parameter flags. The CONFIG/SYS processor checks this to determine the current drive configuration state.
437B
XOR A,08H EE 08
XOR Register A with 08H (bit 3). This flips bit 3 of the drive configuration byte. If the original byte had bit 3 set (value 08H), the XOR produces 00H and sets the Z FLAG.
437D
RET Z C8
If the Z FLAG is set (the drive configuration byte was exactly 08H, meaning XOR result is zero), RETURN. This is a guard check: if the drive configuration matches the expected value 08H exactly, skip the CONFIG/SYS processing and return to the caller.
437E
PUSH HL E5
Save Register Pair HL onto the stack. HL contains the caller's context which must be preserved across the CONFIG/SYS processing calls.
437F
LD HL,4391H 21 91 43
Point Register Pair HL to the time display template string at 4391H. This string is "HH:MM:SS " followed by a 03H terminator, used as a template for displaying the current time.
4382
PUSH HL E5
Save Register Pair HL (pointing to 4391H) onto the stack for later retrieval.
4383
GOSUB to the time-to-HH:MM:SS conversion routine at 446DH (which JPs to 4DB9H). This converts the clock bytes at 4041H-4043H into the "HH:MM:SS" format and writes them to the video RAM clock display area at 3C35H.
4386
POP HL E1
Restore Register Pair HL from the stack. HL now points to 4391H again (the time template string).
4387
LD DE,43C0H 11 C0 43
Point Register Pair DE to the drive configuration table at 43C0H. This table contains the current drive parameters which will be processed alongside the time display.
438A
GOSUB to the display message with drive context routine at 4479H. This displays the time template string while referencing the drive configuration table data.
438D
POP HL E1
Restore Register Pair HL from the stack. HL now contains the original caller's HL value that was saved at 437EH.
438E
JUMP to the display message with drive context routine at 4479H. This processes the remaining CONFIG/SYS data and returns to the original caller via the JP (the routine at 4479H ends with RET).

4391H - Time Display Template and Keyboard Scan ISR Core

Time display format template string "HH:MM:SS " followed by the keyboard scan portion of the interrupt service routine. The keyboard scan reads all 7 keyboard rows, compares against the stored image buffer, and dispatches callbacks for any key changes detected.

4391-439B
DEFM "HH:MM:SS " + 03H 48 48 3A 4D 4D 3A 53 53 20 20 03
Time Display Template
11-byte ASCII string "HH:MM:SS " + 03H terminator. The time conversion routine at 4DB9H overwrites the H, M, and S positions with actual digit characters from the clock variables at 4041H-4043H. The colons (:) and trailing spaces are static. The 03H byte is the ETX terminator that signals the display routine to stop printing.

The following code (439CH-43B4H) is the keyboard matrix scan core of the interrupt service routine. It scans all 7 keyboard rows by reading memory-mapped addresses 3801H-3840H, compares each row against the keyboard image buffer at 4036H, and dispatches to the key change handler at 4BBCH if any difference is detected.

439C
Keyboard Scan Entry
GOSUB to 4300H which contains a RET instruction (C9H). This is effectively a no-op call that allows the drive parameter cache area to serve double duty: the RET at 4300H makes this CALL return immediately. This pattern allows future patching - if 4300H is overwritten with a JP to a pre-scan hook, the keyboard scan will call that hook first.
439F
LD HL,4036H 21 36 40
Point Register Pair HL to the keyboard image buffer at 4036H. This 7-byte buffer stores the last-known state of each keyboard row. HL will be used as the comparison pointer during the row scan loop.
43A2
LD BC,3801H 01 01 38
Load Register Pair BC with 3801H. Register C = 01H (keyboard row select bitmask, starting at row 0). Register B = 38H (high byte of keyboard memory-mapped I/O base address 3800H). Together, (BC) addresses keyboard row 0 at 3801H.
43A5
LD D,00H 16 00
Load Register D with 00H. Register D serves as the keyboard row counter, starting at row 0. It will be incremented for each row scanned, reaching 7 when all rows are done.
43A7
LD A,(BC) 0A
Loop Start
Fetch the current state of the keyboard row at address (BC) into Register A. The keyboard hardware is memory-mapped: reading 3801H returns the state of row 0, 3802H returns row 1, 3804H returns row 2, etc. Each bit represents one key in that row (0 = pressed, 1 = released, active-low).
43A8
LD E,A 5F
Copy the current keyboard row state from Register A into Register E. Register E preserves the raw keyboard data for use by the key change handler if a change is detected.
43A9
XOR A,(HL) AE
XOR Register A with the stored keyboard image byte at (HL). The XOR operation produces a non-zero result in any bit position where the current state differs from the stored state - i.e., where a key has been pressed or released since the last scan.
43AA
If the NZ FLAG is set (XOR result is non-zero, meaning at least one key in this row has changed state), JUMP to the key change handler at 4BBCH. Register A contains the change mask (bits set where keys changed), Register D contains the row number, Register E contains the new row state, and (HL) points to the image buffer entry for this row.
43AD
INC D 14
INCrement Register D (row counter) by 1. Advance to the next keyboard row.
43AE
INC L 2C
INCrement the low byte of Register Pair HL by 1. This advances the keyboard image buffer pointer from 4036H to 4037H, then 4038H, etc. - one byte per row.
43AF
RLC C CB 01
Rotate Register C left through carry. This shifts the row select bitmask: 01H -> 02H -> 04H -> 08H -> 10H -> 20H -> 40H -> 80H. The keyboard rows are selected by powers of 2, so rotating left selects the next row. After row 6 (bitmask 40H), the next rotation produces 80H and sets the SIGN flag (bit 7).
43B1
If the SIGN FLAG (M = Minus) is set (Register C has bit 7 set after the RLC, meaning we've rotated past row 6), JUMP to 41E5H. This exits the keyboard scan loop. Address 41E5H is in the boot-time initialization area and contains the continuation code for the ISR's post-scan processing. This address is set up during cold boot by the LDIR at 4E39H-4E40H which copies ISR continuation code from 50E0H to 41E5H.
43B4
Loop End
LOOP BACK to 43A7H to scan the next keyboard row. This loop executes up to 7 times (rows 0-6), exiting either via the JP NZ at 43AAH (key change detected) or via the JP M at 43B1H (all rows scanned).

43C0H - Drive Configuration Table 2

Three 8-byte drive configuration entries (43C0H-43D7H). Each entry has the same layout: byte +00H is a configuration flags byte read by the CONFIG/SYS processor at 4378H; bytes +01H through +05H have unknown purpose; bytes +06H/+07H are a 2-byte drive type identifier code matched and updated by the SPEC command scanner at 5A82H. Bytes +01H through +05H are zero at cold start; their individual meanings are not yet determined. These entries are indexed by the IY register during disk I/O operations.

Drive 0 Entry
8-byte entry for drive 0 (43C0H-43C7H).

43C0
DEFB 08H 08
Byte +00H: Configuration flags byte. The CONFIG/SYS processor at 4378H reads this byte and XORs it with 08H, returning Z if the result is zero. The individual bit definitions are not yet determined.
43C1
DEFB 00H, 00H, 00H, 00H, 00H 00 00 00 00 00
Bytes +01H through +05H: Zero at cold start. Purpose unknown pending further analysis.
43C6
DEFB 4AH, 4CH 4A 4C
Bytes +06H/+07H: Drive type identifier code "JL" (4AH, 4CH). This 2-byte code is the key used by the SPEC command scanner at 5A82H to locate this entry, and is the field written back when the SPEC command updates a drive's configuration.

Drive 1 Entry
8-byte entry for drive 1 (43C8H-43CFH).

43C8
DEFB 10H 10
Byte +00H: Configuration flags byte. Value 10H at cold start. The individual bit definitions are not yet determined.
43C9
DEFB 15H, 40H, 00H, 00H, 00H 15 40 00 00 00
Bytes +01H through +05H: Values 15H, 40H, 00H, 00H, 00H at cold start. Purpose unknown pending further analysis.
43CE
DEFB 53H, 49H 53 49
Bytes +06H/+07H: Drive type identifier code "SI" (53H, 49H). Matched and updated by the SPEC command scanner at 5A82H.

Drive 2 Entry
8-byte entry for drive 2 (43D0H-43D7H).

43D0
DEFB 10H 10
Byte +00H: Configuration flags byte. Value 10H at cold start. The individual bit definitions are not yet determined.
43D1
DEFB 1DH, 40H, 00H, 00H, 00H 1D 40 00 00 00
Bytes +01H through +05H: Values 1DH, 40H, 00H, 00H, 00H at cold start. Purpose unknown pending further analysis.
43D6
DEFB 4CH, 4FH 4C 4F
Bytes +06H/+07H: Drive type identifier code "LO" (4CH, 4FH). Matched and updated by the SPEC command scanner at 5A82H.

4400H - DOS Service Entry Points

The primary DOS service entry points, each implemented as an SVC stub (LD A,xxH / RST 28H) or JP instruction. These provide the public API for all DOS operations. The layout closely mirrors the TRSDOS 2.3 jump table at the same addresses.

4400
LD A,93H 3E 93
4400H - DOS READY
Load Register A with SVC code 93H (DOS READY / command processor). Identical to the SVC stub at 402DH. This entry point at 4400H is the "official" jump table location for the DOS READY function, called by overlays to return control to the command prompt.
4402
RST 28H EF
Invoke the RST 28H overlay dispatcher with SVC code 93H. Loads and enters the command processor overlay. Does not return.

4405H - DOS Error Exit Entry

Error exit entry point that invokes the error handler overlay before returning to DOS READY.

4405
LD A,0B3H 3E B3
4405H - DOS Error Exit
Load Register A with SVC code B3H. This invokes the error handler overlay: SVC code B3H decodes as function 5 (bits 7-5 = 101), file 2 (bits 4-3 = 10), directory sector 5 (bits 2-0 = 011). The error handler overlay displays the appropriate error message based on the error code in Register A, then transfers to DOS READY.
4407
RST 28H EF
Invoke the RST 28H overlay dispatcher with SVC code B3H. This loads the error handler overlay and enters it. The error code should be on the stack (pushed before calling this entry point).

4409H - Jump Table: Error Display through Additional Utility

The main DOS jump table containing JP instructions and SVC stubs for all standard DOS service calls. Each entry occupies 2-3 bytes. Overlay files and user programs access DOS services through these fixed entry points.

4409
4409H - Error Display Entry
JUMP to the error display handler at 44B0H. Called with the error code in Register A. The handler at 44B0H pushes AF and invokes SVC 86H (error display overlay).
440D
440DH - Enter DEBUG
JUMP to the DEBUG entry point at 44B4H. Pushes AF and invokes SVC 87H (DEBUG monitor overlay).
4410
4410H - Enqueue Timer Callback
JUMP to the timer enqueue routine at 45AEH. Register A = slot number (0-11), DE = callback routine address. Installs a timer callback that will be dispatched on each ISR tick.
4413
4413H - Dequeue Timer Callback
JUMP to the timer dequeue routine at 45ABH. Register A = slot number. Removes the callback by replacing it with the dequeue stub address (45BBH).
4416
4416H - Keep Drives Spinning
JUMP to the drive keep-alive routine at 45BDH. Re-issues the current drive select command to prevent motor timeout.
4419
4419H - DOS-CALL
JUMP to the DOS-CALL routine at 45A6H. Executes a DOS command string and returns to the caller.
441C
LD A,0C3H 3E C3
441CH - Extract Filespec
Load Register A with SVC code C3H. This invokes the filespec extraction overlay: decodes as function 6 (bits 7-5 = 110), file 0 (bits 4-3 = 00), directory sector 5 (bits 2-0 = 011).
441E
RST 28H EF
Invoke the RST 28H overlay dispatcher with SVC code C3H. The filespec parser overlay parses a NAME/EXT.PASSWORD:DRIVE string from the command buffer. Returns after parsing.
4420
LD A,0A4H 3E A4
4420H - Open File (New or Existing)
Load Register A with SVC code A4H. Opens a file, creating it if it does not exist. Decodes as function 5 (bits 7-5 = 101), file 0 (bits 4-3 = 00), directory sector 6 (bits 2-0 = 100).
4422
RST 28H EF
Invoke RST 28H with SVC code A4H. Returns to the caller after the file is opened (RET follows in the SVC stub).
4424
LD A,94H 3E 94
4424H - Open Existing File
Load Register A with SVC code 94H. Opens an existing file only; returns error if file not found. Decodes as function 4 (bits 7-5 = 100), file 2 (bits 4-3 = 10), directory sector 6 (bits 2-0 = 100).
4426
RST 28H EF
Invoke RST 28H with SVC code 94H.
4428
LD A,95H 3E 95
4428H - Close File
Load Register A with SVC code 95H. Closes the file referenced by the FCB, writes pending data, updates directory. Decodes as function 4 (bits 7-5 = 100), file 2 (bits 4-3 = 10), directory sector 7 (bits 2-0 = 101).
442A
RST 28H EF
Invoke RST 28H with SVC code 95H.
442C
LD A,9CH 3E 9C
442CH - Kill File
Load Register A with SVC code 9CH. Deletes the file referenced by the FCB, frees allocated disk space, clears directory entry. Note the SVC code 9CH differs from TRSDOS 2.3's A5H for the same function.
442E
RST 28H EF
Invoke RST 28H with SVC code 9CH.
4430
4430H - Load Program File
JUMP to the program loader at 4CE4H. Opens the file and loads it using the /CMD record format processor.
4433
4433H - Load and Execute
JUMP to the load-and-execute routine at 4CCBH. Loads a program file and begins execution at its entry point.
4436
4436H - Read Record
JUMP to the read record routine at 48A4H. Reads the next sequential record from the file into the user buffer.
4439
4439H - Write Record
JUMP to the write record routine at 48C0H. Writes a record from the user buffer to the file.
443C
443CH - Write with Verify
JUMP to the write-with-verify routine at 48DBH. Writes a record and reads it back to verify correct storage.
443F
443FH - Rewind File
JUMP to the rewind routine at 4826H. Positions the FCB to the start of the file (sector 0, offset 0).
4442
4442H - Position to Record
JUMP to the position-to-record routine at 47B0H. Positions the FCB to the specified record number (BC = record number).
4445
4445H - Position Back One
JUMP to the position-back-one routine at 47FEH. Moves the FCB position backward by one record.
4448
4448H - Position to EOF
JUMP to the position-to-EOF routine at 482DH. Positions the FCB to the end-of-file marker.
444B
444BH - Allocate File Space
JUMP to the allocation routine at 4A12H. Allocates additional disk space for the file.
444E
444EH - Write EOF to Directory
JUMP to the EOF write routine at 4883H. Writes the current FCB position as the end-of-file marker in the directory entry.
4451
4451H - Flush Buffer and Allocate
JUMP to the buffer flush and allocate routine at 483FH.
4454
4454H - Read Current Position
JUMP to the current position reader at 481AH. Returns the current file position in registers.
4457
4457H - Decrement Position
JUMP to the position decrement routine at 4993H.
445A
445AH - Get File Size
JUMP to the file size routine at 484EH. Returns the total file size (sector count and byte offset).
445D
445DH - Get EOF Position
JUMP to the EOF position reader at 4875H. Returns the end-of-file position.
4460
4460H - Position to Next Record
JUMP to the next record routine at 47F5H. Advances the FCB position by one record.

4467H - Display and I/O Service Entry Points

Display message, printer, clock/date conversion, and additional utility entry points. These complete the DOS service jump table.

4467
4467H - Display Message (Screen)
JUMP to the screen display message routine at 44DDH. HL points to the string; display continues until 03H (ETX) or 0DH (CR) terminator is encountered.
446A
446AH - Display Message (Printer)
JUMP to the printer display message routine at 44F2H. Same as screen display but output goes to the printer device.
446D
446DH - Convert Time to HH:MM:SS
JUMP to the time conversion routine at 4DB9H. Reads the clock bytes at 4041H-4043H and writes the formatted "HH:MM:SS" string to video RAM at 3C35H.
4470
4470H - Convert Date to MM/DD/YY
JUMP to the date conversion routine at 4DD4H. Reads the date bytes at 4044H-4046H and writes the formatted date string.
4473
LD A,0D3H 3E D3
4473H - Insert Default Extension
Load Register A with SVC code D3H. This invokes the default extension insertion overlay.
4475
RST 28H EF
Invoke RST 28H with SVC code D3H.
4476
LD A,0E3H 3E E3
4476H - Additional Utility
Load Register A with SVC code E3H. This invokes an additional utility overlay function.
4478
RST 28H EF
Invoke RST 28H with SVC code E3H.
4479
Display Message with DE Context
Short JUMP to 44E0H, entering the display message loop with the current DE register as the device context. This is an alternate entry point that skips the LD DE setup done at 44DDH.
447B
GOSUB to the screen display message routine at 44DDH. After the message is displayed, execution continues at 447EH.
447E
Short JUMP to 44F7H (JP 4378H) to continue with CONFIG/SYS processing after display.

4480H - BACKUP/CMD Filename Template

Filename template for the BACKUP/CMD utility program, stored as an ASCII string with drive specifier and terminator.

4480-448A
DEFM "BACKUP/CMD:0" 03H 42 41 43 4B 55 50 2F 43 4D 44 3A 30 03
BACKUP/CMD Filename
13-byte ASCII filename template "BACKUP/CMD:0" + 03H terminator. Format: 6-character name "BACKUP", "/" separator, 3-character extension "CMD", ":" separator, drive specifier "0". No password field. This template is used to load the BACKUP utility from disk. The 03H byte is the ETX terminator.

44A0H - Overlay State Variable Area

Overlay management state variables controlling the overlay cache, load addresses, and execution state.

44A0
DEFB 80H 80
Overlay Parameter Byte
Overlay control parameter at 44A0H. Value 80H (bit 7 set). This flag is checked by the overlay dispatcher to control overlay loading behavior. Bit 7 set indicates the overlay area is available for loading.
44A1
DEFB 00H 00
Overlay Execution State
Overlay execution state flag at 44A1H. Value 00H = no overlay is currently executing. Set to non-zero while an overlay is active to prevent re-entrant overlay calls.
44A2
DEFB 00H,00H 00 00
Reserved bytes at 44A2H-44A3H (overlay state area).
44A4
DEFB 42H 42
Overlay Page Marker
Overlay load page byte at 44A4H. Value 42H indicates the overlay load area starts at page 42H (4200H). This differs from TRSDOS 2.3 which uses page 4DH (4D00H). VTOS overlays load at 4200H, giving them a larger address range before hitting the overlay load boundary at 4E00H.
44A5
NOP x 5 00 00 00 00 00
5 bytes of zero padding at 44A5H-44A9H within the overlay state area.
44AA
DEFB 00H,00H 00 00
Overlay Cache Markers
2-byte overlay cache state at 44AAH-44ABH. Used by the overlay dispatcher at 4C80H to track whether the currently loaded overlay is still valid in the cache. LD (44AAH),HL with HL=0000H clears the cache markers.
44AC
DEFB FFH,FFH FF FF
Overlay Cache Invalidation Markers
2 bytes at 44ACH-44ADH. Value FFFFH serves as "no valid cache" indicator. When the overlay dispatcher compares these against the requested overlay, FFFFH never matches any valid SVC code, forcing a disk reload.
44AE
DEFW 0000H 00 00
Overlay Load Address
2-byte overlay starting sector address at 44AEH-44AFH. Set during overlay loading by the code at 4C95H which reads the directory entry's sector chain start address. Value 0000H indicates no overlay has been loaded yet.

44B0H - Error Display and DEBUG Entry Points

The error display and DEBUG monitor entry points. Both push the flags register (AF) and invoke their respective SVC overlays. The error display chain continues through multiple SVC calls for extended error context display.

44B0
PUSH AF F5
Error Display Entry
Save Register Pair AF (error code in A, flags in F) onto the stack. The error code is preserved for the error display overlay to read after the overlay is loaded.
44B1
LD A,86H 3E 86
Load Register A with SVC code 86H. This invokes the primary error display overlay: function 4 (bits 7-5 = 100), file 0 (bits 4-3 = 00), directory sector 8 (bits 2-0 = 110).
44B3
RST 28H EF
Invoke the RST 28H overlay dispatcher with SVC code 86H. The error display overlay reads the pushed error code and displays the corresponding error message. Execution continues at 44B4H (next instruction) since the overlay returns.
44B4
PUSH AF F5
DEBUG / Secondary Error Entry
Save Register Pair AF onto the stack. Entry point 44B4H is also the RST 30H vector target (JP 44B4H at 400FH) and the DEBUG command entry (JP 44B4H at 440DH).
44B5
LD A,87H 3E 87
Load Register A with SVC code 87H. This invokes the DEBUG/secondary error overlay: function 4 (bits 7-5 = 100), file 0 (bits 4-3 = 00), directory sector 9 (bits 2-0 = 111).
44B7
RST 28H EF
Invoke the RST 28H overlay dispatcher with SVC code 87H.
44B8
LD A,0C4H 3E C4
Load Register A with SVC code C4H. Continuation of the error chain: invokes function 6 (bits 7-5 = 110), file 0 (bits 4-3 = 00), directory sector 6 (bits 2-0 = 100).
44BA
RST 28H EF
Invoke RST 28H with SVC code C4H.
44BB
LD A,0B5H 3E B5
Load Register A with SVC code B5H. Further error chain continuation.
44BD
RST 28H EF
Invoke RST 28H with SVC code B5H.
44BE
LD A,0C9H 3E C9
Load Register A with SVC code C9H. Final error chain step.
44C0
RST 28H EF
Invoke RST 28H with SVC code C9H. After all error overlays have executed, control transfers to DOS READY.

44C1H - Device I/O Dispatcher

The device I/O dispatcher chains through the device block list starting from the FCB's device block pointer. Each device block contains a type code, handler address, and link to the next device. The dispatcher matches the requested operation type and dispatches to the appropriate handler.

44C1
PUSH HL E5
Save Register Pair HL onto the stack. HL contains the data buffer address for the I/O operation.
44C2
POP IX DD E1
Restore the data buffer address from the stack into Register IX. IX now points to the device block structure. This PUSH HL / POP IX sequence transfers HL into IX for indexed addressing.
44C4
LD L,(IX+01H) DD 6E 01
I/O Dispatch Core Entry
Load Register L with the low byte of the next device block address from offset +01H of the current device block (IX). This is the chain link field.
44C7
LD H,(IX+02H) DD 66 02
Load Register H with the high byte of the next device block address from offset +02H. Register Pair HL now points to the next device block in the chain.
44CA
LD A,(IX+00H) DD 7E 00
Load Register A with the device type code from offset +00H of the current device block. This byte identifies the device type (10H = chain link, 08H = read, etc.).
44CD
CP A,10H FE 10
Compare Register A against 10H. If the device type is 10H, this is a chain link entry (follow the pointer to the next device block).
44CF
If the Z FLAG is set (device type is 10H = chain link), LOOP BACK to 44C1H to follow the chain to the next device block. The HL register already contains the address of the next block.
44D1
If the NO CARRY FLAG is set (device type >= 10H but not equal to 10H, i.e., type > 10H), JUMP to the open/random access handler at 48E1H. Device types above 10H indicate file-level operations.
44D4
AND A,08H E6 08
Mask Register A with 08H, isolating bit 3. This tests whether the device type has the "read" bit set.
44D6
XOR A,08H EE 08
XOR Register A with 08H. Flips bit 3. If bit 3 was set (type had read capability), the XOR clears it and sets Z. If bit 3 was clear, the XOR sets it and clears Z.
44D8
RET Z C8
If the Z FLAG is set (the device type had bit 3 set = read-capable), RETURN to the caller. The device I/O operation is complete.
44D9
LD A,B 78
Load Register A with the operation code from Register B. The caller passed the operation type in B.
44DA
CP A,02H FE 02
Compare Register A against 02H. Tests if the operation code is 02H (write operation).
44DB
JP HL E9
JUMP to the address in Register Pair HL. This dispatches to the device handler routine whose address was loaded from the device block chain. The flags from the CP 02H comparison are preserved for the handler to test.

44DDH - Display Message Routines

Screen and printer display message routines. Both print a string starting at HL through a device output vector pointed to by DE. The screen version uses device block at 401DH; the printer version uses device block at 4025H. Characters are printed until a 03H (ETX) or 0DH (CR) terminator is reached.

44DD
LD DE,401DH 11 1D 40
Display Message (Screen) Entry
Point Register Pair DE to the screen device control block at 401DH. This block contains the device handler address chain for the video display output.
44E0
PUSH HL E5
Display Loop Entry
Save Register Pair HL (string pointer) onto the stack. HL will be modified during character output and needs to be preserved for the next character.
44E1
LD A,(HL) 7E
Fetch the next character from the string at (HL) into Register A.
44E2
CP A,03H FE 03
Compare Register A against 03H (ETX, End of Text). If the character is the ETX terminator, the string is complete.
44E4
If the Z FLAG is set (character is 03H = ETX), JUMP to 44F0H to pop HL and return. The string display is complete.
44E6
PUSH AF F5
Save Register Pair AF (current character) onto the stack for comparison after the character output call.
44E7
CALL 001BH CD 1B 00
GOSUB to ROM routine at 001BH to output the character in Register A to the device. This is the Model I ROM character output entry point which dispatches through the device vector.
44EA
POP AF F1
Restore Register Pair AF from the stack. Register A contains the character that was just output.
44EB
INC HL 23
INCrement Register Pair HL by 1. Advance the string pointer to the next character.
44EC
CP A,0DH FE 0D
Compare Register A against 0DH (CR, Carriage Return). CR also serves as a string terminator, but unlike ETX, the CR character itself is output before stopping.
44EE
If the NZ FLAG is set (character was not 0DH = CR), LOOP BACK to 44E1H to fetch and output the next character. If the character was CR, fall through to exit.
44F0
POP HL E1
Restore Register Pair HL from the stack (the original string pointer saved at 44E0H).
44F1
RET C9
RETURN to the caller. The string has been fully displayed.
44F2
LD DE,4025H 11 25 40
Display Message (Printer) Entry
Point Register Pair DE to the printer device control block at 4025H. This selects the printer as the output device instead of the screen.
44F5
Short JUMP to 44E0H to enter the common display loop. The only difference from the screen version is that DE points to the printer device block instead of the screen device block.
44F7
JUMP to the CONFIG/SYS processor at 4378H. This is the return path from the display message with CONFIG/SYS continuation at 447BH-447EH.
44FA
PUSH HL E5
Flush Buffer Helper
Save Register Pair HL onto the stack.
44FB
GOSUB to the check-flush-needed routine at 4988H. Tests the FCB flags to determine if the sector buffer has been modified and needs to be written to disk.
44FE
POP HL E1
Restore Register Pair HL from the stack.
44FF
RET C9
RETURN to the caller with the flush status in the flags register (Z = no flush needed, NZ = flush error).

4500H - Timer Callback Address Table

12 two-byte entries (24 bytes) forming the timer callback address table. Each entry contains the address of a callback routine that is dispatched by the timer tick handler. All entries are initialized to 45BBH, which is the dequeue stub (a 2-byte DEFW pointing to the default no-op return). The Enqueue Timer routine at 45AEH overwrites entries with actual callback addresses.

4500-4517
DEFW 45BBH x 12 BB 45 BB 45 BB 45 BB 45 BB 45 BB 45 BB 45 BB 45 BB 45 BB 45 BB 45 BB 45
Timer Callback Address Table
12 entries x 2 bytes (4500H-4517H). Each DEFW contains the address of a timer callback routine. Default value 45BBH points to the dequeue stub at 45BBH, which is a 2-byte data area containing DEFW 45BAH (a safe return stub). Slot numbering: slots 0-7 at 4500H-450FH, slots 8-11 at 4510H-4517H. The ISR dispatches slots 8-11 via the timer tick core at 4578H-458BH. The cold boot connects the clock prescaler (45C7H) as callback slot 7 via CALL 4410H at 4F97H. Entries are computed as: address = 4500H + (slot_number x 2).

4518H - Keyboard and Timer Interrupt Service Routine

The RST 38H interrupt service routine, entered approximately 25 times per second on the Model I (driven by the video frame rate). This ISR performs three functions: (1) reads the drive select latch to maintain motor activity, (2) scans the timer callback slot table for active entries and dispatches them, and (3) checks for BREAK key combinations. The ISR saves HL and AF on entry and restores them on exit.

4518
PUSH HL E5
ISR Entry
Save Register Pair HL onto the stack. HL is used extensively within the ISR for pointer operations and must be preserved for the interrupted code.
4519
PUSH AF F5
Save Register Pair AF (accumulator and flags) onto the stack. The interrupted code's register state must be fully preserved.
451A
LD HL,37E0H 21 E0 37
Point Register Pair HL to the drive select latch at 37E0H. On the Model I, reading 37E0H returns the current drive select state; writing refreshes the motor-on signal.
451D
LD A,(HL) 7E
Fetch the current drive select latch value from 37E0H into Register A. This reads which drives are currently selected.
451E
OR A,(HL) B6
OR Register A with the value at (HL) again. This is a read-modify-write operation: by reading the latch and ORing it with itself, the result is the same value, but the act of reading the latch address refreshes the drive motor timeout counter in the Expansion Interface hardware.
451F
LD HL,404BH 21 4B 40
Point Register Pair HL to the timer callback enable mask at 404BH. This byte controls which timer callback slots are active.
4522
LD (HL),A 77
Store the drive select latch value (Register A) to 404BH. This saves the current drive activity state as the timer enable mask. Any drive that is currently selected will have its corresponding bit set, enabling the associated timer callback slot.
4523
INC L 2C
INCrement the low byte of HL from 4BH to 4CH. HL now points to 404CH (system capability flags).
4524
AND A,(HL) A6
AND Register A with the system capability flags at 404CH. This masks the drive activity bits against the system capability flags, ensuring only drives that are both selected AND recognized by the system produce active callback bits.
4525
If the Z FLAG is set (no active timer callbacks after masking), JUMP to 452FH to skip the callback dispatch loop and proceed directly to the BREAK key check.
4527
INC L 2C
Callback Scan Loop
INCrement the low byte of HL. Advances through the timer callback slot pair table at 404DH+. Each slot pair consists of 2 bytes (threshold and callback address index).
4528
RRA 1F
Rotate Register A right through carry. Shifts the next callback enable bit into the CARRY flag. If the bit was set (callback active), CARRY is set.
4529
If the CARRY FLAG is set (this callback slot is active), JUMP to 453DH to save registers and dispatch the callback routine.
452B
INC L 2C
INCrement the low byte of HL to skip past the current slot's second byte.
452C
OR A,A B7
OR Register A with itself. Tests whether any more callback enable bits remain set. Also clears the CARRY flag.
452D
If the NZ FLAG is set (more active callback bits remain), LOOP BACK to 4527H to check the next slot. When all bits have been shifted out (A = 0), fall through to the BREAK key check.

At this point, all active timer callbacks have been dispatched. The ISR now checks for the BREAK key combination.

452F
LD A,(3840H) 3A 40 38
Break Key Check
Fetch the keyboard row 6 state from 3840H into Register A. Row 6 contains: ENTER (bit 0), CLEAR (bit 1), BREAK (bit 2), UP (bit 3), DOWN (bit 4), LEFT (bit 5), RIGHT (bit 6), SPACE (bit 7). The keyboard is active-low: bit = 0 means key is pressed.
4532
AND A,04H E6 04
Mask Register A with 04H (bit 2 only). Isolates the BREAK key status. If BREAK is pressed, bit 2 is 0, so the AND result is 00H (Z flag set). If BREAK is not pressed, bit 2 is 1, so the AND result is 04H (NZ flag set).
4534
If the NZ FLAG is set (BREAK key is NOT pressed, bit 2 was 1), JUMP to 4554H to check the SHIFT key state for the SHIFT+BREAK combination. Note: the active-low logic means NZ = key released.
4536
GOSUB to the BREAK-to-READY handler at 45F1H. This routine checks if the special SHIFT+BREAK combination is active by testing keyboard address 3844H for the value 50H. If the combination is detected, it invokes SVC 93H (DOS READY) to abort the current operation and return to the command prompt.
4539
POP AF F1
ISR Clean Exit
Restore Register Pair AF from the stack. The accumulator and flags are returned to their pre-interrupt values.
453A
POP HL E1
Restore Register Pair HL from the stack.
453B
EI FB
Re-enable interrupts. The Z80 automatically disables interrupts when entering an ISR; they must be explicitly re-enabled before returning.
453C
RET C9
RETURN from the interrupt service routine. Execution resumes at the point where the interrupt occurred.

The following code (453DH-454BH) handles dispatching a single timer callback. Registers are saved, the callback address is loaded from the slot pair table, and control transfers to the callback via JP HL. The callback returns to 454CH where registers are restored and the scan continues.

453D
PUSH AF F5
Timer Callback Dispatch
Save Register Pair AF onto the stack. Register A contains the remaining callback enable bits that still need to be checked after this callback returns.
453E
PUSH BC C5
Save Register Pair BC onto the stack. The callback routine may modify BC.
453F
PUSH DE D5
Save Register Pair DE onto the stack.
4540
PUSH HL E5
Save Register Pair HL onto the stack. HL currently points to the active slot in the callback table.
4541
PUSH IX DD E5
Save Register IX onto the stack. IX may be used by the callback routine.
4543
LD DE,454CH 11 4C 45
Point Register Pair DE to the callback return address 454CH. This is where execution will resume after the callback routine returns.
4546
PUSH DE D5
Save the return address (454CH) onto the stack. This becomes the return address for the callback - when the callback executes RET, it returns to 454CH.
4547
LD E,(HL) 5E
Load Register E with the low byte of the callback routine address from the slot pair at (HL).
4548
INC L 2C
INCrement the low byte of HL to point to the high byte of the callback address.
4549
LD D,(HL) 56
Load Register D with the high byte of the callback routine address.
454A
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now contains the callback routine address; DE contains the table pointer (no longer needed).
454B
JP HL E9
JUMP to the callback routine address in HL. The callback will execute and return (via the RET instruction) to 454CH, the address that was pushed onto the stack at 4546H.
454C
POP IX DD E1
Callback Return Point
Restore Register IX from the stack. The callback routine has completed and returned here.
454E
POP HL E1
Restore Register Pair HL from the stack. HL is restored to its position in the callback table.
454F
POP DE D1
Restore Register Pair DE from the stack.
4550
POP BC C1
Restore Register Pair BC from the stack.
4551
POP AF F1
Restore Register Pair AF from the stack. Register A is restored with the remaining callback enable bits.
4552
LOOP BACK to 452BH to continue scanning the callback table for remaining active slots.

The following code (4554H-4577H) handles the BREAK key processing. When BREAK is pressed (detected at 4534H), this section checks the SHIFT key state, tests the BREAK enable flag, and optionally dispatches to the BREAK handler vector.

4554
LD A,(3880H) 3A 80 38
Shift Key Check
Fetch the keyboard row 7 state from 3880H into Register A. Row 7 contains only the SHIFT key (bit 0). Active-low: bit 0 = 0 means SHIFT is pressed.
4557
RRCA 0F
Rotate Register A right through carry. Shifts bit 0 (SHIFT key state) into the CARRY flag. If SHIFT is pressed (bit 0 = 0), CARRY is clear. If SHIFT is not pressed (bit 0 = 1), CARRY is set.
4558
If the NO CARRY FLAG is set (SHIFT key IS pressed, bit 0 was 0), JUMP to 4561H for the SHIFT+BREAK processing path. If CARRY is set (SHIFT not pressed, regular BREAK), fall through to the drive keep-alive code.
455A
PUSH BC C5
Save Register Pair BC onto the stack.
455B
GOSUB to the drive select routine at 4750H. When BREAK is pressed without SHIFT, the ISR refreshes the drive motor by calling the drive select/keep-alive code. This prevents disk motor timeout during long keyboard wait loops.
455E
POP BC C1
Restore Register Pair BC from the stack.
455F
JUMP to 4539H for the ISR clean exit (POP AF, POP HL, EI, RET).
4561
LD A,(430FH) 3A 0F 43
Break Handler Dispatch
Fetch the system mode flags from 430FH into Register A. These flags control how the BREAK key is handled.
4564
BIT 4,A CB 67
Test bit 4 of Register A (system mode flags). Bit 4 indicates that SYS6 (library commands) is currently cached. If set, BREAK is suppressed to prevent interrupting overlay-critical operations.
4566
If bit 4 is set (SYS6 cached / overlay operation in progress), JUMP to 4539H for clean exit. BREAK is suppressed during overlay operations.
4568
LD HL,4315H 21 15 43
Point Register Pair HL to the BREAK key enable flag at 4315H.
456B
LD A,0C3H 3E C3
Load Register A with C3H (the JP opcode). This is the expected value at 4315H when BREAK handling is enabled.
456D
SUB A,(HL) 96
SUBtract the value at 4315H from Register A (C3H). If 4315H contains C3H (BREAK enabled), the result is 00H (Z flag set). If 4315H contains any other value (BREAK disabled), the result is non-zero.
456E
If the NZ FLAG is set (BREAK handling is disabled, 4315H did not contain C3H), JUMP to 4539H for clean exit. No BREAK handler is invoked.
4570
LD (HL),A 77
Self-Modifying Code
Store Register A (which is 00H after the SUB) to 4315H. This disables the BREAK handler by writing 00H (NOP) over the JP opcode. This is a one-shot mechanism: BREAK fires once and then disables itself to prevent re-entrant BREAK handling.
4571
LD HL,(4316H) 2A 16 43
Fetch the BREAK handler target address from 4316H-4317H into Register Pair HL. This is the address that was installed as the BREAK handler (typically 400FH to enter DEBUG).
4574
POP AF F1
Restore Register Pair AF from the stack (the AF that was pushed at 4519H).
4575
EX (SP),HL E3
Exchange (SP) with HL. The top of stack contains the saved HL (pushed at 4518H). After the exchange, the BREAK handler address is on the stack (where the ISR will "return" to), and HL is restored to the interrupted code's HL value.
4576
EI FB
Re-enable interrupts before transferring to the BREAK handler.
4577
RET C9
RETURN - but the return address has been replaced with the BREAK handler address by the EX (SP),HL at 4575H. So instead of returning to the interrupted code, execution transfers to the BREAK handler (typically 400FH = RST 30H vector = DEBUG entry). This is a clever stack manipulation that redirects the ISR return to the BREAK handler.

4578H - Timer Tick Core and Timer Management

The timer tick core dispatches timer callback slots 8-11, increments the master tick counter, and provides the timer enqueue/dequeue/keep-alive routines. This code is reached from the keyboard scan exit path (via the ISR continuation code at 41E5H which was copied from 50E0H during boot).

4578
LD A,08H 3E 08
Timer Tick Core Entry
Load Register A with 08H (timer callback slot 8). This is the first of four consecutive callback dispatches for the "system" timer slots (8-11).
457A
GOSUB to the timer callback dispatch routine at 4593H with A=08H. Dispatches timer callback slot 8.
457D
LD A,09H 3E 09
Load Register A with 09H (timer callback slot 9).
457F
GOSUB to 4593H with A=09H. Dispatches timer callback slot 9.
4582
LD A,0AH 3E 0A
Load Register A with 0AH (timer callback slot 10).
4584
GOSUB to 4593H with A=0AH. Dispatches timer callback slot 10.
4587
LD A,0BH 3E 0B
Load Register A with 0BH (timer callback slot 11).
4589
GOSUB to 4593H with A=0BH. Dispatches timer callback slot 11.
458C
LD HL,4040H 21 40 40
Point Register Pair HL to the master tick counter at 4040H.
458F
INC (HL) 34
INCrement the master tick counter at 4040H. This counter advances once per ISR call (~25 times per second) and provides the system heartbeat for time-based operations. It wraps from FFH to 00H every ~10.2 seconds.
4590
LD A,(HL) 7E
Fetch the updated master tick counter value into Register A.
4591
AND A,07H E6 07
Mask Register A with 07H (bits 0-2 only). This extracts the low 3 bits of the tick counter, producing a value 0-7 that cycles through 8 states. This is used as an additional callback slot index for dispatching one of the first 8 callback slots (0-7) on each tick, spreading their execution across 8 consecutive ticks. Fall through to 4593H to dispatch the selected slot.

The following routine (4593H-45A5H) is the timer callback dispatch core. It converts a slot number in Register A into a table address, loads the callback routine address from the table, and jumps to it. The same routine is used for both the system slots (8-11, called from 4578H-458BH) and the rotating slots (0-7, called from 4591H via fall-through).

4593
RLCA 07
Timer Callback Dispatch
Rotate Register A left (multiply by 2). Each timer callback slot occupies 2 bytes in the table at 4500H, so the slot number must be doubled to get the byte offset.
4594
LD L,A 6F
Load Register L with the doubled slot offset from Register A.
4595
LD H,45H 26 45
Load Register H with 45H. Combined with L, HL now points to the callback address entry in the table at 4500H + (slot x 2). For slot 0: HL=4500H, slot 1: HL=4502H, etc.
4597
LD (45BEH),HL 22 BE 45
Self-Modifying Code
Store Register Pair HL (the current slot pointer) to 45BEH. This saves the slot pointer for use by the DOS-CALL and Dequeue routines at 45A6H-45AAH, which need to know which slot was most recently dispatched. The target 45BEH is within the Keep Drives Spinning routine's LD HL instruction at 45BDH.
459A
LD E,(HL) 5E
Load Register E with the low byte of the callback address from the table entry at (HL).
459B
INC L 2C
INCrement Register L to point to the high byte of the callback address.
459C
LD D,(HL) 56
Load Register D with the high byte of the callback address. Register Pair DE now contains the complete callback routine address.
459D
PUSH DE D5
Save the callback address (DE) onto the stack, then immediately transfer to IX.
459E
POP IX DD E1
Load Register IX with the callback address from the stack (PUSH DE / POP IX = transfer DE to IX).
45A0
EX DE,HL EB
Exchange DE and HL. HL now contains the callback address; DE now contains the table pointer.
45A1
LD E,(HL) 5E
Load Register E with the low byte of the callback's internal state/parameter from (HL). The callback address points to a 2-byte structure: the first word is the actual routine address. This reads the low byte.
45A2
INC HL 23
INCrement HL to point to the high byte of the callback's routine address.
45A3
LD D,(HL) 56
Load Register D with the high byte. DE now contains the actual routine entry point to call.
45A4
EX DE,HL EB
Exchange DE and HL. HL now contains the routine entry point.
45A5
JP HL E9
JUMP to the callback routine at the address in HL. The routine will execute and return (RET) to the caller of this dispatch routine (either the tick core at 4578H or the fall-through from 4591H).

45A6H - DOS-CALL, Dequeue, and Enqueue Timer

Three timer management routines: DOS-CALL (execute command and return), Dequeue Timer (remove callback from slot), and Enqueue Timer (install callback into slot). These are accessed via the jump table at 4419H, 4413H, and 4410H respectively.

45A6
POP DE D1
DOS-CALL Entry
Pop the return address from the stack into Register DE. This is the address to return to after the DOS command executes.
45A7
LD A,(45BEH) 3A BE 45
Fetch the current timer callback slot pointer (low byte) from the self-modifying location at 45BEH into Register A. This was stored by the dispatch routine at 4597H.
45AA
RRCA 0F
Rotate Register A right (divide by 2). Converts the byte offset back to a slot number.
45AB
LD DE,45BBH 11 BB 45
Dequeue Timer Entry
Point Register Pair DE to the dequeue stub address 45BBH. When a callback slot is dequeued, its table entry is overwritten with this address, which points to a safe no-op stub.
45AE
CP A,0CH FE 0C
Enqueue Timer Entry
Compare Register A against 0CH (12 decimal). If the slot number is >= 12, it's out of range for the 12-slot table.
45B0
RET NC D0
If the NO CARRY FLAG is set (slot number >= 12, out of range), RETURN without modifying the table. This is a bounds check to prevent writing beyond the 12-entry table.
45B1
RLCA 07
Rotate Register A left (multiply by 2). Convert slot number to byte offset in the table.
45B2
LD L,A 6F
Load Register L with the doubled slot offset.
45B3
LD H,45H 26 45
Load Register H with 45H. HL now points to the slot entry at 4500H + (slot x 2).
45B5
DI F3
Disable interrupts. The table update must be atomic to prevent the ISR from reading a half-updated entry.
45B6
LD (HL),E 73
Store the low byte of the callback address (Register E) to the slot entry at (HL).
45B7
INC L 2C
INCrement Register L to point to the high byte of the slot entry.
45B8
LD (HL),D 72
Store the high byte of the callback address (Register D) to the slot entry.
45B9
EI FB
Re-enable interrupts. The table update is complete.
45BA
RET C9
RETURN to the caller.
45BB
DEFW 45BAH BA 45
Dequeue Stub
2-byte data area containing the address 45BAH (the RET instruction at 45BAH). When a timer callback slot is dequeued, its entry is set to 45BBH. The dispatch routine at 459AH reads the value at 45BBH (which is BAH,45H = 45BAH), then the double-indirect mechanism reads from 45BAH which is the RET at 45BAH. This effectively makes dequeued slots execute a harmless RET.

45BDH - Keep Drives Spinning and Clock Cascade

The drive keep-alive routine reads the current drive select state and reissues it to prevent motor timeout. This is followed by the clock prescaler (5-tick divider) and the seconds/minutes/hours cascade routine that maintains the real-time clock.

45BD
LD HL,0000H 21 00 00
Keep Drives Spinning
Load Register Pair HL with 0000H. Self-Modifying Code
The operand 0000H at 45BEH-45BFH is overwritten by the timer dispatch routine at 4597H with the current callback slot pointer. At runtime, HL contains the address of the active timer slot, not 0000H. The initial 0000H is never actually used.
45C0
LD E,(HL) 5E
Load Register E with the low byte of the drive parameter address from the slot entry at (HL).
45C1
INC HL 23
INCrement HL to point to the high byte.
45C2
LD D,(HL) 56
Load Register D with the high byte. DE now contains the drive parameter block address.
45C3
EX DE,HL EB
Exchange DE and HL. HL now points to the drive parameter data.
45C4
POP DE D1
Pop the return address from the stack into DE (restoring caller context).
45C5
JUMP to 45B5H to update the timer slot entry with the new drive parameter address. This refreshes the keep-alive callback with the current drive state via the DI/LD/EI sequence.

45C7H - Clock Prescaler and Clock Cascade

Timer callback routine connected to slot 7 during cold boot. The prescaler divides the ~25Hz ISR rate by 5 to produce a 5Hz tick, then calls the clock cascade to increment seconds/minutes/hours. The calendar limit table at 45E6H defines the rollover values.

45C7
JP Z,0545H CA 45 05
Clock Prescaler Callback
This 3-byte sequence at 45C7H-45C9H is the callback entry structure. The bytes CA 45 05 are interpreted as a DEFW-pair by the dispatch mechanism: the first word (4945H from bytes CA,45 read in reverse = 45CAH) is the actual routine entry point. The JP Z,0545H instruction is never executed as a jump - it is read as data by the callback dispatch chain. The actual routine starts at 45CAH.
45CA
DEC (IX+02H) DD 35 02
Prescaler Decrement
DECrement the prescaler counter at offset +02H from Register IX (which points to the callback's parameter block). This counter starts at 05H and counts down to 0. At ~25Hz ISR rate, this produces a 5Hz tick (25/5 = 5 seconds-ticks per second).
45CD
RET NZ C0
If the NZ FLAG is set (prescaler has not reached zero yet), RETURN. The clock does not advance on this tick. Only 1 in every 5 ISR ticks produces a clock update.
45CE
LD (IX+02H),05H DD 36 02 05
Reset the prescaler counter to 05H. The next 5 ISR ticks will count down to zero again before the clock advances.
45D2
LD B,03H 06 03
Clock Cascade Entry
Load Register B with 03H. Register B is the cascade loop counter: 3 iterations for seconds -> minutes -> hours.
45D4
LD HL,4041H 21 41 40
Point Register Pair HL to the seconds counter at 4041H. The clock variables are stored consecutively: seconds at 4041H, minutes at 4042H, hours at 4043H.
45D7
LD DE,45E6H 11 E6 45
Point Register Pair DE to the calendar limit table at 45E6H. This table contains the rollover values: 3CH (60) for seconds, 3CH (60) for minutes, 18H (24) for hours.
45DA
INC (HL) 34
Loop Start
INCrement the current time unit at (HL) by 1. First iteration: increment seconds. If seconds was 3BH (59), it becomes 3CH (60).
45DB
LD A,(DE) 1A
Fetch the rollover limit for this time unit from the calendar limit table at (DE) into Register A. First iteration: A = 3CH (60) for seconds.
45DC
SUB A,(HL) 96
SUBtract the current time unit value at (HL) from Register A (the limit). If the time unit equals the limit (e.g., seconds = 60), the result is 00H and the Z flag is set, indicating rollover is needed.
45DD
RET NZ C0
If the NZ FLAG is set (the time unit has NOT reached its limit), RETURN. No rollover needed - the clock update is complete. For example, if seconds went from 30 to 31, there is no rollover and we return immediately.
45DE
LD (HL),A 77
Store Register A (which is 00H after the SUB result was zero) to (HL). This resets the time unit to 0 after rollover. For example, seconds rolling over from 59 to 60 is reset to 0.
45DF
INC L 2C
INCrement Register L to advance to the next time unit. From seconds (4041H) to minutes (4042H), or from minutes to hours (4043H).
45E0
INC E 1C
INCrement Register E to advance to the next limit in the calendar table. From seconds limit (45E6H) to minutes limit (45E7H), etc.
45E1
DECrement Register B and LOOP BACK to 45DAH if not zero. This cascades: if seconds rolled over, try incrementing minutes; if minutes rolled over, try incrementing hours.

If all 3 cascade levels rolled over (hours went from 23 to 24, reset to 0), execution falls through here. This means a full day has elapsed (midnight rollover). The code increments the day counter.

45E3
INC L 2C
INCrement Register L. After the loop, HL was at 4043H (hours) + final INC L -> 4044H (day counter). HL now points to the day counter at 4044H.
45E4
INC (HL) 34
INCrement the day counter at 4044H. A new day has begun. The day-of-month and month rollover handling is done elsewhere (during the boot date calculation, not in real-time). This simply advances the day count for the system calendar.
45E5
RET C9
RETURN to the timer dispatch caller. The clock cascade is complete.

45E6H - Calendar Limit Table and Utility Stubs

The calendar limit table defines rollover values for the seconds/minutes/hours cascade. This is followed by a 16-bit divide call stub and a BREAK-to-READY handler.

45E6
DEFB 3CH 3C
Calendar Limit Table
Value 3CH (60 decimal) for seconds maximum. When the seconds counter reaches 60, it rolls over to 0 and increments minutes.
45E7
DEFB 3CH 3C
Value 3CH (60 decimal) for both minutes maximum. When minutes reaches 60, it rolls over to 0 and increments hours.
45E8
DEFB 18H 18
Hours maximum at 45E8H. Value 18H (24 decimal). When hours reaches 24, it rolls over to 0 and increments the day counter.
45E9
DEFB 00H 00
Padding byte at 45E9H. The JR 45EAH at 45E8H-45E9H is also decoded as the bytes 18H,00H which form a JR +0 (jump to the next instruction) - a 2-byte NOP. This allows the calendar table to overlap with the code at 45EAH.
45EA
16-Bit Divide Stub
GOSUB to the 16-bit divide routine at 4BA9H. This stub provides an alternate entry point for HL = HL / A division. Called from the date calculation code.
45ED
DEFB 45H, 4CH, 67H 45 4C 67
Three data bytes at 45EDH-45EFH. As instructions: LD B,L / LD C,H / LD H,A. These transfer the division result: B=quotient low, C=quotient high (from HL), H=remainder (from A). This post-processes the divide result for the caller.
45F0
RET C9
RETURN to the caller with the division results in B, C, and H.
45F1
LD A,(3844H) 3A 44 38
Break to Ready Handler
Fetch the keyboard state at address 3844H into Register A. Address 3844H is the combined state of keyboard rows where the SHIFT+BREAK combination would produce the value 50H. This is a specific hardware check for the VTOS "abort to READY" key combination.
45F4
CP A,50H FE 50
Compare Register A against 50H. Value 50H is the expected keyboard state when the SHIFT+BREAK key combination is active at address 3844H.
45F6
RET NZ C0
If the NZ FLAG is set (the keyboard state does not match the SHIFT+BREAK combination), RETURN. The BREAK-to-READY abort is not triggered.
45F7
LD A,93H 3E 93
Load Register A with SVC code 93H (DOS READY). The SHIFT+BREAK combination has been detected.
45F9
RST 28H EF
Invoke the RST 28H overlay dispatcher with SVC code 93H. This aborts the current operation and transfers directly to the DOS READY command prompt. This is a dead-end call - it does not return to the ISR. The stacked ISR registers are abandoned on the stack (the command processor sets up a new stack).

4600H - Disk I/O Dispatcher

The master disk I/O entry point. Register B contains the function code that selects the operation: B=01H (drive select), B=05H (step in), B=06H (set track/sector + seek), B=07H (wait for ready), B=08H (read sector, single-density), B=0AH (read sector, double-density), B=0EH (write sector), B=0FH (write with verify), and others. Bit 3 of B distinguishes between drive control (bit 3 clear) and sector read/write (bit 3 set). The IY register points to the current drive's configuration parameter block in the table at 43C0H+.

4600
LD A,B 78
Disk I/O Entry
Copy the function code from Register B into Register A for comparison.
4601
BIT 3,B CB 58
Test bit 3 of Register B (function code). Bit 3 distinguishes between drive control operations (bit 3 = 0, functions 01H-07H) and sector data transfer operations (bit 3 = 1, functions 08H+).
4603
If bit 3 is set (function code >= 08H = sector read/write operation), JUMP to the sector read/write handler at 4678H.
4605
CP A,07H FE 07
Compare Register A against 07H. Function 07H = "wait for FDC ready".
4607
If the Z FLAG is set (function is 07H), JUMP to the Wait for FDC Ready routine at 4646H.
4609
CP A,06H FE 06
Compare Register A against 06H. Function 06H = "set track/sector and seek".
460B
If the Z FLAG is set (function is 06H), JUMP to the Set Track and Sector routine at 4654H.
460D
DEC A 3D
DECrement Register A by 1. If A was 01H (drive select), it becomes 00H and Z is set.
460E
If the Z FLAG is set (original function was 01H = drive select), JUMP to the Drive Select handler at 4621H.
4610
INC (IY+05H) FD 34 05
INCrement byte at offset +05H in the current drive's parameter block (pointed to by IY). This is the track position cache - incrementing it prepares for a step-in operation.
4613
CP A,04H FE 04
Compare Register A against 04H. After the DEC at 460DH, A=04H means the original function was 05H (step in with verify).
4615
LD B,58H 06 58
Load Register B with 58H. This is the FDC Step In command byte: 58H = 01011000 binary = Step In with update track register, head load, 15ms step rate.
1771 FDC Command: 58H (01011000)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
01011000Command=Step In
Bit 7-5: Step In Command (010)
T: 1=Update Track Register
h: 1=Head Load (engage head)
V: 0=No Verify after step
r1,r0: 00=6ms stepping rate
4617
If the Z FLAG is set (function 05H = step in), JUMP to the FDC command issue routine at 4666H with B=58H (Step In command).
4619
LD (IY+05H),00H FD 36 05 00
Store 00H to offset +05H of the drive parameter block. This resets the track position cache to track 0, preparing for a Restore command.
461D
LD B,08H 06 08
Load Register B with 08H. This is the FDC Restore command byte: 08H = 00001000 = Restore with head load, 15ms step rate (stepping rate bits = 00 = fastest).
1771 FDC Command: 08H (00001000)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
00001000Command=Restore
Bit 7-4: Restore Command (0000)
h: 1=Head Load (engage head)
V: 0=No Verify after restore
r1,r0: 00=6ms stepping rate
461F
JUMP to the FDC command issue routine at 4666H with B=08H (Restore command). This sends the FDC head to track 0.

4621H - Drive Select Handler

Selects a new drive by waiting for the FDC to become ready, then writing the drive select command byte from the drive parameter block to the drive select latch (37E1H) and to the drive select cache (4309H). Optionally delays for head settle if the drive configuration requires it.

4621
Drive Select Entry
GOSUB to the Wait for FDC Ready routine at 4646H. Ensures the FDC is not busy before changing the drive select. The CARRY flag on return indicates whether the FDC was already idle (NC) or had to wait (C).
4624
RLCA 07
Rotate Register A left. Shifts the CARRY flag (from the FDC ready check) into bit 0 of A, preserving it for the RET NC check below.
4625
LD A,(IY+04H) FD 7E 04
Fetch the drive select command byte from offset +04H of the current drive's parameter block (IY) into Register A. This byte contains the drive number bits combined with stepping rate and motor control flags.
4628
LD (37E1H),A 32 E1 37
Write the drive select command byte to the drive select latch at 37E1H. On the Model I, addresses 37E0H-37E3H all map to the same physical drive select latch. Writing here activates the selected drive's motor and head.
462B
LD (4309H),A 32 09 43
Store the drive select command byte to the drive select cache at 4309H. This records the currently active drive select state for use by the ISR drive keep-alive at 451AH-451EH.
462E
RET NC D0
If the NO CARRY FLAG is set (the FDC was already ready, no wait was needed), RETURN immediately. No head settle delay is required since the drive was already spinning.
462F
LD A,(430FH) 3A 0F 43
Fetch the system mode flags from 430FH into Register A. The CARRY flag was set (FDC had to wait = drive was not previously active), so a head settle delay may be needed.
4632
BIT 3,A CB 5F
Test bit 3 of the system mode flags. Bit 3 indicates RS-232/external I/O is active, which requires a different delay behavior.
4634
If bit 3 is set (RS-232 active), GOSUB to 4637H for the delay with RS-232 considerations. If bit 3 is clear, fall through to 4637H anyway (the CALL is conditional but 4637H is the next instruction).
4637
BIT 2,(IY+03H) FD CB 03 56
Test bit 2 of byte at offset +03H in the drive parameter block. This bit indicates whether the drive requires a head settle delay after selection.
463B
If the Z FLAG is set (bit 2 is clear = head settle delay IS required), GOSUB to the delay routine at 463EH. If bit 2 is set (no delay needed), skip the delay.
463E
PUSH BC C5
Head Settle Delay
Save Register Pair BC onto the stack.
463F
LD B,7FH 06 7F
Load Register B with 7FH (127 decimal). This is the delay loop counter for the head settle time.
4641
CALL 0060H CD 60 00
GOSUB to the ROM delay routine at 0060H. This routine loops with BC as the counter, providing a calibrated delay. With B=7FH, this produces approximately 15-20ms of delay, sufficient for the drive head to settle after track changes.
4644
POP BC C1
Restore Register Pair BC from the stack.
4645
RET C9
RETURN to the caller. The drive is now selected, spinning, and the head has settled.

4646H - Wait for FDC Ready

Polls the FDC status register at 37ECH until bit 0 (BUSY) is clear, indicating the FDC has completed its current operation. Continuously reissues the drive select command to keep the motor running during the wait.

4646
LD A,(37ECH) 3A EC 37
FDC Ready Poll Loop
Fetch the FDC status register from 37ECH (WD1771 Command/Status Register on the Model I) into Register A.
4649
BIT 0,A CB 47
Test bit 0 of the FDC status register. Bit 0 is the BUSY flag: 1 = FDC is executing a command, 0 = FDC is idle and ready for a new command.
464B
RET Z C8
If the Z FLAG is set (bit 0 = 0, FDC is NOT busy), RETURN. The FDC is ready for a new command. The CARRY flag is clear on this exit path.
464C
LD A,(4309H) 3A 09 43
Fetch the drive select command byte from the cache at 4309H into Register A.
464F
LD (37E1H),A 32 E1 37
Re-write the drive select command to the drive select latch at 37E1H. This refreshes the motor-on signal to prevent the drive motor from timing out while waiting for the FDC to finish. Without this refresh, the Expansion Interface would turn off the motor during long operations.
4652
LOOP BACK to 4646H to poll the FDC status register again. This loop continues until the FDC becomes ready.

4654H - Set Track, Sector and Seek

Writes the target track number and sector number to the FDC track and sector registers, then issues a Seek command. Register DE contains the track (D) and sector (E) to seek to. The IY register points to the current drive's parameter block.

4654
Set Track/Sector Entry
GOSUB to Wait for FDC Ready at 4646H. The FDC must be idle before writing to its registers.
4657
LD A,(IY+05H) FD 7E 05
Fetch the cached track position from offset +05H of the drive parameter block into Register A. This is the track number where the FDC head currently resides.
465A
LD (37EDH),A 32 ED 37
Write the cached track position to the FDC Track Register at 37EDH. This tells the FDC what track it is currently on, which is necessary for the Seek command to calculate the correct number of steps.
465D
LD (37EEH),DE ED 53 EE 37
Write Register Pair DE to the FDC Sector Register at 37EEH and Data Register at 37EFH. Register E (sector number) goes to 37EEH (Sector Register); Register D (target track) goes to 37EFH (Data Register). For a Seek command, the FDC reads the target track from the Data Register.
4661
LD (IY+05H),D FD 72 05
Store Register D (target track number) to offset +05H of the drive parameter block. This updates the track position cache to reflect the new track position after the seek completes.
4664
LD B,18H 06 18
Load Register B with 18H. FDC Seek command byte: 18H = 00011000 = Seek with head load, 15ms step rate. Fall through to 4666H to issue the command.
1771 FDC Command: 18H (00011000)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
00011000Command=Seek
Bit 7-4: Seek Command (0001)
h: 1=Head Load (engage head)
V: 0=No Verify after seek
r1,r0: 00=6ms stepping rate
4666
Issue FDC Command
GOSUB to the drive select handler at 4621H. This ensures the drive is selected and ready before issuing the FDC command.
4669
LD A,(IY+03H) FD 7E 03
Fetch the FDC configuration flags from offset +03H of the drive parameter block into Register A. The low 2 bits contain the stepping rate override for this drive.
466C
AND A,03H E6 03
Mask Register A with 03H (bits 0-1 only). Extracts the stepping rate bits from the drive configuration.
466E
OR A,B B0
OR Register A with Register B (the FDC command byte). This merges the drive-specific stepping rate bits into the command. For example, Seek 18H OR'd with stepping rate 03H = 1BH (Seek with 15ms step rate).
466F
LD (37ECH),A 32 EC 37
Write the complete FDC command byte to the FDC Command Register at 37ECH. The FDC immediately begins executing the command (Seek, Restore, Step, etc.).
4672
LD B,08H 06 08
Load Register B with 08H. This is a delay counter for the post-command delay loop at 4674H.
4674
DJNZ $ 10 FE
DECrement B and jump to self (DJNZ $). This is a tight delay loop that wastes 8 iterations (approximately 50 microseconds) to give the FDC time to begin processing the command and assert the BUSY flag before any subsequent status polling.
4676
XOR A,A AF
Set Register A to ZERO and clear all flags. Register A = 00H indicates success (no error).
4677
RET C9
RETURN to the caller with A=00H (success). The FDC command has been issued and is now executing asynchronously.

4678H - Sector Read/Write Handler

Handles sector data transfers (read and write). Determines the FDC command based on the function code in Register A, sets up the inline parameter blocks for the FDC operation, and executes the data transfer via the DRQ polling loop. Function codes: 08H/0AH = read, 0EH = write, 0FH = write with verify. Register C carries the FDC command byte; Register B carries the retry count.

4678
BIT 2,B CB 50
Sector Transfer Entry
Test bit 2 of Register B (function code). Bit 2 distinguishes between read operations (bit 2 = 0, functions 08H-0BH) and write operations (bit 2 = 1, functions 0CH-0FH).
467A
LD B,05H 06 05
Load Register B with 05H. Default retry count = 5 attempts for read operations.
467C
If bit 2 is set (write operation), JUMP to 4692H to set up the write command.
467E
LD C,88H 0E 88
Load Register C with 88H. FDC Read Sector command: 88H = 10001000 = Read Sector, IBM format, single record, head load enabled.
1771 FDC Command: 88H (10001000)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
10001000Command=Read Sector
Bit 7-5: Read Command (100)
m: 0=Single Record
b: 1=IBM format
E: 0=Assume Head Already Engaged, no Delay
Remainder: Unused (00)
4680
CP A,0AH FE 0A
Compare Register A against 0AH. Function 0AH = read sector with preceding seek operation.
4682
If the Z FLAG is set (function is 0AH = read with seek), JUMP to 468CH, skipping the seek setup since the seek was already done.
4684
LD B,0AH 06 0A
Load Register B with 0AH. Increased retry count = 10 attempts (for operations that need a seek first, more retries are allowed).
4686
Inline Parameter Block 1
GOSUB to the inline parameter block reader at 46A4H. The bytes immediately following this CALL instruction are read as parameters, not executed as code.
4689
DEFB 01H,1AH,02H 01 1A 02
Inline Parameters
3 bytes read by the EX (SP),HL mechanism at 46A4H. Byte 01H is the I/O operation code. Bytes 1AH,02H (DEFW 021AH) form a 16-bit address parameter. These configure the seek operation that precedes the sector read.
468C
Inline Parameter Block 2
GOSUB to 46A4H for the second set of inline parameters.
468F
DEFB 01H,1AH,00H 01 1A 00
Inline Parameters
3 bytes: operation 01H, address 001AH. These configure the sector buffer read operation parameters.
4692
LD C,0A8H 0E A8
Write Command Setup
Load Register C with A8H. FDC Read Sector with verify: A8H = 10101000. However, this is immediately overwritten if the function is actually a write.
1771 FDC Command: A8H (10101000)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
10101000Command=Write Sector
Bit 7-5: Write Command (101)
m: 0=Single Record
b: 1=IBM format
E: 0=No Delay
a1,a0: 00=Normal Data Address Mark
4694
CP A,0EH FE 0E
Compare Register A against 0EH. Function 0EH = write sector (without verify).
4696
If the CARRY FLAG is set (function < 0EH, which is a read verify), JUMP to 469EH with the A8H read-verify command in C.
4698
LD C,0A9H 0E A9
Load Register C with A9H. FDC Write Sector command: A9H = 10101001 = Write Sector, IBM format, single record, normal DAM (Data Address Mark).
1771 FDC Command: A9H (10101001)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
10101001Command=Write Sector
Bit 7-5: Write Command (101)
m: 0=Single Record
b: 1=IBM format
E: 0=No Delay
a1,a0: 01=Normal Data Address Mark
469A
If the Z FLAG is set (function is exactly 0EH = write), JUMP to 469EH with C=A9H.
469C
LD C,0F4H 0E F4
Load Register C with F4H. FDC Write Track (Format) command: F4H = 11110100. Function 0FH = format track operation.
1771 FDC Command: F4H (11110100)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
11110100Command=Write Track (Format)
Bit 7-4: Write Track Command (1111)
Bit 3: 0=Reserved
E: 1=15ms Head Settling Delay
Bit 1-0: 00=Unused
469E
Inline Parameter Block 3
GOSUB to the inline parameter block reader at 46A4H for the sector data transfer parameters.
46A1
DEFB 09H 09
Inline Parameter
I/O operation code 09H, followed by 2-byte address. These are the first 3 bytes of the inline parameter block read by 46A4H. They configure the DRQ polling loop and data transfer direction.
46A2
DEFB 0AH,12H 0A 12
Remaining inline parameter bytes: 0AH and 12H. These configure the I/O transfer address offset and direction for the sector data movement.

46A4H - Inline Parameter Block Reader

The core inline parameter block mechanism used by the disk I/O subsystem. Uses EX (SP),HL to read bytes embedded immediately after the CALL instruction, then patches the DRQ polling loop and data transfer code with the extracted parameters. This is the same technique used in TRSDOS 2.3 at 4672H.

46A4
EX (SP),HL E3
Inline Parameter Reader Entry
Exchange (SP) with HL. The top of stack contains the return address from the CALL, which points to the first byte of the inline parameter block. After the exchange, HL points to the parameter data, and the saved HL value is on the stack.
46A5
LD A,(HL) 7E
Fetch the first parameter byte from the inline block at (HL) into Register A. This is typically an I/O operation code or data transfer instruction opcode.
46A6
INC HL 23
INCrement HL to point to the next parameter byte.
46A7
LD (46F9H),A 32 F9 46
Self-Modifying Code
Store the extracted parameter byte to 46F9H. This patches the data transfer instruction within the DRQ polling loop at 46F8H-46F9H. The patched byte becomes the opcode for the actual I/O transfer (e.g., LD A,(BC) for read, LD (DE),A for write).
46AA
LD A,(HL) 7E
Fetch the second parameter byte (low byte of DRQ polling address) into Register A.
46AB
INC HL 23
INCrement HL to point to the third parameter byte.
46AC
LD H,(HL) 66
Load Register H with the third parameter byte (high byte of DRQ polling address).
46AD
LD L,A 6F
Load Register L with the second parameter byte (low byte). HL now contains the 16-bit DRQ polling target address.
46AE
LD (46D4H),HL 22 D4 46
Self-Modifying Code
Store Register Pair HL to 46D4H-46D5H. This patches the DRQ polling loop's data transfer target address. The code at 46D3H-46D6H becomes a data transfer instruction operating on the address in HL.
46B1
POP HL E1
Restore Register Pair HL from the stack (the original HL value saved by the EX (SP),HL at 46A4H). The return address has been consumed by reading the parameter bytes; the new "return" address (past the parameter block) is now the correct continuation point.

The following code (46B2H-46DAH) is the FDC command execution and DRQ polling loop. It optionally sets track/sector, waits for FDC ready, issues the FDC command, then loops transferring data bytes via DRQ (Data Request) polling until the FDC is no longer busy.

46B2
PUSH BC C5
FDC Command Execute
Save Register Pair BC onto the stack. B = retry count, C = FDC command byte.
46B3
BIT 4,C CB 61
Test bit 4 of Register C (FDC command byte). Bit 4 in the context of Read/Write Sector commands indicates whether a preceding seek is needed.
46B5
If bit 4 is clear (seek needed), GOSUB to the Set Track and Sector routine at 4654H to position the FDC head on the correct track and sector.
46B8
PUSH DE D5
Save Register Pair DE (data buffer address) onto the stack.
46B9
PUSH HL E5
Save Register Pair HL onto the stack.
46BA
LD HL,37ECH 21 EC 37
Point Register Pair HL to the FDC Status/Command Register at 37ECH. HL will be used for polling the FDC status during the data transfer.
46BD
LD DE,37EFH 11 EF 37
Point Register Pair DE to the FDC Data Register at 37EFH. DE is used as the FDC data port address for reading/writing sector bytes.
46C0
GOSUB to Wait for FDC Ready at 4646H. Ensure the FDC is idle before issuing the new command.
46C3
LD A,C 79
Copy the FDC command byte from Register C into Register A.
46C4
GOSUB to 466FH to issue the FDC command. This writes the command byte (with drive-specific stepping rate merged) to the FDC Command Register and executes the post-command delay.
46C7
POP BC C1
Restore Register Pair BC from the stack. BC now contains the saved HL (data buffer address for the DRQ loop).
46C8
PUSH BC C5
Push BC back onto the stack (preserving it for after the transfer loop).
46C9
JUMP to 46CEH to enter the DRQ polling loop.
46CB
RRCA 0F
DRQ Poll Loop Body
Rotate Register A right through carry. Shifts bit 1 (DRQ) into the CARRY flag for fast testing.
46CC
If the NO CARRY FLAG is set (bit 1 was 0, DRQ is NOT active = FDC has no data to transfer), JUMP to 46DAH to check if the FDC command is complete.
46CE
LD A,(HL) 7E
DRQ Poll Loop Entry
Fetch the FDC status register from (HL) = 37ECH into Register A. This polls the FDC for the DRQ (Data Request) signal.
46CF
BIT 1,A CB 4F
Test bit 1 of the FDC status. Bit 1 is DRQ: 1 = the FDC has a byte ready to transfer (read) or needs a byte (write).
46D1
If the Z FLAG is set (DRQ is NOT active), LOOP BACK to 46CBH to rotate and check the completion status. The tight loop between 46CBH and 46D1H polls both DRQ and BUSY.
46D3
DI F3
Disable interrupts. The data transfer between FDC and memory must not be interrupted, as the FDC has strict timing requirements for DRQ servicing.
46D4
NOP 00
Self-Modifying Code
This byte at 46D4H is overwritten by LD (46D4H),HL at 46AEH with the low byte of the data transfer target address. At runtime, bytes 46D4H-46D6H form a 3-byte instruction that transfers data between the FDC Data Register (37EFH via DE) and the sector buffer (address patched at 46AEH). The initial NOP values are never executed - they are always overwritten before the DRQ loop runs.
46D5
NOP 00
Second byte of the self-modifying data transfer instruction. Overwritten with the high byte of the transfer target address.
46D6
INC BC 03
INCrement Register Pair BC (sector buffer pointer) by 1. Advances to the next byte position in the sector buffer after each byte transfer.
46D7
JUMP BACK to 46CEH to poll for the next DRQ. The loop continues: poll status -> transfer byte -> increment pointer -> poll again, until the FDC command completes.
46DA
EI FB
Transfer Complete Check
Re-enable interrupts. The FDC either has no more DRQ requests or the command is complete.
46DB
LD A,(HL) 7E
Fetch the FDC status register from (HL) = 37ECH into Register A one more time for final status analysis.
46DC
AND A,7CH E6 7C
Mask Register A with 7CH (01111100 binary). This extracts the error bits from the FDC status: bit 2 (Lost Data), bit 3 (CRC Error), bit 4 (Record Not Found), bit 5 (Record Type), bit 6 (Write Protect). Bits 0 (BUSY) and 1 (DRQ) are masked out.
1771 FDC Status Mask: 7CH (01111100)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
01111100Type II Status Error Mask
Bit 6: Write Protected
Bit 5: Record Type (Deleted Data)
Bit 4: Record Not Found
Bit 3: CRC Error
Bit 2: Lost Data
Bits 1,0: Masked out (DRQ, BUSY)
46DE
POP HL E1
Restore Register Pair HL from the stack (data buffer address).
46DF
POP DE D1
Restore Register Pair DE from the stack.
46E0
POP BC C1
Restore Register Pair BC from the stack (B = retry count, C = FDC command).
46E1
RET Z C8
If the Z FLAG is set (AND result was zero = no error bits set), RETURN with A=00H (success). The sector transfer completed without errors.
46E2
BIT 2,A CB 57
Test bit 2 of the error status (Lost Data). Lost Data means the FDC DRQ was not serviced in time and data was lost.
46E4
If bit 2 is set (Lost Data error), LOOP BACK to 46B2H to retry the operation immediately. Lost Data is a soft error that often succeeds on retry.
46E6
PUSH AF F5
Save the error status (Register A) onto the stack for later analysis.
46E7
AND A,18H E6 18
Mask Register A with 18H (bits 3 and 4). Isolates CRC Error (bit 3) and Record Not Found (bit 4). These are errors that may benefit from a recalibration (Restore to track 0) before retrying.
46E9
If the Z FLAG is set (no CRC or RNF error - the error is something else like Write Protect), JUMP to 46F6H to check the retry counter without recalibrating.
46EB
BIT 4,A CB 67
Test bit 4 (Record Not Found). If RNF is set, the head may be on the wrong track.
46ED
PUSH BC C5
Save Register Pair BC onto the stack.
46EE
If bit 4 is set (Record Not Found), GOSUB to 4619H which issues a Restore command (FDC head returns to track 0). This recalibrates the head position to recover from possible track misalignment.
46F1
POP BC C1
Restore Register Pair BC from the stack.
46F2
POP AF F1
Restore the error status (Register A) from the stack.
46F3
DECrement Register B (retry counter) and LOOP BACK to 46B2H if not zero. Each retry re-executes the full FDC command sequence. When B reaches zero, all retries are exhausted.
46F5
LD B,0F1H 06 F1
Load Register B with F1H. After all retries are exhausted, this value is used to encode the final error status. F1H is never reached as a retry count - it's a marker value.
46F6
LD B,A 47
Copy the error status from Register A into Register B for the error return path. The error code in A will be converted to a VTOS error code.
46F7
LD A,00H 3E 00
Self-Modifying Code
Load Register A with 00H. The operand at 46F8H-46F9H is overwritten by LD (46F9H),A at 46A7H with the I/O operation code. At this point in the error path, this instruction loads A with whatever value was patched during setup.
46FA
RRC B CB 08
Rotate Register B (error status) right through carry. Shifts the error bits to convert the FDC status format into a VTOS error code format.
46FC
RET C D8
If the CARRY FLAG is set (a specific error bit was shifted into carry), RETURN with the converted error code. This is a fast exit for certain error types.
46FD
INC A 3C
INCrement Register A by 1. Advances the error code counter for each error bit that was not the one we're looking for.
46FE
LOOP BACK to 46FAH to continue rotating and counting error bits. This loop converts the FDC bit-position error status into a sequential VTOS error code number.

4700H - Drive Configuration Parameter Block Table

Table of drive configuration parameter blocks, with up to 8 entries of 10 bytes each (80 bytes total). Each block starts with a JP instruction to the drive's I/O handler (typically JP 4600H), followed by 7 bytes of drive-specific configuration: flags, select command byte, track cache, stepping rate, and sector geometry. The disassembler has decoded these data bytes as instructions, but they are non-executable data. Blocks 0-3 are populated during cold boot via the LDIR at 4F8AH-4F90H which copies 70 bytes (46H) from directory sector data at 422AH into 470AH. Block 0 is initialized separately. Blocks 4-7 are null (RET + zeros).
The FORMAT/CMD and BACKUP commands use direct IY-offset read and write operations. The IY register is loaded with the base address of a drive's 10-byte block via the routine at 47A0H, which multiplies the drive number (0–7) by 10 and adds 4700H. Every (IY+N) access maps one-for-one to a block offset.

Drive Block 0

4700
I/O Handler Vector
JUMP to the disk I/O dispatcher at 4600H. The first 3 bytes of each drive parameter block form a JP instruction, making the block callable as a subroutine. When the IY dispatch at 478DH (JP IY) executes, IY points to this JP, which redirects to the main disk I/O engine. This provides hardware abstraction: different drives could have different handler addresses.
4703
DEFB 83H 83
Drive Flags (Offset +03H)
Value 83H = 10000011 binary. Bit 7: drive present. Bit 6: double-sided configured. Bit 5: 40-track (vs 35-track). Bit 4: 256-byte sectors. Bit 3: double-density capable. Bit 2: motor-on status. Bits 1:0: WD1771 stepping rate (00=6ms … 11=15ms/30ms). This byte is read by the FDC command builder at 4669H which masks bits 0-1 and ORs them into the FDC command.
BitMeaning
0-1WD1771 stepping rate: 00=6ms, 01=10ms, 10=12ms (or 20ms on 1791), 11=15ms (or 30ms on 1791). Bits 0–1 are masked and inserted directly into the FDC TYPE I command's r1:r0 bits.
2Motor-on delay needed: bit=0 means the head motor needs a spin-up delay; bit=1 means motor already running
3Double-density (MFM) capable: bit=1 = drive supports double density. FORMAT checks this to decide whether to attempt double-density formatting. When set, the FORMAT routine doubles the sector-in-track count at 6253H
4256-byte vs 128-byte sector flag: bit=1 = 256-byte sectors (size code 1, result = 01H after the shift); bit=0 = 128-byte sectors (size code 0). The four right-rotates extract this bit to the LSB as the WD1771 sector-length field
5Track count selector: bit=0 = 35-track drive; bit=1 = 40-track drive. The two table entries at 711DH differ by 7 bytes, representing single-/double-sided, 35/40 track combinations
6Double-sided flag: bit=1 = double-sided drive. FORMAT sets this after user confirms double-sided format, clears it otherwise
7Drive present/active flag: bit=1 = drive is physically present and active. Blocks 4–7 (RET + zeros) have +03H = 00H confirming absent
4704
DEFB 01H 01
Drive Select (Offset +04H)
Value 01H = drive 0 select bit. Written to the drive select latch at 37E1H by the drive select handler at 4628H. Bits 3:0: one-hot drive select written to port 37E1H. Bit 5: double-density enabled this session. Bit 6: double-sided enabled this session. Bits 7:5: upper bits extracted into track sector-ID descriptor.
BitMeaning
0-3Drive select bits: bit 0=drive 0, bit 1=drive 1, bit 2=drive 2, bit 3=drive 3. Written directly to the drive-select latch
5Double-density enabled flag: bit=1 = this drive is configured for double-density operation this session. FORMAT sets this at 6F23H after user confirms, reads it back at 60F2H, 7009H to control MFM track writing
6Double-sided enabled flag: bit=1 = drive currently configured for double-sided access. FORMAT tests this at 6EB9H to determine if side-1 tracks need to be written
7Upper bits → track descriptor: bits 7:5 are extracted by AND 0E0H then rotated to bits 2:0 (3× RLCA, AND 07H) and combined with the IY+08H value at 6314H–631DH to form the first byte of the formatted track's sector ID field. Bit 7 is likely the drive's "sides" count encoding
4705
DEFB 01H 01
Track Cache (Offset +05H)
Cached FDC Track Register value for this drive. Value 01H means the head was last positioned at track 1. Updated by the seek routine at 4661H and read during drive select at 4657H to avoid unnecessary restore-to-track-0 operations. Shadow of FDC Track Register for this drive. Saved on deselect, restored on select. Updated by seek routine. Compared against +06H during FORMAT to detect final track
4706
DEFB 22H 22
Last Track # (Offset +06H)
Value 22H (34 decimal). Last track number, 0-based (= tracks-per-side − 1). 22H = 34 → 35-track drive. Format loop terminates when track cache (+05H) reaches this value. Written from format parameter tables.
4707
DEFB 09H 09
Sectors/Track + Interleave (Offset +07H)
Bits 4:0: sectors-per-track minus 1 (09H → 10 sectors/track). Bits 7:5: sector interleave factor. Directly used to build FORMAT track descriptor.
BitMeaning
0-4When 1FH is ANDed and 1 is added, this gives the sectors per track. Stored value 09H → 09 & 1F = 9 → +1 = 10 sectors per track.
5-7After XOR with D. The XOR A,D then 3× RLCA then INC A sequence extracts bits 7:5 and converts them into an interleave factor or sector-skew value, which is passed to CALL 6210H (a byte-multiply routine). This encodes the sector interleave factor — how far apart consecutive logical sectors are placed on the physical track to improve sequential read speed.
4708
DEFB 24H 24
Sector Size Code (Offset +08H)
Bits 4:2: WD1771 N field (sector size: 0=128B, 1=256B, 2=512B, 3=1024B). 24H → bits 4:2 = 001 → 256 bytes/sector. Additional bits encode related parameters for double-sided formatting. Rotated into FDC sector address field.
4709
DEFB 11H 11
Sector Register Cache (Offset +09H)
Cache of FDC Sector Register for this drive. Initialized to 17 (directory track sector). Updated after every sector access. Used by FORMAT as the starting sector number for write-track passe. The initial value 11H (17 decimal) in drives 1–3 is exactly the TRS-80 directory track number (track 17 = 11H on standard TRSDOS disks). The OS initializes this to the directory track so that the first file-system access on a freshly-selected drive starts by reading the directory rather than scanning from track 0. For drive 0 the value is also 11H at offset +09H.

Drive blocks 1-3 follow the same 10-byte layout. Each begins with JP 4600H and has drive-specific configuration bytes. These blocks are overwritten during cold boot by the LDIR at 4F8AH which copies 70 bytes of configuration data from the boot sector's directory area (422AH) to 470AH.

Drive :1 Block

470A
JUMP to the disk I/O handler vector for Drive :1.
470D
DEFB 03H 03
Drive Flags.
470E
DEFB 02H 02
Drive Select.
470F
DEFB 11H 11
Track Cache.
4710
DEFB 22H 22
Last Track #.
4711
DEFB 09H 09
Sectors/Track + Interleave.
4712
DEFB 24H 24
Sector Size Code.
4713
DEFB 11H 11
Sector Register Cache.

Drive :2 Block

4714
JUMP to the disk I/O handler vector for Drive :2.
4717
DEFB 03H 03
Drive Flags.
4718
DEFB 04H 04
Drive Select.
4719
DEFB 11H 11
Track Cache.
471A
DEFB 22H 22
Last Track #.
471B
DEFB 09H 09
Sectors/Track + Interleave.
471C
DEFB 24H 24
Sector Size Code.
471D
DEFB 11H 11
Sector Register Cache.

Drive :3 Block

471E
JUMP to the disk I/O handler vector for Drive :3.
4721
DEFB 03H 03
Drive Flags.
4722
DEFB 08H 08
Drive Select.
4723
DEFB 11H 11
Track Cache.
4724
DEFB 22H 22
Last Track #.
4725
DEFB 09H 09
Sectors/Track + Interleave.
4726
DEFB 24H 24
Sector Size Code.
4727
DEFB 11H 11
Sector Register Cache.

Drive blocks 4-7 are null entries for drives not present on standard Model I hardware. Each block starts with a RET instruction (C9H) as a safe null handler, followed by 9 zero bytes. The RET ensures that if code calls through the I/O handler vector for an unconfigured drive, it returns harmlessly.

4728
RET C9
Drive Block 4 - Null Handler
RET instruction serves as a no-op I/O handler for unconfigured drive 4. If JP (IY) dispatches to this address, the RET immediately returns to the caller.
4729-4731
NOP x 9 00 x 9
9 zero bytes completing drive block 4 (4729H-4731H). All parameters are zero for this unconfigured drive.
4732
RET C9
Drive Block 5 - Null Handler
RET instruction for unconfigured drive 5.
4733-473B
NOP x 9 00 x 9
9 zero bytes completing drive block 5 (4733H-473BH).
473C
RET C9
Drive Block 6 - Null Handler
RET instruction for unconfigured drive 6.
473D-4745
NOP x 9 00 x 9
9 zero bytes completing drive block 6 (473DH-4745H).
4746
RET C9
Drive Block 7 - Null Handler
RET instruction for unconfigured drive 7.
4747-474F
NOP x 9 00 x 9
9 zero bytes completing drive block 7 (4747H-474FH). This is the end of the 80-byte drive parameter block table.

4750H - Drive Select and Disk I/O Wrappers

A set of wrapper routines that save BC, load a specific disk I/O function code into B, and enter the common dispatcher at 477AH. The dispatcher saves IY, loads IY with the drive parameter block pointer for the drive number in Register C, executes the disk I/O operation via JP (IY), restores IY and BC, and returns. Each wrapper occupies 4-5 bytes (PUSH BC / LD B,xxH / JR 477AH). These wrappers are called by the file management and directory I/O routines throughout SYS0.

4750
LD A,(4308H) 3A 08 43
Drive Select Entry (4750H)
Fetch the last-selected drive identifier from the cache at 4308H into Register A. This entry point is called by the ISR BREAK handler at 455BH to refresh the drive motor, and by other routines that need to reselect the current drive.
4753
LD C,A 4F
Copy the drive identifier from Register A to Register C. Register C carries the drive number through to the common dispatcher at 477AH.
4754
PUSH BC C5
Save Register Pair BC onto the stack. The caller's BC must be preserved across the disk I/O operation.
4755
LD B,01H 06 01
Load Register B with 01H (function code: drive select). This tells the disk I/O dispatcher at 4600H to select the drive and spin up the motor.
4757
JUMP to the common drive I/O dispatcher at 477AH.
4759
PUSH BC C5
Wait for FDC Ready (4759H)
Save Register Pair BC onto the stack.
475A
LD B,07H 06 07
Load Register B with 07H (function code: wait for FDC ready). This polls the FDC status register until the BUSY bit clears.
475C
JUMP to 477AH.
475E
PUSH BC C5
Set Track/Sector and Seek (475EH)
Save Register Pair BC onto the stack.
475F
LD B,06H 06 06
Load Register B with 06H (function code: set track/sector registers and issue Seek command). Register DE must contain the target track (D) and sector (E).
4761
JUMP to 477AH.
4763
PUSH BC C5
Write Sector with Seek (4763H)
Save Register Pair BC onto the stack.
4764
LD B,0DH 06 0D
Load Register B with 0DH (function code: write sector with preceding seek to the correct track/sector).
4766
JUMP to 477AH.
4768
PUSH BC C5
Write with Verify (4768H)
Save Register Pair BC onto the stack.
4769
LD B,0EH 06 0E
Load Register B with 0EH (function code: write sector followed by read-back verify to confirm the data was written correctly).
476B
JUMP to 477AH.
476D
PUSH BC C5
Format Track (476DH)
Save Register Pair BC onto the stack.
476E
LD B,0FH 06 0F
Load Register B with 0FH (function code: write track / format). This issues the WD1771 Write Track command (F4H) to format an entire track.
4770
JUMP to 477AH.
4772
PUSH BC C5
Read Sector with Seek (4772H)
Save Register Pair BC onto the stack.
4773
LD B,0AH 06 0A
Load Register B with 0AH (function code: read sector with preceding seek to the correct track/sector).
4775
JUMP to 477AH.
4777
PUSH BC C5
Read Sector without Seek (4777H)
Save Register Pair BC onto the stack.
4778
LD B,09H 06 09
Load Register B with 09H (function code: read sector without seek - assumes the head is already positioned on the correct track). Falls through to 477AH.
477A
LD A,C 79
Common Drive I/O Dispatcher
Copy the drive number from Register C into Register A. All 8 wrapper routines converge here with B = function code and C = drive number.
477B
LD (4308H),A 32 08 43
Store the drive number to the last-selected drive cache at 4308H. This updates the record of which drive is currently active for the ISR drive keep-alive at 451AH-451EH.
477E
PUSH IY FD E5
Save Register IY onto the stack. IY will be loaded with the new drive's parameter block pointer, but the caller's IY must be preserved.
4780
GOSUB to the parameter block loader at 478FH. This computes the parameter block address for the drive in Register C and loads IY with it. Returns with IY pointing to the drive's 10-byte parameter block at 4700H + (drive x 10).
4783
LD A,20H 3E 20
Load Register A with 20H (32 decimal). This value serves as a status/flag setup before the I/O dispatch.
4785
OR A,A B7
OR Register A with itself. This sets the NZ flag (since A = 20H, non-zero) and clears the CARRY flag. The I/O handler uses these flag states on entry.
4786
GOSUB to the JP (IY) dispatch at 478DH. This executes JP (IY) which jumps to the first instruction in the drive parameter block - the JP 4600H that redirects to the disk I/O engine. The I/O handler processes the operation in Register B and returns here.
4789
POP IY FD E1
Restore Register IY from the stack (the caller's original IY value).
478B
POP BC C1
Restore Register Pair BC from the stack (the caller's original BC value).
478C
RET C9
RETURN to the caller with the disk I/O result in Register A and the flags register.
478D
JP IY FD E9
IY Dispatch
JUMP to the address in Register IY. IY points to the first byte of the drive parameter block, which is a JP 4600H instruction. This indirect dispatch provides hardware abstraction: different drive types can have different I/O handlers by placing a different JP target in their parameter block. For standard drives, all blocks point to the same handler at 4600H.

478FH - Parameter Block Loader and Address Calculator

Loads Register IY with the parameter block address for the drive specified in Register C. Also provides a byte-read helper at 4797H that reads a single byte from any field within a drive's parameter block, and the core address calculator at 47A0H that computes 4700H + (drive x 10).

478F
PUSH HL E5
Parameter Block Loader
Save Register Pair HL onto the stack. HL will be used to hold the computed parameter block address temporarily.
4790
GOSUB to the parameter block address calculator at 47A0H. Returns with HL pointing to the parameter block for the drive in Register C.
4793
EX (SP),HL E3
Exchange (SP) with HL. The parameter block address (in HL) goes onto the stack; the saved HL value (the caller's HL) is restored into HL. This is a standard pattern for transferring a computed value through the stack without corrupting the caller's registers.
4794
POP IY FD E1
Pop the parameter block address from the stack into Register IY. IY now points to the start of the drive's 10-byte parameter block, enabling IY-indexed access to all fields (IY+00H through IY+09H) throughout the disk I/O code.
4796
RET C9
RETURN to the caller with IY loaded with the parameter block pointer.
4797
PUSH HL E5
Read Parameter Block Byte
Save Register Pair HL onto the stack. Entry: Register A = high byte (used as page selector), Register C = drive number. Exit: Register A = byte read from the drive's parameter block.
4798
LD H,A 67
Copy Register A into Register H. Register H temporarily holds the page/offset selector while 47A0H is called.
4799
GOSUB to the parameter block address calculator at 47A0H. Returns with HL = parameter block address for drive C. However, Register A (low byte of computed address) is also available.
479C
LD L,A 6F
Load Register L with the computed address low byte from Register A. Combined with H (which was set from the input parameter), HL now points to the specific byte within the parameter block to read.
479D
LD A,(HL) 7E
Fetch the byte at the computed address (HL) into Register A. This reads the requested field from the drive's parameter block.
479E
POP HL E1
Restore Register Pair HL from the stack (the caller's original HL).
479F
RET C9
RETURN with the parameter block byte in Register A.
47A0
LD A,C 79
Parameter Block Address Calculator
Copy the drive number from Register C into Register A. Entry: Register C = drive number (0-7). Exit: Register A = computed byte offset, HL = parameter block address at 4700H + (drive x 10).
47A1
AND A,07H E6 07
Mask Register A with 07H (bits 0-2 only). This limits the drive number to the valid range 0-7, preventing out-of-bounds table access.
47A3
ADD A,A 87
Double Register A. A = drive_number x 2.
47A4
LD L,A 6F
Save the doubled value in Register L. L = drive_number x 2 (saved for later addition).
47A5
ADD A,A 87
Double Register A again. A = drive_number x 4.
47A6
ADD A,A 87
Double Register A again. A = drive_number x 8.
47A7
ADD A,L 85
ADD Register L (drive_number x 2) to Register A (drive_number x 8). Result: A = drive_number x 10. This computes the byte offset into the parameter block table using the multiplication-by-addition technique: 10 = 8 + 2.
47A8
ADD A,00H C6 00
ADD 00H to Register A. The operand at 47A9H is a field offset that can be set by callers to access a specific byte within the parameter block. Default value 00H means the result points to the start of the block (offset +00H). Callers that need a different field would patch this operand before calling.
47AA
LD L,A 6F
Load Register L with the computed byte offset (drive_number x 10 + field_offset).
47AB
ADD A,H 84
ADD Register H to Register A. Register H may contain a page number from the 4797H entry point (used for extended addressing), or 00H from the standard path.
47AC
LD H,47H 26 47
Load Register H with 47H. Register Pair HL now = 4700H + (drive_number x 10) + field_offset. This points directly into the parameter block table at 4700H.
47AE
RET C9
RETURN with HL pointing to the computed parameter block address and Register A containing the low byte of the address.

47B0H - Position to Record

Positions the FCB to a specified record number in Register Pair BC. If the file uses fixed record length mode (bit 7 of IX+01H set), the record number is multiplied by the record length (IX+09H) using the 24-bit multiply at 4B8FH to compute the absolute byte position. The result is split into a sector number (BC) and byte offset (A). If the target sector differs from the current sector, the buffer is flushed and the new position is stored. Called via the jump table at 4442H.

47B0
GOSUB to the FCB validator at 49EBH. Validates the FCB pointed to by IX, saves the overlay context (return address to 430CH, FCB pointer to 430AH), and returns with A = 00H (success) or error code.
47B3
SET 6,(IX+01H) DD CB 01 F6
SET bit 6 of the FCB access flags at IX+01H. Bit 6 = file is open for extend (the file system will allocate new sectors if the position goes beyond the current file size).
47B7
BIT 7,(IX+01H) DD CB 01 7E
Test bit 7 of the FCB access flags at IX+01H. Bit 7 = fixed record length mode. If set, the record number in BC must be converted to a sector/offset position using the record length. If clear, BC is used as a direct sector number.
47BB
If the Z FLAG is set (bit 7 is clear = NOT in fixed record length mode), JUMP to 47DFH to set the position directly using BC as the sector number and A as the byte offset.
47BD
LD H,B 60
Copy Register B (record number high byte) into Register H.
47BE
LD L,C 69
Copy Register C (record number low byte) into Register L. Register Pair HL now contains the 16-bit record number.
47BF
LD A,(IX+09H) DD 7E 09
Fetch the fixed record length from IX+09H into Register A. This is the number of bytes per record as defined when the file was opened.
47C2
OR A,A B7
Test Register A for zero. If the record length is 0, the file does not use fixed-length records despite bit 7 being set.
47C3
If the Z FLAG is set (record length is 0), JUMP to 47DFH for direct sector-based positioning.
47C5
GOSUB to the 24-bit multiply at 4B8FH. Computes HL:A = DE x C, where DE = record number (from the HL loaded at 47BD-47BE, transferred to DE inside the routine) and C = record length. The 24-bit result represents the absolute byte position within the file. Register Pair HL contains the sector number (high 16 bits of byte_position / 256), and Register A contains the byte offset within that sector (low 8 bits).
47C8
LD B,H 44
Copy the sector number high byte from Register H into Register B.
47C9
LD C,L 4D
Copy the sector number low byte from Register L into Register C. Register Pair BC now contains the target sector number.
47CA
LD (IX+05H),A DD 77 05
Store the byte offset (Register A, the remainder from the multiply) to IX+05H. This is the position within the target sector where the record begins.
47CD
BIT 5,(IX+01H) DD CB 01 6E
Test bit 5 of the FCB access flags at IX+01H. Bit 5 = buffer needs refresh (the current sector buffer contents are stale because a sector boundary was crossed).
47D1
If bit 5 is set (buffer needs refresh), JUMP to 47E2H to flush the current buffer and update the sector position. There is no point comparing sector numbers when the buffer is already stale.
47D3
LD L,(IX+0AH) DD 6E 0A
Fetch the current sector position (low byte) from IX+0AH into Register L.
47D6
LD H,(IX+0BH) DD 66 0B
Fetch the current sector position (high byte) from IX+0BH into Register H. Register Pair HL now contains the current sector number.
47D9
SCF 37
Set the CARRY flag. This prepares for the SBC HL,BC instruction below which subtracts BC+1 from HL (because CARRY is added to the subtraction).
47DA
SBC HL,BC ED 42
SUBtract Register Pair BC plus CARRY from HL. Since CARRY was set, this computes HL = current_sector - target_sector - 1. If the current sector equals the target sector, the result is FFFFH and the Z flag is set (because the subtract-with-carry produces a borrow-adjusted zero result).
47DC
RET Z C8
If the Z FLAG is set (current sector matches target sector), RETURN. The buffer already contains the correct sector data - only the byte offset (already stored at 47CAH) needed updating.
47DD
The current sector does not match the target sector. JUMP to 47E2H to flush the old buffer contents and load the new sector.
47DF
LD (IX+05H),A DD 77 05
Set Position - Sector Mode Entry
Store Register A (byte offset, typically 00H for sector-based positioning) to the byte offset field at IX+05H. For sector-based access, the position starts at byte 0 of the target sector.
47E2
PUSH BC C5
Flush and Update Position
Save the target sector number (Register Pair BC) onto the stack. BC must be preserved across the buffer flush operation.
47E3
GOSUB to the check-flush-needed routine at 4988H. If the current buffer has been modified (dirty), this routine writes it to disk before the position change. Returns Z if successful, NZ if an error occurred.
47E6
POP BC C1
Restore the target sector number from the stack into Register Pair BC.
47E7
RET NZ C0
If the NZ FLAG is set (the flush operation returned an error), RETURN with the error code in Register A. The position is not updated.
47E8
LD (IX+0AH),C DD 71 0A
Store the target sector number (low byte, Register C) to the current sector position field at IX+0AH.
47EB
LD (IX+0BH),B DD 70 0B
Store the target sector number (high byte, Register B) to the current sector position field at IX+0BH. The FCB now records the new file position.
47EE
SET 5,(IX+01H) DD CB 01 EE
SET bit 5 of the FCB access flags at IX+01H. Bit 5 = buffer needs refresh. The sector data at the new position has not been read into the buffer yet; the next read or write operation will load it.
47F2
JUMP to the EOF check routine at 4A15H. This compares the new file position against the end-of-file marker and returns the appropriate status (Z = within file, NZ with error code = at or past EOF).

47F5H - Position Forward One Record

Advances the FCB position by one record. Gets the current file position via 4851H, increments the sector count, and sets the new position. Called via the jump table at 4460H.

47F5
GOSUB to the FCB validator at 49EBH.
47F8
GOSUB to the get-current-position routine at 4851H. Returns with HL = current sector number, D = byte offset within sector, E = record length.
47FB
INC BC 03
INCrement Register Pair BC by 1. Advances the position by one sector/record.
47FC
JUMP to 47DFH to set the new position. Register A (byte offset) is 0, so the position starts at the beginning of the next sector.

47FEH - Position Back One Record

Moves the FCB position backward by one record. Subtracts the record length from the current byte offset. If the result underflows (crosses backward past the start of the current sector), the sector number is decremented. Called via the jump table at 4445H.

47FE
GOSUB to the FCB validator at 49EBH.
4801
LD A,(IX+09H) DD 7E 09
Fetch the record length from IX+09H into Register A.
4804
NEG ED 44
Negate Register A. A = 0 - record_length = the two's complement negation. This converts the record length into a negative offset for subtraction.
4806
ADD A,(IX+05H) DD 86 05
ADD the current byte offset (IX+05H) to the negated record length in Register A. Result = current_byte_offset - record_length. If the result is negative (CARRY set), the position has crossed backward past the start of the current sector.
4809
LD (IX+05H),A DD 77 05
Store the new byte offset to IX+05H. Even if negative/underflowed, the value is stored temporarily.
480C
RET Z C8
If the Z FLAG is set (the new byte offset is exactly 0, meaning the position is at the start of the current sector), RETURN with success.
480D
If the NO CARRY FLAG is set (no underflow - the new offset is still positive and within the current sector), JUMP to 4811H to decrement the sector number and update the position.
480F
XOR A,A AF
Underflow occurred (the position crossed backward past the sector boundary). Set Register A to 00H and clear all flags. This clamps the byte offset at 0.
4810
RET C9
RETURN with A = 00H (success). The byte offset has been adjusted and stored.
4811
LD C,(IX+0AH) DD 4E 0A
Fetch the current sector position (low byte) from IX+0AH into Register C.
4814
LD B,(IX+0BH) DD 46 0B
Fetch the current sector position (high byte) from IX+0BH into Register B. Register Pair BC = current sector number.
4817
DEC BC 0B
DECrement Register Pair BC by 1. Move back one sector to account for the byte offset crossing the sector boundary.
4818
JUMP to 47E2H to flush the current buffer and update the sector position to the decremented value in BC.

481AH - Read Current Position

Reads the current file position and decrements the sector count by 1. Called via the jump table at 4454H.

481A
GOSUB to the FCB validator at 49EBH.
481D
LD C,(IX+0AH) DD 4E 0A
Fetch the current sector position (low byte) from IX+0AH into Register C.
4820
LD B,(IX+0BH) DD 46 0B
Fetch the current sector position (high byte) from IX+0BH into Register B. Register Pair BC = current sector.
4823
DEC BC 0B
DECrement Register Pair BC by 1.
4824
JUMP to 47E2H to flush the buffer and update the position to the decremented sector number.

4826H - Rewind File

Positions the FCB to the very start of the file: sector 0, byte offset 0. Called via the jump table at 443FH.

4826
GOSUB to the FCB validator at 49EBH. Returns with Register A = 00H on success.
4829
LD B,A 47
Load Register B with Register A (00H). B = high byte of target sector = 0.
482A
LD C,A 4F
Load Register C with Register A (00H). C = low byte of target sector = 0. Register Pair BC = 0000H (sector 0).
482B
JUMP to 47DFH to set the file position to sector 0, byte offset 0 (Register A is also 00H). This rewinds the file to its beginning.

482DH - Position to End of File

Positions the FCB to the end-of-file marker. Reads the total sector count (IX+0CH-0DH) and the EOF byte offset (IX+08H). If the EOF byte offset is non-zero (file ends partway through a sector), the sector position is one less than the total count. Called via the jump table at 4448H.

482D
GOSUB to the FCB validator at 49EBH.
4830
LD C,(IX+0CH) DD 4E 0C
Fetch the total sector count (low byte) from IX+0CH into Register C.
4833
LD B,(IX+0DH) DD 46 0D
Fetch the total sector count (high byte) from IX+0DH into Register B. Register Pair BC = total sectors allocated to this file.
4836
LD A,(IX+08H) DD 7E 08
Fetch the EOF byte offset from IX+08H into Register A. If this value is non-zero, the file's last byte of data is at this offset within the last sector. If zero, the file ends exactly on a sector boundary.
4839
OR A,A B7
Test Register A for zero. Sets the Z flag if the EOF byte offset is 0 (file ends on a sector boundary).
483A
If the Z FLAG is set (EOF byte offset is 0, file ends on a sector boundary), JUMP to 47DFH to set the position using the total sector count directly as the target sector.
483C
DEC BC 0B
DECrement Register Pair BC by 1. The EOF is within the last sector (not on a boundary), so the sector position should be one less than the total count - i.e., the last sector that contains actual data.
483D
JUMP to 47DFH to set the file position to the last data-containing sector (BC) with byte offset A (the EOF byte offset from IX+08H).

483FH - Flush Buffer and Allocate

Flushes the current sector buffer if dirty, checks the EOF position, reads the sector if needed, and writes the sector to disk. Called via the jump table at 4451H.

483F
GOSUB to the FCB validator at 49EBH. Validates the FCB, saves overlay context.
4842
GOSUB to the EOF check routine at 4A15H. Compares the current file position against the end-of-file marker. Returns Z if within file bounds, NZ with error code if at or past EOF.
4845
If the Z FLAG is set (position is within file bounds), GOSUB to the read-sector-to-buffer routine at 4A3CH. This loads the sector at the current file position into the FCB's sector buffer.
4848
RET NZ C0
If the NZ FLAG is set (either the EOF check or the sector read returned an error), RETURN with the error code in Register A.
4849
GOSUB to the set-track/sector-and-seek wrapper at 475EH (B=06H). This positions the FDC head on the correct track for the sector write operation.
484C
XOR A,A AF
Set Register A to ZERO and clear all flags. A = 00H indicates success.
484D
RET C9
RETURN with A = 00H (success). The buffer has been flushed and the sector allocated.

484EH - Get File Size and Get Current Position

Returns the file size as a sector count and byte offset. Entry at 484EH returns the full file size; entry at 4851H returns the current position. Both use the same calculation core at 485AH which computes the byte-accurate position from sector count, byte offset, and record length. The result is returned on the stack via EX (SP),HL manipulation.

484E
Get File Size Entry (445AH)
GOSUB to the FCB validator at 49EBH.
4851
LD L,(IX+0AH) DD 6E 0A
Get Current Position Entry (also reached from 47F8H)
Fetch the current sector position (low byte) from IX+0AH into Register L.
4854
LD H,(IX+0BH) DD 66 0B
Fetch the current sector position (high byte) from IX+0BH into Register H. Register Pair HL = current sector number.
4857
LD D,(IX+05H) DD 56 05
Fetch the current byte offset within the sector from IX+05H into Register D.
485A
LD E,(IX+09H) DD 5E 09
Position Calculation Core
Fetch the record length from IX+09H into Register E. If the record length is non-zero, the position must be converted from sector/offset form into a record number.
485D
LD A,D 7A
Copy the byte offset from Register D into Register A.
485E
OR A,A B7
Test Register A for zero. If the byte offset is zero, the position is exactly at a sector boundary.
485F
If the Z FLAG is set (byte offset is 0, position is on a sector boundary), JUMP to 4862H to skip the sector adjustment.
4861
DEC HL 2B
DECrement Register Pair HL (sector count) by 1. When the byte offset is non-zero, the position is within a sector, so the sector count needs to be adjusted down by 1 for the calculation.
4862
LD A,E 7B
Copy the record length from Register E into Register A.
4863
OR A,A B7
Test Register A for zero. If the record length is 0, no record-based conversion is needed.
4864
If the Z FLAG is set (record length is 0, sector-based mode), JUMP to 4870H to skip the division and return the sector count directly.
4866
GOSUB to the 16-bit divide stub at 45EAH. This calls the divide routine at 4BA9H and post-processes the result. Divides HL (sector count) by A (record length). Returns quotient in HL and remainder in A.
4869
LD L,D 6A
Load Register L with Register D (the byte offset saved from earlier). This combines the sector-based quotient with the byte offset for the final record number calculation.
486A
LD A,E 7B
Load Register A with Register E (the record length).
486B
GOSUB to the 16-bit divide at 4BA9H. Divides HL by A (record length). Returns the record number in HL.
486E
LD A,C 79
Copy Register C into Register A. Register C contains part of the division result.
486F
LD H,B 60
Copy Register B into Register H. Assembles the final result into HL from the division outputs.
4870
POP BC C1
Return Position via Stack
Pop Register Pair BC from the stack. This restores the caller's return address (which was pushed before entering this routine).
4871
EX (SP),HL E3
Exchange (SP) with HL. The file position result (in HL) is placed on the stack where the caller expects it, and the previous stack value is loaded into HL. This is the standard pattern for returning a 16-bit result through the caller's stack frame.
4872
PUSH BC C5
Push Register Pair BC (the caller's return address) back onto the stack so that RET will return to the correct location.
4873
OR A,A B7
OR Register A with itself. Sets flags based on the byte offset / remainder value. NZ indicates a partial record at the current position.
4874
RET C9
RETURN to the caller with the file position on the stack and the partial-record indicator in the flags.

4875H - Get EOF Position

Returns the end-of-file position. Loads the total sector count and EOF byte offset from the FCB, then falls into the position calculation core at 485AH. Called via the jump table at 445DH.

4875
GOSUB to the FCB validator at 49EBH.
4878
LD L,(IX+0CH) DD 6E 0C
Fetch the total sector count (low byte) from IX+0CH into Register L.
487B
LD H,(IX+0DH) DD 66 0D
Fetch the total sector count (high byte) from IX+0DH into Register H. Register Pair HL = total sectors.
487E
LD D,(IX+08H) DD 56 08
Fetch the EOF byte offset from IX+08H into Register D. This is where the file's data ends within the last sector.
4881
JUMP to the position calculation core at 485AH. This routine loads Register E with the record length (IX+09H) and converts the sector/offset position into a record number, returning the result via the stack.

4883H - Write EOF to Directory

Writes the current FCB file position as the end-of-file marker in the disk directory entry. Flushes the buffer, reads the directory sector, updates the total sector count at directory offset +14H, and writes the directory sector back to disk. Called via the jump table at 444EH.

4883
GOSUB to the FCB validator at 49EBH.
4886
GOSUB to the check-flush-needed routine at 4988H. If the sector buffer is dirty, writes it to disk before modifying the directory.
4889
LD B,(IX+07H) DD 46 07
Fetch the directory entry info byte from IX+07H into Register B. This identifies which directory entry on which sector contains this file's metadata.
488C
LD C,(IX+06H) DD 4E 06
Fetch the drive number from IX+06H into Register C. The directory sector will be read from this drive.
488F
GOSUB to the directory sector read routine at 4B10H. Reads the directory sector identified by Register B into the directory buffer. Returns with HL pointing to the directory entry for this file. Returns NZ on error.
4892
RET NZ C0
If the NZ FLAG is set (directory read failed), RETURN with the error code.
4893
LD DE,0014H 11 14 00
Load Register Pair DE with 0014H (20 decimal). This is the offset within the directory entry where the total sector count / file size field is stored.
4896
ADD HL,DE 19
ADD Register Pair DE to HL. HL now points to directory_entry + 14H, the sector count field within the directory entry.
4897
LD A,(IX+0CH) DD 7E 0C
Fetch the current total sector count (low byte) from the FCB at IX+0CH into Register A.
489A
LD (HL),A 77
Store the sector count low byte to the directory entry at (HL). This updates the on-disk record of the file's size.
489B
INC HL 23
INCrement Register Pair HL to point to the next byte in the directory entry (sector count high byte at offset +15H).
489C
LD A,(IX+0DH) DD 7E 0D
Fetch the current total sector count (high byte) from the FCB at IX+0DH into Register A.
489F
LD (HL),A 77
Store the sector count high byte to the directory entry at (HL)+1.
48A0
GOSUB to the directory sector write routine at 4B1FH. Writes the modified directory sector back to disk. Returns NZ on error.
48A3
RET C9
RETURN to the caller with the write status in the flags.

48A4H - Read Record

Reads a fixed-length record from the file into the user buffer at HL. The record length is taken from IX+09H. If the record length is 0 (sector mode), jumps to the sector allocation routine at 4949H. Otherwise, loops B times calling the read-byte routine at 48F1H, storing each byte to (HL) and advancing HL. Called via the jump table at 4436H.

48A4
GOSUB to the FCB validator at 49EBH.
48A7
GOSUB to the flush buffer helper at 44FAH. This checks if the sector buffer needs flushing (via 4988H) and flushes if necessary. Returns NZ on error.
48AA
RET NZ C0
If the NZ FLAG is set (flush error), RETURN with the error code.
48AB
LD B,(IX+09H) DD 46 09
Fetch the record length from IX+09H into Register B. Register B serves as the byte count for the read loop.
48AE
LD A,B 78
Copy the record length from Register B into Register A for testing.
48AF
OR A,A B7
Test Register A for zero. If the record length is 0, the file uses sector-based I/O rather than fixed-record I/O.
48B0
If the Z FLAG is set (record length is 0, sector mode), JUMP to the sector allocation routine at 4949H. In sector mode, the read operation works differently - it reads an entire sector rather than a fixed-length record.
48B3
PUSH HL E5
Read Record Loop Start
Save Register Pair HL (user buffer pointer) onto the stack.
48B4
PUSH BC C5
Save Register Pair BC (B = remaining byte count) onto the stack.
48B5
GOSUB to the read-byte-from-file routine at 48F1H. This reads one byte from the current file position into Register A, then advances the file position by one byte. Returns NZ on error (typically EOF).
48B8
POP BC C1
Restore Register Pair BC from the stack. B = remaining byte count.
48B9
POP HL E1
Restore Register Pair HL from the stack. HL = user buffer pointer.
48BA
RET NZ C0
If the NZ FLAG is set (the read-byte routine returned an error, typically end-of-file), RETURN with the error code. The partially-filled buffer is available to the caller.
48BB
LD (HL),A 77
Store the byte read from the file (Register A) into the user buffer at (HL).
48BC
INC HL 23
INCrement Register Pair HL by 1. Advance the user buffer pointer to the next byte position.
48BD
DECrement Register B (remaining byte count) and LOOP BACK to 48B3H if not zero. The loop continues until all B bytes of the record have been read.
48BF
RET C9
RETURN with A = 00H (success). The complete record has been read into the user buffer.

48C0H - Write Record

Writes a fixed-length record from the user buffer at HL to the file. Similar structure to Read Record but writes bytes via the write-byte routine at 4910H. The verify flag at 49C7H is set to 00H (no verify). Called via the jump table at 4439H.

48C0
GOSUB to the FCB validator at 49EBH. Returns with A = 00H on success.
48C3
LD (49C7H),A 32 C7 49
Self-Modifying Code
Store Register A (00H after the validator returns success) to the write verify flag at 49C7H. Value 00H disables write verification - the sector is written without a read-back check. This is the operand of the LD A,00H instruction at 49C6H within the sector write core.
48C6
LD B,(IX+09H) DD 46 09
Fetch the record length from IX+09H into Register B (byte count for the write loop).
48C9
LD A,B 78
Copy the record length from Register B into Register A for testing.
48CA
OR A,A B7
Test Register A for zero.
48CB
If the Z FLAG is set (record length is 0, sector mode), JUMP to the sector write access check at 49A3H. In sector mode, the write operation handles entire sectors.
48CE
LD A,(HL) 7E
Write Record Loop Start
Fetch the next byte to write from the user buffer at (HL) into Register A.
48CF
INC HL 23
INCrement Register Pair HL by 1. Advance the user buffer pointer.
48D0
PUSH HL E5
Save Register Pair HL (updated buffer pointer) onto the stack.
48D1
PUSH BC C5
Save Register Pair BC (B = remaining byte count) onto the stack.
48D2
GOSUB to the write-byte-to-file routine at 4910H. Writes the byte in Register A to the current file position, then advances the position. Returns NZ on error.
48D5
POP BC C1
Restore Register Pair BC from the stack.
48D6
POP HL E1
Restore Register Pair HL from the stack.
48D7
RET NZ C0
If the NZ FLAG is set (write error), RETURN with the error code.
48D8
DECrement Register B and LOOP BACK to 48CEH if not zero. Continue writing bytes until the entire record is written.
48DA
RET C9
RETURN with A = 00H (success). The complete record has been written.

48DBH - Write Record with Verify

Identical to Write Record (48C0H) except the verify flag at 49C7H is set to 01H (non-zero), enabling read-back verification after each sector write. Called via the jump table at 443CH.

48DB
GOSUB to the FCB validator at 49EBH. Returns with A = 00H.
48DE
INC A 3C
INCrement Register A by 1. A goes from 00H to 01H. This non-zero value will be stored to the verify flag, enabling write verification.
48DF
JUMP to 48C3H to store A (01H) to the verify flag at 49C7H and continue with the standard write record logic. The only difference from the non-verify path is that 49C7H contains 01H instead of 00H.

48E1H - Open/Random Access File Handler

Handles the open/random-access entry from the I/O dispatcher at 44D1H. Sets the fixed record length flag (bit 7 of IX+01H) and dispatches to either the write-byte routine (B >= 02H) or the read-byte routine (B < 02H).

48E1
PUSH IX DD E5
Save Register IX onto the stack. IX currently points to the device block from the I/O dispatcher.
48E3
POP DE D1
Pop the IX value into Register Pair DE. DE = device block address (PUSH IX / POP DE transfers IX to DE).
48E4
GOSUB to the FCB validator at 49EBH. After this call, IX points to the validated FCB.
48E7
SET 7,(IX+01H) DD CB 01 FE
SET bit 7 of the FCB access flags at IX+01H. Bit 7 = fixed record length mode. This marks the file as using record-based I/O.
48EB
LD A,B 78
Copy the operation code from Register B into Register A.
48EC
CP A,02H FE 02
Compare Register A against 02H. If B >= 02H, this is a write operation; if B < 02H, this is a read operation.
48EE
LD A,C 79
Copy Register C (the data byte for write, or unused for read) into Register A.
48EF
If the NO CARRY FLAG is set (B >= 02H = write operation), JUMP to the write-byte-to-file routine at 4910H with the data byte in Register A.

48F1H - Read Byte from File

Reads a single byte from the current file position into Register A, then advances the position by one byte. If the buffer needs refreshing (sector boundary was crossed), reads the sector first. Handles sector boundary crossing by setting the "needs refresh" flag after the byte offset wraps from FFH to 00H.

48F1
Read Byte Entry
GOSUB to the EOF check at 4A15H. Ensures the current position is within the file bounds. Returns NZ with error code if at or past EOF.
48F4
RET NZ C0
If the NZ FLAG is set (at EOF or error), RETURN with the error code. No byte can be read past the end of the file.
48F5
BIT 5,(IX+01H) DD CB 01 6E
Test bit 5 of the FCB access flags. Bit 5 = buffer needs refresh (the sector data in the buffer is stale because a sector boundary was crossed).
48F9
If the Z FLAG is set (bit 5 is clear = buffer is current), JUMP to 48FFH to read the byte directly from the buffer without reloading.
48FB
GOSUB to the sector allocation / read routine at 4949H. This reads the sector at the current position into the buffer and clears the "needs refresh" flag.
48FE
RET NZ C0
If the NZ FLAG is set (sector read error), RETURN with the error code.
48FF
GOSUB to the buffer address calculator at 497AH. Computes DE = buffer_base_address + byte_offset, pointing to the exact byte within the sector buffer.
4902
XOR A,A AF
Set Register A to ZERO. This clears A before the byte fetch (the XOR also clears all flags, establishing a "success" state).
4903
LD A,(DE) 1A
Fetch the byte from the sector buffer at address (DE) into Register A. This is the file data byte at the current position.
4904
PUSH AF F5
Save the data byte (Register A) and flags onto the stack. The byte must be preserved while the position is advanced.
4905
INC (IX+05H) DD 34 05
INCrement the byte offset at IX+05H by 1. Advance the position within the current sector to the next byte. If the offset wraps from FFH to 00H, the Z flag is set (sector boundary crossed).
4908
If the NZ FLAG is set (byte offset did NOT wrap to 00H, still within the same sector), JUMP to 490EH to return the byte.
490A
SET 5,(IX+01H) DD CB 01 EE
The byte offset wrapped from FFH to 00H, indicating a sector boundary crossing. SET bit 5 of the FCB access flags at IX+01H. This marks the buffer as needing refresh - the next read will load the next sector before returning data.
490E
POP AF F1
Restore the data byte and flags from the stack.
490F
RET C9
RETURN with the file data byte in Register A and the Z flag set (success).

4910H - Write Byte to File

Writes a single byte from Register A to the current file position, then advances the position. If the buffer needs refreshing, reads the sector first. After writing, marks the buffer as dirty (bit 4) and handles sector boundary crossing. If the position reaches or exceeds EOF, updates the EOF fields in the FCB.

4910
PUSH AF F5
Write Byte Entry
Save Register Pair AF (the data byte to write in A) onto the stack.
4911
BIT 5,(IX+01H) DD CB 01 6E
Test bit 5 of the FCB access flags. Bit 5 = buffer needs refresh (sector boundary was crossed, buffer contents are stale).
4915
If the Z FLAG is set (bit 5 clear = buffer is current), JUMP to 491FH to proceed directly to writing the byte.
4917
GOSUB to the sector read with access check at 494DH. This reads the sector at the current position into the buffer and checks access permissions. Returns NZ on error.
491A
If the Z FLAG is set (sector read succeeded), JUMP to 491FH to proceed with the write.
491C
EX (SP),HL E3
Exchange (SP) with HL. The error code/state is placed on the stack, replacing the saved AF. This sets up for an error return.
491D
POP HL E1
Pop the modified stack value into HL. This discards the saved AF and cleans up the stack.
491E
RET C9
RETURN with the error code from the sector read. The byte was not written.
491F
GOSUB to the buffer address calculator at 497AH. Computes DE = buffer_base + byte_offset, pointing to the exact position within the sector buffer where the byte should be written.
4922
POP AF F1
Restore the data byte from the stack into Register A.
4923
LD (DE),A 12
Store the data byte (Register A) to the sector buffer at address (DE). The byte is now in the buffer but has not yet been written to disk.
4924
SET 4,(IX+01H) DD CB 01 E6
SET bit 4 of the FCB access flags at IX+01H. Bit 4 = buffer dirty. This marks the buffer as modified, so it will be written to disk when flushed.
4928
INC (IX+05H) DD 34 05
INCrement the byte offset at IX+05H by 1. Advance the position to the next byte. If the offset wraps from FFH to 00H (sector boundary crossing), the Z flag is set.
492B
GOSUB to the EOF check at 4A15H. After writing, check whether the new position has reached or passed the end of file. Returns Z if within file, NZ with error code if at/past EOF.
492E
If the NZ FLAG is set (position is now at or past EOF), JUMP to 4936H to update the EOF fields in the FCB. This extends the file to include the newly written byte.
4930
BIT 6,(IX+01H) DD CB 01 76
Test bit 6 of the FCB access flags. Bit 6 = file open for extend. If set, the file can grow; if clear, writing past EOF is not allowed.
4934
If bit 6 is set (extend mode), JUMP to 493FH to skip the EOF update since the file is being extended and the caller will update EOF separately.
4936
LD (IX+08H),C DD 71 08
Update EOF Fields
Store Register C (low byte of the byte offset / position indicator) to the EOF byte offset at IX+08H. This records where the file data ends within the current sector.
4939
LD (IX+0CH),L DD 75 0C
Store Register L to the total sector count (low byte) at IX+0CH.
493C
LD (IX+0DH),H DD 74 0D
Store Register H to the total sector count (high byte) at IX+0DH. The FCB now reflects the new end-of-file position.
493F
LD A,C 79
Copy Register C (byte offset) into Register A.
4940
OR A,A B7
Test Register A for zero. If the byte offset is 0, a sector boundary has been crossed.
4941
If the NZ FLAG is set (byte offset is non-zero, still within the same sector), JUMP to 4978H to return success (XOR A / RET).
4943
SET 5,(IX+01H) DD CB 01 EE
Byte offset is 0 - a sector boundary has been crossed. SET bit 5 of the FCB access flags to mark the buffer as needing refresh.
4947
JUMP to 4996H to flush the current sector buffer to disk (since we've crossed into a new sector, the old sector data must be saved).

4949H - Allocate and Read Sector

Checks file access permissions and reads the sector at the current position into the buffer. If the access mode indicates the file cannot be extended (mode >= 6), returns error 25H (access denied). Otherwise, reads the sector via the disk I/O wrappers and clears the "buffer needs refresh" flag.

4949
GOSUB to the EOF check at 4A15H. Returns NZ with error code if past EOF.
494C
RET NZ C0
If the NZ FLAG is set (error from EOF check), RETURN with the error code.
494D
LD A,(IX+01H) DD 7E 01
Access Check Entry
Fetch the FCB access flags from IX+01H into Register A.
4950
AND A,07H E6 07
Mask Register A with 07H (bits 0-2 only). Extracts the access mode from the low 3 bits.
4952
CP A,06H FE 06
Compare the access mode against 06H. If the mode is >= 6, the file is opened with restricted access that does not permit extension/allocation.
4954
If the CARRY FLAG is set (access mode < 6, access is permitted), JUMP to 495AH to proceed with the sector read.
4956
LD A,25H 3E 25
Load Register A with error code 25H (access denied). The file's access mode does not permit this operation.
4958
OR A,A B7
OR Register A with itself. Sets the NZ flag (since A = 25H, non-zero) to indicate an error.
4959
RET C9
RETURN with A = 25H (access denied) and the NZ flag set.
495A
GOSUB to the read-sector-to-buffer routine at 4A3CH. Reads the sector at the current file position into the FCB's sector buffer.
495D
RET NZ C0
If the NZ FLAG is set (disk read error), RETURN with the error code.
495E
RES 5,(IX+01H) DD CB 01 AE
RESET bit 5 of the FCB access flags at IX+01H. Clears the "buffer needs refresh" flag since the correct sector is now in the buffer.
4962
LD L,(IX+03H) DD 6E 03
Fetch the sector buffer base address (low byte) from IX+03H into Register L.
4965
LD H,(IX+04H) DD 66 04
Fetch the sector buffer base address (high byte) from IX+04H into Register H. Register Pair HL = buffer address.
4968
GOSUB to the read-sector-without-seek wrapper at 4777H (B=09H). Reads the sector from disk into the buffer at HL. The FDC head should already be on the correct track from the preceding seek.
496B
If the Z FLAG is set (read succeeded), JUMP to 4970H to increment the sector position and return success.
496D
CP A,06H FE 06
Compare the error code in Register A against 06H. Error 06H = attempted to read a deleted data record, which may be acceptable in some contexts.
496F
RET NZ C0
If the NZ FLAG is set (error is NOT 06H, it's a real error), RETURN with the error code.
4970
INC (IX+0AH) DD 34 0A
Advance Sector Position
INCrement the current sector position (low byte) at IX+0AH by 1. If the low byte wraps from FFH to 00H, the Z flag is set.
4973
If the NZ FLAG is set (low byte did not wrap), JUMP to 4978H to return success.
4975
INC (IX+0BH) DD 34 0B
The low byte wrapped to 00H, so INCrement the high byte of the sector position at IX+0BH. This handles the carry from the low byte.
4978
XOR A,A AF
Success Return
Set Register A to ZERO and clear all flags. A = 00H indicates success.
4979
RET C9
RETURN with A = 00H (success).

497AH - Buffer Address Calculation

Computes the absolute memory address within the sector buffer for the current byte offset. Returns DE = buffer_base_address + byte_offset. The buffer base is a 16-bit address stored at IX+03H-04H, and the byte offset is stored at IX+05H.

497A
LD A,(IX+05H) DD 7E 05
Fetch the current byte offset from IX+05H into Register A. This is the position within the 256-byte sector buffer (00H-FFH).
497D
ADD A,(IX+03H) DD 86 03
ADD the sector buffer base address (low byte) from IX+03H to Register A. The result is the low byte of the absolute buffer address. If the addition overflows (carry), it will be handled by the ADC below.
4980
LD E,A 5F
Store the computed low byte into Register E.
4981
LD A,(IX+04H) DD 7E 04
Fetch the sector buffer base address (high byte) from IX+04H into Register A.
4984
ADC A,00H CE 00
ADD the carry from the previous ADD to the high byte. If the low byte addition overflowed, this carry propagates to the high byte, correctly handling buffer addresses that cross a 256-byte page boundary.
4986
LD D,A 57
Store the computed high byte into Register D. Register Pair DE now contains the absolute memory address of the current byte within the sector buffer.
4987
RET C9
RETURN with DE = buffer_base + byte_offset.

4988H - Check If Buffer Needs Flushing

Tests the FCB access flags to determine if the sector buffer needs to be written to disk. Checks both bit 4 (buffer dirty) and bit 7 (fixed record mode). If both are set, the buffer must be flushed. If not, returns success without writing.

4988
LD A,(IX+01H) DD 7E 01
Check Flush Entry
Fetch the FCB access flags from IX+01H into Register A.
498B
AND A,90H E6 90
Mask Register A with 90H (10010000 binary). Isolates bit 4 (buffer dirty) and bit 7 (fixed record mode). The result is 90H only if both bits are set.
498D
CP A,90H FE 90
Compare Register A against 90H. If A = 90H (both dirty AND fixed record mode), the Z flag is set, indicating the buffer needs flushing.
498F
If the Z FLAG is set (buffer is dirty and in fixed record mode), JUMP to the sector write routine at 4996H to flush the buffer to disk.
4991
Buffer does not need flushing. JUMP to 4978H (XOR A / RET) to return success without writing.

4993H - Decrement Position

Decrements the current file position by one sector. Called via the jump table at 4457H. Validates the FCB, then falls through to the sector write core at 4996H.

4993
GOSUB to the FCB validator at 49EBH.

4996H - Sector Write Core

Writes the current sector buffer to disk, then checks access permissions and handles post-write operations including verify (if enabled), EOF check, and sector position updates. This is the central sector write routine called by the buffer flush path, the write record loop, and the decrement position handler.

4996
LD C,(IX+0AH) DD 4E 0A
Sector Write Core Entry
Fetch the current sector position (low byte) from IX+0AH into Register C.
4999
LD B,(IX+0BH) DD 46 0B
Fetch the current sector position (high byte) from IX+0BH into Register B. Register Pair BC = sector number to write.
499C
DEC BC 0B
DECrement Register Pair BC by 1. Adjusts the sector number (the internal position is 1-based while the disk sector is 0-based, or this accounts for the previous INC during the read).
499D
LD (IX+0AH),C DD 71 0A
Store the adjusted sector number (low byte) back to IX+0AH.
49A0
LD (IX+0BH),B DD 70 0B
Store the adjusted sector number (high byte) back to IX+0BH.
49A3
LD A,(IX+01H) DD 7E 01
Write Access Check
Fetch the FCB access flags from IX+01H into Register A.
49A6
AND A,07H E6 07
Mask Register A with 07H (bits 0-2). Extracts the access mode.
49A8
CP A,05H FE 05
Compare the access mode against 05H. If the mode is >= 5, write access is restricted.
49AA
If the CARRY FLAG is set (access mode < 5, write is permitted), JUMP to 49B0H to proceed with the write.
49AC
LD A,25H 3E 25
Load Register A with error code 25H (access denied).
49AE
OR A,A B7
Set the NZ flag.
49AF
RET C9
RETURN with A = 25H (access denied).
49B0
GOSUB to the read-sector-to-buffer routine at 4A3CH. This resolves the current file position through the extent chain to find the physical track/sector on disk.
49B3
RET NZ C0
If the NZ FLAG is set (extent resolution error), RETURN with the error code.
49B4
LD L,(IX+03H) DD 6E 03
Fetch the sector buffer base address (low byte) from IX+03H into Register L.
49B7
LD H,(IX+04H) DD 66 04
Fetch the sector buffer base address (high byte) from IX+04H into Register H. Register Pair HL = buffer address to write from.
49BA
SET 2,(IX+00H) DD CB 00 D6
SET bit 2 of the FCB status byte at IX+00H. Bit 2 indicates a write operation has been performed on this file, used for tracking file modification state.
49BE
GOSUB to the write-sector-with-seek wrapper at 4763H (B=0DH). Writes the sector buffer contents to the physical disk sector identified by the extent chain resolution.
49C1
RET NZ C0
If the NZ FLAG is set (disk write error), RETURN with the error code.
49C2
RES 4,(IX+01H) DD CB 01 A6
RESET bit 4 of the FCB access flags at IX+01H. Clears the "buffer dirty" flag since the buffer contents have been successfully written to disk.
49C6
LD A,00H 3E 00
Self-Modifying Code
Load Register A with 00H. The operand at 49C7H is overwritten by LD (49C7H),A at 48C3H. For write-without-verify (48C0H), A is stored as 00H; for write-with-verify (48DBH), A is stored as 01H. If A is 00H, no verify is performed; if non-zero, a read-back verify follows.
49C8
OR A,A B7
Test Register A for zero. If A = 00H (no verify), the Z flag is set and the verify step is skipped.
49C9
If the NZ FLAG is set (A is non-zero = verify enabled), GOSUB to the read-sector-with-seek wrapper at 4772H (B=0AH). This reads the sector back from disk to verify the write was successful. Returns NZ on read error.
49CC
RET NZ C0
If the NZ FLAG is set (verify read error), RETURN with the error code.
49CD
GOSUB to the EOF check at 4A15H. Check if the current position is at or past EOF after the write.
49D0
If the NZ FLAG is set (position is past EOF), JUMP to 49D8H to handle the post-write EOF update.
49D2
BIT 6,(IX+01H) DD CB 01 76
Test bit 6 of the FCB access flags. Bit 6 = file open for extend.
49D6
If bit 6 is set (extend mode), JUMP to 4970H to increment the sector position and return success. In extend mode, the file grows automatically.
49D8
INC HL 23
INCrement Register Pair HL by 1. Advances the sector count for EOF tracking.
49D9
LD (IX+0CH),L DD 75 0C
Store the updated sector count (low byte) to IX+0CH.
49DC
LD (IX+0DH),H DD 74 0D
Store the updated sector count (high byte) to IX+0DH.
49DF
BIT 3,(IX+01H) DD CB 01 5E
Test bit 3 of the FCB access flags. Bit 3 = auto-update directory on write (the directory EOF marker should be updated after each write).
49E3
If bit 3 is set (auto-update enabled), GOSUB to the partial write-EOF-to-directory routine at 4889H. This updates the directory entry with the new file size.
49E6
RET NZ C0
If the NZ FLAG is set (directory update error), RETURN with the error code.
49E7
JUMP to 4970H to increment the sector position and return success.

49E9H - FCB Validator Prologue

This is the initial entry sequence of the FCB validator before the main validation at 49EBH. The bytes at 49E9H-49EAH (37H 22H) are part of a data structure or alternate entry path.

49E9
SCF 37
Set the CARRY flag. This pre-sets CARRY before the address store below, which uses the carry state to distinguish between the direct-call path (CARRY set from here) and an error path (CARRY clear).
49EA
LD (071AH),HL 22 1A 07
Store Register Pair HL to address 071AH. This saves HL to a scratch area in the ROM address space. On the Model I, addresses below 3000H are ROM and writes have no effect - this instruction is effectively a NOP that consumes 3 bytes and preserves HL. The 3-byte encoding provides the correct displacement for the JR NC below.

49EBH - FCB Validator

Validates the FCB and saves the overlay context. Called at the start of every file operation. Saves the caller's return address to 430CH and the caller's DE (FCB pointer) to 430AH. Then sets up the FCB for the overlay call by pushing synthetic return addresses onto the stack so that when the overlay operation completes, registers are automatically restored via the cleanup code at 4A0CH. If the FCB is not valid (CARRY clear on entry from a non-file path), returns error 26H (file not open).

49EB
EX (SP),HL E3
Exchange (SP) with HL. The top of stack contains the caller's return address; after the exchange, HL holds the return address and the caller's HL is on the stack. This captures the return address for saving.
49EC
LD (430CH),HL 22 0C 43
Store the caller's return address (Register Pair HL) to the saved overlay return address at 430CH. This records where the caller expects to return after the file operation completes.
49EF
LD (430AH),DE ED 53 0A 43
Store Register Pair DE to the saved FCB pointer at 430AH. DE typically contains the FCB pointer or the data buffer address passed by the caller.
49F3
EX (SP),HL E3
Exchange (SP) with HL again. The caller's original HL is restored from the stack, and the return address is put back. The stack is now in its original state.
49F4
If the NO CARRY FLAG is set (the entry at 49E9H set CARRY, but if we got here via a different path with CARRY clear, the FCB is not valid), JUMP to 4A07H to return error 26H (file not open).
49F6
POP AF F1
Pop the return address from the stack into Register AF. This removes the immediate caller's return from the stack, as the validator will set up its own return mechanism.
49F7
PUSH DE D5
Save Register Pair DE onto the stack.
49F8
EX (SP),IX DD E3
Exchange (SP) with IX. The saved DE value is loaded into IX (setting IX to point to the FCB), and the caller's IX is placed on the stack for later restoration.
49FA
PUSH HL E5
Save Register Pair HL onto the stack.
49FB
PUSH DE D5
Save Register Pair DE onto the stack again.
49FC
PUSH BC C5
Save Register Pair BC onto the stack.
49FD
PUSH HL E5
Save Register Pair HL onto the stack again. This will be replaced with the cleanup routine address via the EX (SP),HL below.
49FE
LD HL,4A0CH 21 0C 4A
Point Register Pair HL to the register restore cleanup routine at 4A0CH. After the file operation completes and returns, execution will transfer to 4A0CH which pops BC, DE, HL, and IX to restore the caller's registers.
4A01
EX (SP),HL E3
Exchange (SP) with HL. The cleanup address (4A0CH) is placed on the stack where RET will find it, and the most recently pushed HL is restored.
4A02
PUSH AF F5
Save Register Pair AF onto the stack. AF contains the caller's flags and the popped return address value.
4A03
XOR A,A AF
Set Register A to ZERO and clear all flags. A = 00H indicates success (no error from the validator itself).
4A04
RET C9
RETURN. But instead of returning to the original caller, this returns to the address pushed by the most recent PUSH AF at 4A02H. The stack has been constructed so that after the file operation is done, the chain of RETs will unwind through the cleanup code at 4A0CH, restoring all registers. The file operation routine (which called 49EBH) continues executing with IX pointing to the validated FCB and A = 00H.

4A07H - FCB Not Open Error and Register Restore

Error return for invalid FCB (file not open), and the register restore cleanup routine that runs after every file operation to restore the caller's BC, DE, HL, and IX.

4A07
POP AF F1
File Not Open Error
Pop the return address from the stack into AF to clean up the stack after the failed validation.
4A08
LD A,26H 3E 26
Load Register A with error code 26H (file not open / invalid FCB).
4A0A
OR A,A B7
Set the NZ flag (since A = 26H, non-zero) to indicate an error.
4A0B
RET C9
RETURN with A = 26H (file not open) and the NZ flag set.
4A0C
POP BC C1
Register Restore Cleanup
Restore Register Pair BC from the stack. This is the cleanup routine address (4A0CH) that was pushed onto the stack by the FCB validator at 49FEH-4A01H. After each file operation returns, this sequence restores all the caller's registers.
4A0D
POP DE D1
Restore Register Pair DE from the stack.
4A0E
POP HL E1
Restore Register Pair HL from the stack.
4A0F
POP IX DD E1
Restore Register IX from the stack. All four register pairs (BC, DE, HL, IX) have been restored to their pre-file-operation values.
4A11
RET C9
RETURN to the original caller of the file operation. The file operation's result is in Register A and the flags.

4A12H - Allocate File Space (EOF Check)

This routine validates that the file is open (via the FCB validator at 49EBH), then checks whether the current file position has reached or passed the end of file. It compares the current sector position (IX+0AH/0BH) and byte offset (IX+05H) against the total sector count (IX+0CH/0DH) and EOF byte offset (IX+08H). Returns with Register A = 00H and Z flag set if the position is valid (not at EOF), or returns an error code in Register A with NZ flag if at or past EOF. Called from the jump table entry at 444BH (JP 4A12H).

4A12
GOSUB to the FCB validator at 49EBH. This routine verifies the file is open and sets up a synthetic stack frame for automatic register preservation on return. If the file is not open, it exits with error 25H (access denied).
4A15
LD L,(IX+0AH) DD 6E 0A
Load Register L with the low byte of the current sector position from FCB offset 0AH. This is the 16-bit sector number (0-based) where the file pointer currently sits.
4A18
LD H,(IX+0BH) DD 66 0B
Load Register H with the high byte of the current sector position from FCB offset 0BH. Register Pair HL now holds the full 16-bit current sector position.
4A1B
LD C,(IX+05H) DD 4E 05
Load Register C with the byte offset within the current sector from FCB offset 05H. This ranges from 00H to FFH (0-255), representing the byte position within the 256-byte sector.
4A1E
LD A,H 7C
Load Register A with the high byte of the current sector position (from Register H) for comparison against the total sector count.
4A1F
CP A,(IX+0DH) DD BE 0D
Compare Register A (high byte of current sector position) against the high byte of the total sector count at FCB offset 0DH. If they differ, the position is either before or after the last sector.
4A22
If the NZ FLAG (Not Zero) has been set because the high bytes differ, JUMP to 4A37H to determine whether the position is past EOF (Carry clear) or before EOF (Carry set).
4A24
LD A,L 7D
Load Register A with the low byte of the current sector position (from Register L) for comparison against the low byte of the total sector count.
4A25
CP A,(IX+0CH) DD BE 0C
Compare Register A (low byte of current sector position) against the low byte of the total sector count at FCB offset 0CH. If they differ, we are not on the last sector.
4A28
If the NZ FLAG (Not Zero) has been set because the low bytes differ, JUMP to 4A37H to check if position is past or before EOF.

On the Last Sector
If we reach here, the current sector position equals the total sector count, so we are on the last sector of the file. Now we need to compare the byte offset against the EOF byte offset to determine if we are at or past EOF within this final sector.

4A2A
DEC C 0D
DECrement Register C (byte offset within current sector) by 1. This adjusts the byte offset down by one so that the comparison checks whether we are at or past the last valid byte.
4A2B
LD A,(IX+08H) DD 7E 08
Load Register A with the EOF byte offset from FCB offset 08H. This is the byte offset within the last sector where the file ends. A value of 00H means the file ends on a sector boundary (the entire last sector is full).
4A2E
DEC A 3D
DECrement Register A (EOF byte offset) by 1. Combined with the DEC C above, this sets up for a comparison that determines if we have reached the EOF byte.
4A2F
SUB A,C 91
SUBtract Register C (adjusted byte offset) from Register A (adjusted EOF byte offset). If A >= C, the result is positive or zero meaning we are at or before EOF. If A < C, Carry is set meaning we are past EOF.
4A30
CCF 3F
Complement the Carry Flag. This inverts the result of the subtraction so that Carry now means "at or before EOF" and No Carry means "past EOF".
4A31
INC BC 03
INCrement Register Pair BC by 1 to restore the byte offset (Register C) to its original value after the DEC C at 4A2AH. This does not affect flags.
4A32
If the NZ FLAG (Not Zero) has been set from the SUB at 4A2FH (meaning the byte offsets are not equal), JUMP to 4A37H to evaluate the Carry flag for position determination.

Exact EOF Position
If we reach here, the current position is exactly at EOF (sector matches and byte offset matches). Return error 1CH (position at EOF) with the NZ flag set.

4A34
OR A,1CH F6 1C
OR Register A with 1CH. Since A is zero after the SUB, this loads 1CH (error code 28 decimal: "position at EOF") into Register A and sets the NZ flag.
4A36
RET C9
Return to the caller with Register A = 1CH (position at EOF) and NZ flag set, indicating the file position is exactly at EOF.
4A37
LD A,1DH 3E 1D
Load Register A with 1DH (error code 29 decimal: "position past EOF"). This is the default error return for when the current position is beyond EOF.
4A39
RET NC D0
If the NO CARRY FLAG has been set (meaning the current position is past EOF), return to the caller with Register A = 1DH and NZ flag set.
4A3A
XOR A,A AF
Set Register A to ZERO and set the Z flag. This is the success return: the current position is before EOF, so the position is valid.
4A3B
RET C9
Return to the caller with Register A = 00H and Z flag set, indicating the position is valid (before EOF).

4A3CH - Read Sector to Buffer (Extent Chain Walker)

This routine reads a sector from disk into the file buffer by walking the extent chain in the directory entry. It first determines which extent contains the current sector position by dividing the sector position by the number of sectors per extent. It then walks through the directory entry's extent table to find the matching extent, calculates the physical disk sector, and reads it. Register Pair HL holds the current sector position on entry. Register C holds the drive number.

4A3C
LD L,(IX+0AH) DD 6E 0A
Load Register L with the low byte of the current sector position from FCB offset 0AH.
4A3F
LD H,(IX+0BH) DD 66 0B
Load Register H with the high byte of the current sector position from FCB offset 0BH. Register Pair HL now holds the full 16-bit current sector position in the file.
4A42
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset 06H. This identifies which disk drive the file resides on.
4A45
LD A,08H 3E 08
Load Register A with 08H, the parameter table offset for the "sectors per granule" value. This will be used to look up the disk geometry for the current drive.
4A47
GOSUB to the drive parameter table reader at 4797H. This reads the byte at parameter offset 08H for drive C from the drive configuration table at 4700H+. Returns the sectors-per-granule value in Register A.
4A4A
AND A,1FH E6 1F
Mask the sectors-per-granule value with 1FH (binary 00011111) to extract the lower 5 bits. This isolates the granule size field from any flags stored in the upper bits.
4A4C
INC A 3C
INCrement Register A by 1. The stored value is one less than the actual sectors per granule count, so adding 1 converts to the true count.
4A4D
GOSUB to the 16-bit divide routine at 4BA9H. This divides the sector position in Register Pair HL by the sectors-per-granule count in Register A. Returns the granule number (extent index) in HL and the remainder (sector offset within granule) in Register A.
4A50
LD (4B0CH),A 32 0C 4B
Self-Modifying Code
Store the sector offset within the current granule (remainder from the divide) into 4B0CH. This value will be added to the physical starting sector of the granule later at 4B0BH to compute the exact disk sector to read.

Extent Table Walker Setup
The IX register points to the FCB. The extent table begins at IX+0EH within the directory entry buffer. Each extent entry is 3 bytes: 1 byte for the granule allocation byte (starting granule + length encoded), followed by a 2-byte sector count for that extent. There are up to 5 extent entries per directory entry, and a continuation pointer if more extents exist.

4A53
PUSH IX DD E5
Save the FCB base pointer (Register Pair IX) onto the stack. This will be swapped with HL to provide the base address for extent table calculations.
4A55
EX (SP),HL E3
Exchange the top of stack with HL. Now HL holds the IX value (FCB base address) and the stack holds the granule number (the divide quotient). This is a technique to transfer IX into HL without using a temporary register.
4A56
LD BC,000EH 01 0E 00
Load Register Pair BC with 000EH (decimal 14). This is the offset from the FCB base to the first extent entry in the directory entry.
4A59
ADD HL,BC 09
ADD 000EH to HL to point HL at the first extent entry (FCB base + 0EH). HL now points to the start of the extent table.
4A5A
POP BC C1
Restore the granule number from the stack into Register Pair BC. This is the target granule index we need to locate in the extent chain.
4A5B
LD A,05H 3E 05
Load Register A with 05H (decimal 5). This is the maximum number of extent entries per directory entry. The loop counter will count down from 5.
4A5D
LD DE,0000H 11 00 00
Load Register Pair DE with 0000H. DE will accumulate the running total of granules in all extents examined so far.

Extent Walk Loop
This loop iterates through up to 5 extent entries in the directory entry. For each entry, it reads the granule allocation byte, extracts the extent length, and accumulates granules in DE. When the accumulated count exceeds the target granule number (in BC), the matching extent has been found.

4A60
PUSH AF F5
Save the remaining extent count (Register A) onto the stack.
4A61
LD A,(HL) 7E
Load Register A with the granule allocation byte from the current extent entry. This byte encodes both the starting granule number and the extent length. A value of FFH indicates an unused/empty extent slot.
4A62
INC HL 23
INCrement HL to point past the granule allocation byte to the first byte of the 2-byte sector count for this extent.
4A63
INC A 3C
INCrement Register A by 1. If the granule allocation byte was FFH (empty extent), this produces 00H and sets the Z flag. This is a test for an unused extent entry.
4A64
If the Z FLAG (Zero) has been set because the granule allocation byte was FFH (empty extent), JUMP to 4A71H to skip this extent entry and advance to the next one.
4A66
PUSH HL E5
Save the current extent table pointer (pointing to the sector count bytes) onto the stack.
4A67
LD H,D 62
Load Register H with Register D (high byte of accumulated granule count). This copies DE into HL for the subtraction comparison.
4A68
LD L,E 6B
Load Register L with Register E (low byte of accumulated granule count). Register Pair HL now holds the running total of granules.
4A69
XOR A,A AF
Set Register A to ZERO and clear all flags, including the Carry flag, to prepare for the SBC instruction.
4A6A
SBC HL,BC ED 42
SUBtract the target granule number (Register Pair BC) from the accumulated granule count (Register Pair HL) with borrow. If the accumulated count is less than the target, Carry is set. If equal, Z is set. If greater, neither is set.
4A6C
If the CARRY FLAG has been set (accumulated granule count is less than the target granule number), JUMP to 4A7CH to add this extent's granules to the running total and continue walking.
4A6E
POP HL E1
Restore the extent table pointer from the stack. We found the matching extent (or went past it).
4A6F
If the Z FLAG (Zero) has been set (accumulated count exactly equals the target granule number), JUMP to 4ABCH to compute the physical sector from this extent entry. This is the exact match case.

Skip to Next Extent Entry
If the extent was empty (FFH) or we need to move past this entry, advance HL by 2 bytes (past the sector count) and continue to the next extent entry.

4A71
INC HL 23
INCrement HL to skip past the first byte of the sector count for this extent entry.
4A72
POP AF F1
Restore the remaining extent counter from the stack into Register A.
4A73
DEC A 3D
DECrement the extent counter by 1. There are up to 5 extent entries per directory entry.
4A74
If the Z FLAG (Zero) has been set because all 5 extent entries have been examined without finding the target, JUMP to 4A8FH to handle the extent chain continuation (follow the directory chain pointer to the next directory entry).
4A76
LD E,(HL) 5E
Load Register E with the low byte of the sector count for the next extent entry. This is used as the accumulated count for chain-following purposes.
4A77
INC HL 23
INCrement HL past the low byte of the sector count.
4A78
LD D,(HL) 56
Load Register D with the high byte of the sector count. Register Pair DE now holds the cumulative sector count up to and including this extent.
4A79
INC HL 23
INCrement HL to point to the next extent entry's granule allocation byte.
4A7A
LOOP BACK to 4A60H to process the next extent entry in the directory.

Extent Granule Accumulation
We arrive here when the accumulated granule count was less than the target (Carry set from SBC at 4A6AH). We need to add this extent's granule count to the running total and continue to the next extent.

4A7C
INC H 24
INCrement Register H. After the SBC HL,BC at 4A6AH, H holds the high byte of the difference. INCrementing it tests whether H was FFH (meaning the difference high byte was -1, so the result was a small negative number within 256). This is used to determine if we are within the current extent.
4A7D
LD A,L 7D
Load Register A with Register L, which holds the low byte of (accumulated - target). If H was FFH and is now 00H (Z set), this value is the negative offset representing how many granules into this extent the target falls.
4A7E
POP HL E1
Restore the extent table pointer from the stack (saved at 4A66H).
4A7F
If the NZ FLAG (Not Zero) has been set (the target is not within this extent), JUMP to 4A71H to skip to the next extent entry.

Target Found Within This Extent
The target granule falls within this extent. Register A holds the negative of the granule offset from the end of this extent. We need to check whether the sector offset fits within this extent's granule allocation and compute the physical sector address.

4A81
PUSH DE D5
Save the accumulated granule count in Register Pair DE onto the stack.
4A82
LD E,A 5F
Load Register E with Register A (the negative offset into this extent). This preserves the offset while we examine the extent's allocation byte.
4A83
LD A,(HL) 7E
Load Register A with the granule allocation byte from the current extent entry (HL points to the first sector count byte, but the allocation byte was already read). Actually, HL was restored from the stack and points to the sector count position. This reads the low byte of the sector count.
4A84
AND A,1FH E6 1F
Mask with 1FH to extract the lower 5 bits. For the sector count byte, this extracts a granule count or length field.
4A86
ADD A,E 83
ADD Register E (the negative offset) to Register A. If the result produces a carry, the target granule is within this extent's allocation range.
4A87
LD A,E 7B
Load Register A with Register E (the negative granule offset). This preserves the offset value for use in computing the physical sector.
4A88
POP DE D1
Restore the accumulated granule count from the stack into Register Pair DE.
4A89
If the NO CARRY FLAG has been set (target granule is not within this extent's range), JUMP to 4A71H to continue to the next extent entry.
4A8B
NEG ED 44
Negate Register A (compute 0 - A). This converts the negative offset into a positive granule offset from the start of this extent.
4A8D
JUMP to 4ABCH to compute the physical sector address from the extent's starting granule plus the offset in Register A.

4A8FH - Extent Chain Continuation (Overlay Allocation)

When all 5 extent entries in the current directory entry have been examined without finding the target sector, this routine handles the continuation. It invokes the overlay loader via RST 28H to load the directory management overlay that can follow extent chain pointers to the next directory entry. Uses self-modifying code to pass the remaining sector offset to the overlay.

4A8F
LD A,(4B0CH) 3A 0C 4B
Load Register A with the sector offset within the granule, previously stored at 4B0CH by the self-modifying code at 4A50H.
4A92
LD (4AB1H),A 32 B1 4A
Self-Modifying Code
Store the sector offset into the operand byte at 4AB1H. This patches the ADD A,00H instruction at 4AB0H so that it becomes ADD A,nn where nn is the sector offset. This value will be used after the overlay returns to compute the final physical sector.
4A95
LD (0028H),A 32 28 00
Store Register A to address 0028H. On the Model I, address 0028H is within ROM (RST 28H vector), so this write has no effect - it is consumed as a NOP-equivalent for timing or displacement purposes.
4A98
LD A,(430EH) 3A 0E 43
Load Register A with the currently loaded overlay ID from 430EH. This saves the current overlay state before loading a new overlay for extent chain processing.
4A9B
LD (4AA3H),A 32 A3 4A
Self-Modifying Code
Store the current overlay ID into the operand byte at 4AA3H. This patches the LD A,00H instruction at 4AA2H to restore the correct overlay ID after the extent chain overlay finishes.
4A9E
GOSUB to 4AB9H to invoke the overlay loader via RST 28H with SVC code 9AH. This loads the extent chain management overlay and executes it. The overlay follows the continuation pointer in the directory entry to find the next directory entry in the chain.
4AA1
PUSH AF F5
Save the flags and return code from the overlay call onto the stack.
4AA2
LD A,00H 3E 00
Self-Modifying Code
Load Register A with 00H. The operand byte at 4AA3H was patched at 4A9BH to hold the original overlay ID. At runtime this becomes LD A,nn where nn is the overlay that was loaded before this routine was called.
4AA4
AND A,8FH E6 8F
Mask Register A with 8FH (binary 10001111). This preserves bit 7 (overlay call flag) and bits 0-3 (overlay file number) while clearing bits 4-6 (function number). This extracts just the overlay file identity without the function code.
4AA6
CP A,83H FE 83
Compare Register A against 83H. This checks if the original overlay was overlay file 3 (with bit 7 set). Overlay 83H is a specific system overlay that requires reloading.
4AA8
If the Z FLAG (Zero) has been set (the original overlay was overlay 83H), GOSUB to the overlay loader at 4C53H to reload it. This restores the original overlay context after the extent chain overlay modified it.
4AAB
POP AF F1
Restore the flags and return code from the stack.
4AAC
RET NZ C0
If the NZ FLAG (Not Zero) has been set (the overlay returned an error), return to the caller with the error code in Register A.
4AAD
LD (4AECH),A 32 EC 4A
Self-Modifying Code
Store Register A (which is 00H on success) into the operand at 4AECH. This patches the ADD A,00H instruction at 4AEBH in the physical sector computation below.
4AB0
LD A,00H 3E 00
Self-Modifying Code
Load Register A with 00H. The operand at 4AB1H was patched at 4A92H to hold the sector offset within the granule. At runtime this becomes LD A,nn where nn is the sector offset.
4AB2
LD (4B0CH),A 32 0C 4B
Store the sector offset back into 4B0CH for use by the physical sector computation at 4B0BH.
4AB5
If the NO CARRY FLAG has been set, JUMP to 4AE5H to begin the physical sector computation. This path is taken when the overlay returned successfully and there are no additional extent entries to process.
4AB7
JUMP to 4ADCH to handle an alternate entry into the sector computation. This path is taken when the Carry flag was set, indicating a different allocation state.

4AB9H - RST 28H Overlay Call (SVC 9AH)

Short stub that invokes the RST 28H overlay loader with SVC code 9AH. This loads and executes the extent chain management overlay.

4AB9
LD A,9AH 3E 9A
Load Register A with 9AH, the SVC code for the extent chain management function. This encodes the overlay file number and function to execute.
4ABB
RST 28H EF
Execute RST 28H to invoke the overlay loader at 4C28H. The SVC code in Register A (9AH) identifies which overlay to load and which function within it to call. Execution transfers to the overlay, which returns here when complete.

4ABCH - Compute Physical Sector from Extent

Given a granule offset (in Register A) into the matching extent entry, this routine computes the physical disk sector by looking up the extent's starting granule, calculating the track and sector from the drive geometry, and adding the sector offset stored at 4B0CH. HL points to the extent entry's sector count position in the directory buffer.

4ABC
LD (4AECH),A 32 EC 4A
Self-Modifying Code
Store Register A (the granule offset within this extent) into the operand byte at 4AECH. This patches the ADD A,00H instruction at 4AEBH to become ADD A,nn, where nn is the granule offset. This value will be combined with the extent's starting granule later.
4ABF
LD B,(HL) 46
Load Register B with the byte at (HL). HL points to the high byte of the sector count for this extent entry. This byte also contains extent metadata.
4AC0
DEC HL 2B
DECrement HL to point to the low byte of the sector count for this extent entry.
4AC1
LD C,(HL) 4E
Load Register C with the low byte of the sector count. Register Pair BC now holds the sector count information for this extent, though the high byte may contain flags in its upper bits.
4AC2
INC HL 23
INCrement HL back to the high byte position, restoring the pointer.
4AC3
POP AF F1
Restore the extent counter from the stack (pushed at 4A60H). This cleans up the stack frame from the extent walk loop.
4AC4
CPL 2F
Complement Register A (ones complement: A = NOT A). The extent counter was counting down from 5 to 0, so complementing converts it to a value representing which extent entry (0-4) was matched, in inverted form.
4AC5
ADD A,04H C6 04
ADD 04H to Register A. This adjusts the complemented extent counter to produce a zero-based extent index offset (0 for the first extent, 1 for the second, etc.).
4AC7
If the NO CARRY FLAG has been set (the extent index addition did not overflow), JUMP to 4AE3H. This path is taken when the extent is the first entry and no LDDR shift is needed.

Extent Entry Reorder via LDDR
When the matching extent is not the first entry, the code shifts extent entries to move the matched entry to the front of the extent table. This is a "move to front" optimization that speeds up subsequent accesses to the same file region, since the most recently used extent will be found first in future walks.

4AC9
INC A 3C
INCrement Register A by 1 to adjust the extent index for the LDDR byte count calculation.
4ACA
RLCA 07
Rotate Register A left. This begins multiplying the extent index by 4 (since each extent entry is approximately 3-4 bytes, this computes the byte count for the block move).
4ACB
RLCA 07
Rotate Register A left again. Register A now holds the extent index multiplied by 4, which is the number of bytes to shift in the LDDR block move.
4ACC
PUSH BC C5
Save the extent's sector count (Register Pair BC) onto the stack.
4ACD
PUSH DE D5
Save the accumulated granule count (Register Pair DE) onto the stack.
4ACE
LD C,A 4F
Load Register C with Register A (byte count for the LDDR block move).
4ACF
LD B,00H 06 00
Load Register B with 00H. Register Pair BC now holds the byte count for the LDDR move (0 to 20 bytes).
4AD1
EX DE,HL EB
Exchange Register Pairs DE and HL. DE now points to the current position in the extent table, and HL holds the accumulated count.
4AD2
LD HL,0FFFCH 21 FC FF
Load Register Pair HL with 0FFFCH (decimal -4 or 65532). This is the offset to the destination for the LDDR: 4 bytes before the source, creating room for the matched extent entry at the front.
4AD5
ADD HL,DE 19
ADD DE (current extent table position) to HL (-4 offset). HL now points 4 bytes before the current position, which is the LDDR destination address.
4AD6
LDDR ED B8
Block Move (Decrementing)
Copy BC bytes from (HL) to (DE), decrementing both HL and DE after each byte. Source = HL (extent table entries after the matched one), Destination = DE (shifted forward by one entry slot). This shifts the extent entries to make room at the front for the matched entry.
4AD8
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now points to the destination where the matched extent entry should be written.
4AD9
POP DE D1
Restore the accumulated granule count from the stack into Register Pair DE.
4ADA
POP BC C1
Restore the extent's sector count from the stack into Register Pair BC.
4ADB
LD (DE),A 12
Store Register A into the location pointed to by DE. This writes the granule allocation byte for the matched extent into its new position at the front of the extent table.
4ADC
LD (HL),B 70
Store Register B (high byte of sector count) into the extent entry's new position.
4ADD
DEC HL 2B
DECrement HL to point to the low byte position of the sector count in the new extent entry.
4ADE
LD (HL),C 71
Store Register C (low byte of sector count) into the extent entry's new position.
4ADF
DEC HL 2B
DECrement HL to the next byte of the extent entry.
4AE0
LD (HL),D 72
Store Register D into the extent entry position.
4AE1
DEC HL 2B
DECrement HL to the next byte of the extent entry.
4AE2
LD (HL),E 73
Store Register E into the extent entry position.

Physical Sector Computation
At this point the matched extent's granule allocation byte has been positioned. Now compute the physical disk sector from the extent's starting granule, the granule offset, and the drive geometry. Register Pair BC holds the extent metadata, with the granule allocation byte encoding the starting granule number in its upper bits and the extent length in its lower bits.

4AE3
LD H,B 60
Load Register H with Register B (high byte of extent sector count, which also contains the granule allocation encoding).
4AE4
LD L,C 69
Load Register L with Register C (low byte of extent sector count). HL now holds the extent metadata word.
4AE5
LD A,H 7C
Load Register A with Register H (the high byte containing the granule allocation encoding).
4AE6
RLCA 07
Rotate Register A left through carry. This begins extracting the starting granule number from the upper bits of the allocation byte.
4AE7
RLCA 07
Rotate Register A left again.
4AE8
RLCA 07
Rotate Register A left a third time. The starting granule number from bits 5-7 is now in bits 0-2 of Register A.
4AE9
AND A,07H E6 07
Mask with 07H to isolate the 3-bit starting granule number (0-7) from the rotation result.
4AEB
ADD A,00H C6 00
Self-Modifying Code
ADD 00H to Register A. The operand at 4AECH was patched at 4ABCH (or 4AADH) to hold the granule offset within this extent. At runtime this becomes ADD A,nn, adding the granule offset to the starting granule number to produce the actual granule number on disk.
4AED
LD E,A 5F
Load Register E with Register A (the computed granule number on disk).
4AEE
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset 06H. This is needed to look up the drive's geometry parameters.
4AF1
LD A,08H 3E 08
Load Register A with 08H, the parameter table offset for the sectors-per-granule value.
4AF3
GOSUB to the drive parameter reader at 4797H to fetch the sectors-per-granule value for drive C from the parameter table at 4700H+.
4AF6
PUSH AF F5
Save the sectors-per-granule value onto the stack for later use in computing the sector offset within the track.
4AF7
RLCA 07
Rotate Register A left. This begins extracting the upper bits of the sectors-per-granule byte, which encode the number of granules per track.
4AF8
RLCA 07
Rotate Register A left again.
4AF9
RLCA 07
Rotate Register A left a third time. The granules-per-track value from bits 5-7 is now in bits 0-2.
4AFA
AND A,07H E6 07
Mask with 07H to isolate the 3-bit granules-per-track value (0-7).
4AFC
INC A 3C
INCrement Register A by 1. The stored value is one less than actual, so adding 1 gives the true granules per track.
4AFD
GOSUB to the 8-bit divide routine at 4B7BH. Divides Register E (granule number) by Register A (granules per track). Returns quotient in Register A (track number) and remainder in Register E (granule within track).
4B00
ADD A,L 85
ADD Register L to Register A. Register L holds a track offset value (from the extent metadata low byte), which is added to the quotient to produce the final physical track number.
4B01
LD D,A 57
Load Register D with the computed physical track number.
4B02
POP AF F1
Restore the sectors-per-granule value from the stack.
4B03
AND A,1FH E6 1F
Mask with 1FH to extract the lower 5 bits (sectors per granule minus 1).
4B05
INC A 3C
INCrement Register A by 1 to get the true sectors per granule count.
4B06
PUSH DE D5
Save Register Pair DE (D = track number, E = granule within track) onto the stack.
4B07
GOSUB to the 8-bit multiply routine at 4B6CH. Multiplies Register E (granule within track) by Register A (sectors per granule). Returns the product in Register A, which is the starting sector offset within the track for this granule.
4B0A
POP DE D1
Restore Register Pair DE (D = track number, E = granule within track) from the stack.
4B0B
ADD A,00H C6 00
Self-Modifying Code
ADD 00H to Register A. The operand at 4B0CH was patched at 4A50H (and 4AB2H) to hold the sector offset within the granule. At runtime this becomes ADD A,nn, adding the intra-granule offset to the granule's starting sector to produce the final physical sector number within the track.
4B0D
LD E,A 5F
Load Register E with the computed physical sector number. Register D already holds the track number. Register Pair DE now contains the complete physical disk address: D = track, E = sector.
4B0E
XOR A,A AF
Set Register A to ZERO and set the Z flag, indicating success (no error).
4B0F
RET C9
Return to the caller with Register A = 00H (success), Z flag set, and Register Pair DE holding the physical disk address (D = track, E = sector).

4B10H - Directory Sector Read

Reads a directory sector from disk into the directory buffer. Calls the drive geometry decoder at 4B37H to determine the directory track and sector, then performs the disk read. Returns with Z flag set on success or NZ with error code in Register A.

4B10
PUSH DE D5
Save Register Pair DE onto the stack (preserves caller's context).
4B11
GOSUB to the directory geometry decoder at 4B37H. This determines the physical track and sector for the directory entry, using the directory entry number in Register B. Returns with HL pointing to the directory buffer and DE containing the disk address.
4B14
PUSH HL E5
Save the directory buffer pointer onto the stack.
4B15
LD L,00H 2E 00
Load Register L with 00H. This sets the low byte of the buffer address to the start of a page boundary (256-byte aligned buffer).
4B17
GOSUB to the sector read routine at 4B45H. This reads the directory sector at the disk address in DE into the buffer at HL. Returns with Z flag on success, NZ with error code on failure.
4B1A
POP HL E1
Restore the directory buffer pointer from the stack.
4B1B
LD A,11H 3E 11
Load Register A with 11H (error code 17 decimal: "disk read error"). This preloads the error code in case the read failed.
4B1D
POP DE D1
Restore Register Pair DE from the stack.
4B1E
RET C9
Return to the caller. The Z flag from the CALL at 4B17H determines success/failure. If the read succeeded, Z is set and A = 11H is ignored. If the read failed, NZ is set and A = 11H is the error code.

4B1FH - Directory Sector Write

Writes a directory sector from the directory buffer to disk. Similar to the read routine at 4B10H but performs a write operation. Includes verification: if the write reports a correctable error, it attempts a read-back to check whether the GAT sector was also affected.

4B1F
PUSH DE D5
Save Register Pair DE onto the stack.
4B20
GOSUB to the directory geometry decoder at 4B37H to compute the track and sector for this directory entry.
4B23
LD L,00H 2E 00
Load Register L with 00H to set the buffer address to the page boundary.
4B25
GOSUB to the sector write routine at 4768H. This writes the directory sector from the buffer at HL to the disk address in DE. The entry point at 4768H uses Register B = 0EH as a function code for "write sector".
4B28
If the Z FLAG (Zero) has been set (write succeeded), GOSUB to 4772H to perform a verify read-back. This entry point uses Register B = 0AH as a function code for "read with verify".
4B2B
CP A,06H FE 06
Compare Register A against 06H (error code 6: "write fault" or sector-level error). This checks if the write/verify returned a specific recoverable error.
4B2D
POP DE D1
Restore Register Pair DE from the stack.
4B2E
RET Z C8
If the Z FLAG (Zero) has been set (error code was exactly 06H), return with Z set. This treats error 06H as a non-fatal condition that the caller can handle.
4B2F
CP A,0FH FE 0F
Compare Register A against 0FH (error code 15: "write/verify failure"). This checks for a more serious verification error.
4B31
LD A,12H 3E 12
Load Register A with 12H (error code 18 decimal: "disk write error"). This is the error code returned for write failures other than the specific codes checked above.
4B33
RET NZ C0
If the NZ FLAG (Not Zero) has been set (error was not 0FH), return with NZ and error code 12H in Register A.
4B34
SUB A,03H D6 03
SUBtract 03H from Register A (12H - 03H = 0FH). This produces 0FH (error code 15: "write/verify failure") as the specific error code. The NZ flag is set since the result is non-zero.
4B36
RET C9
Return to the caller with Register A = 0FH (write/verify failure) and NZ flag set.

4B37H - Directory Geometry Decoder

Converts a directory entry number (in Register B) into a physical disk address and buffer pointer. The directory entry number encodes both the directory sector and the position within that sector. This routine extracts the sector number, computes the track, and returns the buffer pointer in HL with the physical disk address ready for I/O.

4B37
GOSUB to 4B65H to read the directory track number from the drive parameter table. Returns with Register D holding the directory track number for the current drive.
4B3A
LD A,B 78
Load Register A with Register B (the directory entry number). This encodes both the sector within the directory track and the entry position within that sector.
4B3B
AND A,0E0H E6 E0
Mask with 0E0H (binary 11100000) to extract the upper 3 bits of the directory entry number. These bits identify the page (256-byte block) within the directory buffer.
4B3D
LD L,A 6F
Load Register L with the masked value. This sets the low byte of the buffer pointer to the page offset (00H, 20H, 40H, 60H, 80H, A0H, C0H, or E0H).
4B3E
LD H,42H 26 42
Load Register H with 42H. Register Pair HL now points to the directory entry within the buffer at 4200H-42FFH. The entry is at 42xxH where xx is the page offset from the upper bits of the entry number.
4B40
XOR A,B A8
XOR Register A with Register B. Since A still holds the masked upper bits and B holds the full entry number, this extracts the lower 5 bits (the sector number within the directory track, minus an offset).
4B41
ADD A,02H C6 02
ADD 02H to Register A. The directory sectors start at sector 2 on the directory track (sectors 0 and 1 are the GAT and HIT), so this adjusts the zero-based sector index to the actual sector number.
4B43
LD E,A 5F
Load Register E with the computed directory sector number. Register D already holds the track number (from 4B65H). Register Pair DE now holds the complete physical disk address for this directory sector.
4B44
RET C9
Return to the caller with HL pointing to the directory entry in the buffer and DE holding the physical disk address (D = track, E = sector).

4B45H - Sector Read with Drive Select and Track Cache

Reads a sector from disk with automatic drive selection, track seeking, and track cache management. Handles retries by re-reading the GAT sector (sector 0) on the same track when a read error occurs, to ensure the track cache stays valid. Register Pair HL holds the buffer address, Register Pair DE holds the disk address (D = track, E = sector), Register C holds the drive number.

4B45
GOSUB to the disk I/O dispatcher at 4777H with function code 09H (sector read). This performs drive selection, track seeking, and the actual sector read into the buffer at HL from the disk address in DE.
4B48
SUB A,06H D6 06
SUBtract 06H from Register A (the return code). If A was 06H (success with specific condition), this produces 00H and sets Z. This tests for a specific return status.
4B4A
RET Z C8
If the Z FLAG (Zero) has been set (return code was exactly 06H), return to the caller with Z set, treating this as a success.
4B4B
ADD A,06H C6 06
ADD 06H back to Register A to restore the original return code.
4B4D
RET NZ C0
If the NZ FLAG (Not Zero) has been set (original return code was non-zero and not 06H), return to the caller with the error code. This exits on any genuine error.

Track Cache Refresh
If we reach here, the read was successful (return code 00H). Now perform a follow-up read of the directory/GAT area on the same track to keep the track cache updated. This ensures the cached track information stays synchronized with the actual disk state.

4B4E
PUSH DE D5
Save the original disk address (Register Pair DE) onto the stack.
4B4F
LD DE,0000H 11 00 00
Load Register Pair DE with 0000H. This sets the disk address to track 0, sector 0 (the GAT sector) for the cache refresh read.
4B52
GOSUB to the disk I/O dispatcher at 4777H to read sector 0 of track 0. This refreshes the cached directory/GAT information.
4B55
POP DE D1
Restore the original disk address from the stack.
4B56
RET NZ C0
If the NZ FLAG (Not Zero) has been set (the cache refresh read failed), return with the error code.
4B57
PUSH HL E5
Save the buffer pointer onto the stack.
4B58
INC HL 23
INCrement HL to point to the second byte of the buffer. After reading sector 0, the buffer contains the GAT data, and offset 02H holds track-related information.
4B59
INC HL 23
INCrement HL again to point to offset 02H in the buffer.
4B5A
LD D,(HL) 56
Load Register D with the byte at buffer offset 02H. This is a track-related value from the GAT that may update the cached track number.
4B5B
LD H,09H 26 09
Load Register H with 09H, the parameter table offset for the directory track number. This will be used to store the cached track value into the drive parameter table.
4B5D
GOSUB to the parameter table address calculator at 47A0H. This computes the address in the drive parameter table for parameter offset 09H of drive C. Returns the address in HL.
4B60
LD L,A 6F
Load Register L with Register A (the computed offset into the parameter table).
4B61
LD (HL),D 72
Store Register D (the track value from the GAT) into the drive parameter table. This updates the cached directory track number for the current drive.
4B62
POP HL E1
Restore the buffer pointer from the stack.
4B63
LOOP BACK to 4B45H to re-read the original sector now that the track cache has been updated. This ensures the buffer contains the correct data.

4B65H - Read Directory Track Number

Reads the directory track number from the drive parameter table for the current drive. Returns the track number in Register D.

4B65
LD A,09H 3E 09
Load Register A with 09H, the parameter table offset for the directory track number.
4B67
GOSUB to the drive parameter reader at 4797H. Reads the byte at parameter offset 09H for drive C from the drive configuration table at 4700H+. Returns the directory track number in Register A.
4B6A
LD D,A 57
Load Register D with the directory track number (from Register A).
4B6B
RET C9
Return to the caller with Register D holding the directory track number for the current drive.

4B6CH - 8-Bit Multiply (A = D x E)

Performs an 8-bit unsigned multiplication: Register D multiplied by Register E, with the 8-bit product returned in Register A. Uses a shift-and-add algorithm over 8 iterations. Register Pair BC is preserved.

4B6C
PUSH BC C5
Save Register Pair BC onto the stack (preserved across the multiply).
4B6D
LD D,A 57
Load Register D with Register A (the multiplicand). On entry, A holds one of the two values to multiply, and E holds the other.
4B6E
XOR A,A AF
Set Register A to ZERO. This initializes the accumulator for the shift-and-add product.
4B6F
LD B,08H 06 08
Load Register B with 08H (decimal 8). This is the loop counter for 8 iterations, processing one bit of the multiplier per iteration.

Shift-and-Add Multiply Loop
For each bit of Register E (the multiplier), shift the product left by 1, then shift E left. If the bit shifted out of E was a 1, add the multiplicand (D) to the accumulator.

4B71
ADD A,A 87
ADD Register A to itself (shift left by 1). This doubles the partial product.
4B72
SLA E CB 23
Shift Register E (multiplier) left by 1. The highest bit is shifted into the Carry flag. If Carry is set, this bit of the multiplier is 1 and we need to add the multiplicand.
4B74
If the NO CARRY FLAG has been set (current bit of the multiplier is 0), JUMP to 4B77H to skip the addition.
4B76
ADD A,D 82
ADD Register D (multiplicand) to Register A (partial product). This adds the multiplicand for the current bit position of the multiplier.
4B77
DECrement B and loop back to 4B71H if not zero. Repeats for all 8 bits of the multiplier.
4B79
POP BC C1
Restore Register Pair BC from the stack.
4B7A
RET C9
Return to the caller with Register A holding the 8-bit product of D x E.

4B7BH - 8-Bit Divide (E / A, Quotient in A, Remainder in E)

Performs an 8-bit unsigned division: Register E divided by Register A (the divisor). Returns the quotient in Register A and the remainder in Register E. Uses a shift-and-subtract restoring division algorithm over 8 iterations. Register C is used as a temporary for the divisor. Register Pair BC is preserved.

4B7B
PUSH BC C5
Save Register Pair BC onto the stack (preserved across the divide).
4B7C
LD C,A 4F
Load Register C with Register A (the divisor). C holds the divisor throughout the loop.
4B7D
LD B,08H 06 08
Load Register B with 08H (decimal 8). This is the loop counter for 8 iterations, processing one bit per iteration.
4B7F
XOR A,A AF
Set Register A to ZERO. This initializes the remainder accumulator.

Restoring Division Loop
For each bit, shift the dividend (E) left, bringing the top bit into the remainder (A) via RLA. Then try to subtract the divisor. If the subtraction succeeds (no borrow), set the corresponding quotient bit in E. If not, restore the remainder and leave the quotient bit as 0.

4B80
SLA E CB 23
Shift Register E (dividend/quotient) left by 1. The highest bit is shifted into Carry, making room for the quotient bit at bit 0.
4B82
RLA 17
Rotate Register A left through Carry. This shifts the bit from E into the remainder accumulator, building up the partial remainder.
4B83
CP A,C B9
Compare Register A (partial remainder) against Register C (divisor). If A >= C, the CARRY FLAG is clear and we can subtract.
4B84
If the CARRY FLAG has been set (remainder < divisor), JUMP to 4B88H to skip the subtraction. The quotient bit remains 0.
4B86
SUB A,C 91
SUBtract the divisor (Register C) from the remainder (Register A). The subtraction succeeded, so the current quotient bit is 1.
4B87
INC E 1C
INCrement Register E to set bit 0 (the current quotient bit) to 1. Since SLA shifted E left and put 0 in bit 0, INC sets it to 1.
4B88
DECrement B and loop back to 4B80H if not zero. Repeats for all 8 bits.
4B8A
LD C,A 4F
Load Register C with the final remainder (Register A).
4B8B
LD A,E 7B
Load Register A with Register E (the quotient).
4B8C
LD E,C 59
Load Register E with Register C (the remainder). Now A = quotient, E = remainder.
4B8D
POP BC C1
Restore Register Pair BC from the stack.
4B8E
RET C9
Return to the caller with Register A = quotient and Register E = remainder.

4B8FH - 24-Bit Multiply (HL:A = DE x C)

Performs a 24-bit multiplication: the 16-bit value in Register Pair DE multiplied by the 8-bit value in Register C. Returns the 24-bit result with the high 16 bits in Register Pair HL and the low 8 bits in Register A. Used for position calculations where sector counts exceed 8 bits. Register Pair BC is preserved.

4B8F
PUSH BC C5
Save Register Pair BC onto the stack.
4B90
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now holds the 16-bit multiplicand (was in DE) and DE is free.
4B91
LD C,A 4F
Load Register C with Register A. On entry, A holds the 8-bit multiplier value (or it was loaded into C already by the caller - depending on calling convention).
4B92
LD HL,0000H 21 00 00
Load Register Pair HL with 0000H. This initializes the high 16 bits of the 24-bit product accumulator.
4B95
LD A,L 7D
Load Register A with Register L (which is 00H). Register A serves as the low 8 bits of the 24-bit accumulator (A:HL = 24-bit product, though organized as HL:A for the return).
4B96
LD B,08H 06 08
Load Register B with 08H (decimal 8). Loop counter for 8 iterations, one for each bit of the multiplier in Register C.

24-Bit Shift-and-Add Loop
For each bit of Register C (the multiplier), shift the 24-bit accumulator (HL:A) left by 1, then rotate C left to get the next multiplier bit. If the bit is 1, add the multiplicand (DE) to HL and propagate carry into A.

4B98
ADD HL,HL 29
ADD HL to itself (shift HL left by 1). This doubles the high 16 bits of the partial product.
4B99
RLA 17
Rotate Register A left through Carry. This shifts the carry from the ADD HL,HL into the low accumulator, extending the shift to the full 24-bit value.
4B9A
RLC C CB 01
Rotate Register C (multiplier) left through Carry. The highest bit of C is shifted into the Carry flag. If Carry is set, this bit of the multiplier is 1.
4B9C
If the NO CARRY FLAG has been set (current bit of multiplier is 0), JUMP to 4BA1H to skip the addition.
4B9E
ADD HL,DE 19
ADD the multiplicand (Register Pair DE) to the high 16 bits of the product (Register Pair HL).
4B9F
ADC A,00H CE 00
ADD the Carry flag (from the ADD HL,DE) to Register A. This propagates any overflow from the 16-bit addition into the high byte of the 24-bit result.
4BA1
DECrement B and loop back to 4B98H if not zero. Repeats for all 8 bits of the multiplier.

Result Rearrangement
After the loop, the 24-bit product is spread across A (bits 23-16), H (bits 15-8), and L (bits 7-0). The code rearranges to return HL = high 16 bits and A = low 8 bits.

4BA3
LD C,A 4F
Load Register C with Register A (bits 23-16 of the product).
4BA4
LD A,L 7D
Load Register A with Register L (bits 7-0 of the product). A now holds the low 8 bits of the result.
4BA5
LD L,H 6C
Load Register L with Register H (bits 15-8). L now holds the middle byte.
4BA6
LD H,C 61
Load Register H with Register C (bits 23-16). H now holds the high byte. Register Pair HL = high 16 bits of product, Register A = low 8 bits.
4BA7
POP BC C1
Restore Register Pair BC from the stack.
4BA8
RET C9
Return to the caller with the 24-bit product: HL = high 16 bits, A = low 8 bits.

4BA9H - 16-Bit Divide (HL / A, Quotient in HL, Remainder in A)

Performs a 16-bit unsigned division: Register Pair HL divided by Register A (the divisor). Returns the quotient in Register Pair HL and the remainder in Register A. Uses a 16-bit restoring division algorithm over 16 iterations. Register Pair DE is preserved.

4BA9
PUSH DE D5
Save Register Pair DE onto the stack.
4BAA
LD D,A 57
Load Register D with Register A (the divisor). D holds the divisor throughout the loop.
4BAB
LD E,10H 1E 10
Load Register E with 10H (decimal 16). This is the loop counter for 16 iterations, processing one bit per iteration for the 16-bit dividend.
4BAD
XOR A,A AF
Set Register A to ZERO. This initializes the remainder accumulator.

16-Bit Restoring Division Loop
For each of the 16 bits, shift HL left (bringing the top bit into A via RLA), then try to subtract the divisor from the partial remainder. If successful, set the quotient bit; otherwise, leave it as 0.

4BAE
ADD HL,HL 29
ADD HL to itself (shift the dividend/quotient left by 1). The highest bit of HL is shifted into the Carry flag.
4BAF
RLA 17
Rotate Register A left through Carry. This shifts the bit from HL into the remainder accumulator.
4BB0
If the CARRY FLAG has been set (overflow from RLA, meaning the remainder >= 256), JUMP to 4BB5H to subtract the divisor unconditionally since the remainder must be >= divisor.
4BB2
CP A,D BA
Compare Register A (partial remainder) against Register D (divisor). If A >= D, the Carry flag is clear.
4BB3
If the CARRY FLAG has been set (remainder < divisor), JUMP to 4BB7H to skip the subtraction. The quotient bit remains 0.
4BB5
SUB A,D 92
SUBtract the divisor (Register D) from the remainder (Register A). The subtraction succeeded, so the current quotient bit should be 1.
4BB6
INC L 2C
INCrement Register L to set bit 0 (the current quotient bit) to 1. Since ADD HL,HL shifted L left and put 0 in bit 0, INC sets it to 1.
4BB7
DEC E 1D
DECrement Register E (loop counter) by 1.
4BB8
If the NZ FLAG (Not Zero) has been set (loop counter not yet zero), LOOP BACK to 4BAEH for the next iteration.
4BBA
POP DE D1
Restore Register Pair DE from the stack.
4BBB
RET C9
Return to the caller with Register Pair HL = quotient, Register A = remainder.

4BBCH - Keyboard Input and Character Processing

This routine handles keyboard input with character processing including shift key detection, key debouncing, CAPS LOCK toggle, BREAK key checking, and conversion from keyboard scan codes to ASCII values. It scans the keyboard matrix at memory-mapped addresses 3801H-3880H, decodes the key position, and returns the ASCII character in Register A. Uses the ROM keyboard decode routine at 03FAH. IX+03H holds the last key code (for auto-repeat detection) and IX+04H holds the debounce/repeat timer.

4BBC
LD (HL),E 73
Store Register E into the memory location pointed to by HL. This saves the previous scan result as part of the keyboard state machine initialization.
4BBD
AND A,E A3
AND Register A with Register E. This masks the current keyboard scan against the previous state to detect newly pressed keys (edge detection).
4BBE
If the Z FLAG (Zero) has been set (no new keys detected after masking), JUMP to 4C02H to return with A = 00H indicating no key pressed.
4BC0
GOSUB to the ROM keyboard decode routine at 03FAH. This converts the raw keyboard matrix scan into an ASCII character value. Returns the decoded character in Register A.
4BC3
LD BC,1A9FH 01 9F 1A
Load Register Pair BC with 1A9FH. Register B = 1AH (26 decimal, the number of alphabetic characters A-Z) and Register C = 9FH (the inverse mask for shift detection, or a boundary value for uppercase conversion). These values are used by the character validation logic below.
4BC6
LD HL,4039H 21 39 40
Point Register Pair HL to 4039H, a keyboard state flags byte. This byte tracks the CAPS LOCK state and other keyboard mode flags.
4BC9
CP A,B B8
Compare Register A (the decoded character) against Register B (1AH = 26). This checks if the character code is less than 26, which would indicate a control character (Ctrl+A through Ctrl+Z).
4BCA
If the NZ FLAG (Not Zero) has been set (character is not a control code matching B), JUMP to 4BD0H to continue character processing.
4BCC
BIT 2,(HL) CB 56
Test bit 2 of the keyboard state flags at 4039H. Bit 2 indicates whether a specific keyboard mode is active (such as a control key modifier state).
4BCE
If the Z FLAG (Zero) has been set (bit 2 is clear, mode not active), JUMP to 4C02H to return with A = 00H (no key).
4BD0
LD L,3CH 2E 3C
Load Register L with 3CH to point HL to 403CH. This address holds the CAPS LOCK toggle state flag.
4BD2
BIT 1,(HL) CB 4E
Test bit 1 of the CAPS LOCK flag at 403CH. If set, CAPS LOCK is active and alphabetic characters should be uppercased.
4BD4
LD HL,3880H 21 80 38
Point Register Pair HL to 3880H, the keyboard row for the SHIFT key. On the Model I, address 3880H is keyboard row 7, which contains only the SHIFT key.
4BD7
If the Z FLAG (Zero) has been set (CAPS LOCK is not active), JUMP to 4BDAH to skip the CAPS LOCK uppercase conversion.
4BD9
OR A,L B5
OR Register A with Register L (80H). When CAPS LOCK is active, this sets bit 7 of the character code, which will be used later to force uppercase conversion.
4BDA
LD D,A 57
Load Register D with Register A (the character code, possibly with CAPS LOCK flag in bit 7). Save the character for later use.
4BDB
RES 5,A CB AF
Reset bit 5 of Register A. This converts lowercase letters (61H-7AH) to uppercase (41H-5AH) by clearing the lowercase bit. The character is now uppercase for comparison purposes.
4BDD
SUB A,41H D6 41
SUBtract 41H (ASCII 'A') from Register A. If the result is in range 00H-19H, the original character was an alphabetic letter.
4BDF
CP A,B B8
Compare Register A against Register B (1AH = 26). If A < 26 (Carry set), the character was alphabetic.
4BE0
LD A,D 7A
Load Register A with Register D (the original character code with possible CAPS LOCK flag).
4BE1
If the NO CARRY FLAG has been set (character is not alphabetic), JUMP to 4BE5H to skip the shift/CAPS LOCK XOR toggle.
4BE3
XOR A,00H EE 00
Self-Modifying Code
XOR Register A with 00H. The operand at 4BE4H is modified at runtime. When CAPS LOCK is toggled at 4BFBH, this operand is XORed with 20H (the lowercase bit), so it alternates between 00H and 20H. XORing with 20H toggles the case of alphabetic characters, implementing CAPS LOCK behavior.
4BE5
BIT 0,(HL) CB 46
Test bit 0 of the SHIFT key row at (HL = 3880H). On the Model I keyboard, bit 0 of row 7 (3880H) is the SHIFT key. If set, the SHIFT key is currently pressed.
4BE7
If the Z FLAG (Zero) has been set (SHIFT key is not pressed), JUMP to 4BFFH to continue without shift processing.

Shift Key Processing
The SHIFT key is pressed. Check for special cases: if the character is SPACE (20H) while SHIFT is held, toggle the CAPS LOCK state. Otherwise, check if the shifted character matches a repeat detection value.

4BE9
CP A,C B9
Compare Register A against Register C (9FH). This checks if the character code matches a specific boundary value for shift handling.
4BEA
If the Z FLAG (Zero) has been set (character matches 9FH), JUMP to 4BFEH to handle this special shifted character.
4BEC
CP A,20H FE 20
Compare Register A against 20H (ASCII SPACE). This checks if the user pressed SHIFT+SPACE, which is the CAPS LOCK toggle combination.
4BEE
If the NZ FLAG (Not Zero) has been set (character is not SPACE), JUMP to 4C03H to store the character and proceed with auto-repeat timing.

CAPS LOCK Toggle (Shift+Space)
The user pressed SHIFT+SPACE. Toggle the CAPS LOCK state by XORing the self-modifying operand at 4BE4H with 20H. This flips between case-toggling and non-toggling for alphabetic characters.

4BF0
LD HL,403AH 21 3A 40
Point Register Pair HL to 403AH, a keyboard state byte that tracks whether CAPS LOCK toggle is allowed.
4BF3
BIT 0,(HL) CB 46
Test bit 0 of the keyboard state at 403AH. This checks a guard flag to prevent rapid re-toggling of CAPS LOCK.
4BF5
If the Z FLAG (Zero) has been set (guard flag not set, toggle not allowed yet), JUMP to 4C03H to skip the toggle.
4BF7
LD HL,4BE4H 21 E4 4B
Point Register Pair HL to 4BE4H, the self-modifying operand byte of the XOR instruction at 4BE3H.
4BFA
XOR A,(HL) AE
XOR Register A with the current value at 4BE4H. Register A is 20H (SPACE character). XORing 20H with the current operand toggles its case-flip bit.
4BFB
LD (HL),A 77
Store the toggled value back into 4BE4H. This modifies the XOR instruction's operand so that future alphabetic characters will have their case toggled (or not, depending on the current state).
4BFC
JUMP to 4C02H to return A = 00H (the CAPS LOCK toggle itself does not produce a character).
4BFE
XOR A,L AD
XOR Register A with Register L. This processes the special shifted character by combining it with the low byte of the keyboard address.
4BFF
CP A,C B9
Compare Register A against Register C (9FH). This final check determines if the processed character matches the boundary value.
4C00
If the NZ FLAG (Not Zero) has been set (character does not match boundary), JUMP to 4C03H to store and return the character.
4C02
XOR A,A AF
Set Register A to ZERO. This is the "no key pressed" return value.
4C03
LD (IX+03H),A DD 77 03
Store the character code (or 00H for no key) into FCB/keyboard state offset 03H (IX+03H). This saves the current key for auto-repeat comparison on the next scan.
4C06
LD BC,0333H 01 33 03
Load Register Pair BC with 0333H. Register B = 03H (repeat delay divisor) and Register C = 33H (repeat rate constant). These control the auto-repeat timing for held keys.
4C09
GOSUB to the ROM delay routine at 0060H. This introduces a debounce/repeat delay using the value in Register Pair BC as the delay count.
4C0C
LD A,(4040H) 3A 40 40
Load Register A with the master tick counter from 4040H. This system timer value is incremented by the interrupt service routine and provides a time reference for key repeat timing.
4C0F
ADD A,1EH C6 1E
ADD 1EH (decimal 30) to the current tick counter. This computes a future tick value that serves as the auto-repeat timeout: the key must be held for approximately 30 ticks (about 0.5 seconds) before auto-repeat begins.
4C11
LD (IX+04H),A DD 77 04
Store the computed timeout value into IX+04H. This sets the deadline for auto-repeat activation. On subsequent scans, the current tick counter is compared against this value.
4C14
LD A,(IX+03H) DD 7E 03
Load Register A with the stored character code from IX+03H. This retrieves the character that was just processed.
4C17
OR A,A B7
OR Register A with itself to set flags based on its value. If A is zero (no key), Z is set.
4C18
RET Z C8
If the Z FLAG (Zero) has been set (no key was pressed), return to the caller with A = 00H.

Break Key Detection
A valid character was decoded. Now check for the BREAK key combination: the DOWN arrow key (bit 4 of keyboard row 3840H) held simultaneously with the SHIFT key (bit 0 of keyboard row 3880H).

4C19
LD HL,3840H 21 40 38
Point Register Pair HL to 3840H, keyboard row 6. This row contains ENTER, CLEAR, BREAK, and the arrow keys (UP, DOWN, LEFT, RIGHT, SPACE).
4C1C
BIT 4,(HL) CB 66
Test bit 4 of keyboard row 6 at 3840H. Bit 4 corresponds to the DOWN arrow key, which when combined with SHIFT forms the BREAK key.
4C1E
If the Z FLAG (Zero) has been set (DOWN arrow is not pressed), JUMP to 4C26H to return normally without BREAK.
4C20
LD L,80H 2E 80
Load Register L with 80H to point HL to 3880H, keyboard row 7 (the SHIFT key row).
4C22
BIT 0,(HL) CB 46
Test bit 0 of keyboard row 7 at 3880H. This checks if the SHIFT key is pressed simultaneously with the DOWN arrow, forming the BREAK combination.
4C24
SCF 37
Set the Carry Flag. This prepares the BREAK indicator: Carry set means BREAK was detected.
4C25
RET NZ C0
If the NZ FLAG (Not Zero) has been set (SHIFT is pressed = BREAK detected), return to the caller with the Carry flag set to signal BREAK. Register A still holds the character code.
4C26
OR A,A B7
OR Register A with itself. This clears the Carry flag (no BREAK) while keeping NZ set (valid character in A).
4C27
RET C9
Return to the caller with the ASCII character in Register A, NZ flag set (valid key), and Carry flag clear (no BREAK).

4C28H - RST 28H Overlay Loader

This is the RST 28H handler, reached via the jump table at 400CH (JP 4C28H). The overlay loader examines Register A to determine which overlay to load. If bit 7 is clear, the call is treated as a return-from-overlay (restoring the previous return address). If bit 7 is set, the overlay identified by bits 0-3 is loaded from disk into the overlay area at 4E00H, and control is transferred to the overlay's entry point based on the function number in bits 4-6. Uses 4310H to save/restore the caller's return address and 430EH to track which overlay is currently loaded.

4C28
LD HL,(4310H) 2A 10 43
Load Register Pair HL with the saved overlay return address from 4310H-4311H. When an overlay was previously loaded, the caller's return address was stored here.
4C2B
EX (SP),HL E3
Exchange the top of stack with HL. The current return address (the address after the RST 28H instruction) is swapped with the saved return address. HL now holds the address after the RST 28H, and the stack holds the previously saved return address.
4C2C
DEC A 3D
DECrement Register A by 1. This is part of a test sequence: DEC A / NOP / INC A / OR A / RET. The DEC sets the Sign flag if A was 00H (producing FFH). Combined with the INC that follows, this effectively tests whether bit 7 of the original A was set.
4C2D
NOP 00
Self-Modifying Code
NOP placeholder. This byte at 4C2DH is patched at runtime (at 4FBBH during CONFIG/SYS processing) with C8H (RET Z) to alter the overlay loader's behavior when a specific system configuration is active. When patched to RET Z, it causes the loader to return immediately if A was 01H (DEC made it 00H), bypassing the overlay load.
4C2E
INC A 3C
INCrement Register A by 1, restoring it to its original value after the DEC at 4C2CH.
4C2F
OR A,A B7
OR Register A with itself to set flags. This tests bit 7: if set, the Sign flag is set (negative), indicating this is an overlay load request rather than a return-from-overlay.
4C30
RET C9
Return. If bit 7 was clear (Sign flag clear, P condition), this returns to the saved return address on the stack, effectively returning from the overlay back to the original caller. If bit 7 was set, the RET is skipped by the JP P below because the return address was already swapped.

Overlay Dispatch Entry
The code at 4C31H is reached when the RST 28H vector jumps here. The flow is: RST 28H at 0028H jumps to 400CH, which is JP 4C28H. But actually, the RET at 4C30H and JP P at 4C33H work together: after restoring flags, if bit 7 was set (this IS an overlay call), the code falls through to 4C31H.

4C31
EX (SP),HL E3
Exchange the top of stack with HL again. This restores the stack to its original state and puts the caller's return address back into HL for saving.
4C32
OR A,A B7
OR Register A with itself to test bit 7 again. If bit 7 is set (this is an overlay load request), the Sign flag is set.
4C33
If the P flag (Positive/bit 7 clear), JUMP back to 4C28H. This handles the return-from-overlay case: bit 7 is clear, so this is not a new overlay call but a return to the previous context.

Overlay Load Request
Bit 7 of Register A is set, confirming this is a request to load and execute an overlay. Save the caller's return address, then proceed to load the overlay from disk.

4C36
POP HL E1
POP the return address from the stack into HL. This is the address after the RST 28H / LD A,xxH sequence in the caller.
4C37
GOSUB to the overlay loader core at 4C53H. This routine saves state, determines which overlay file to load, reads it from disk into 4E00H, and computes the entry point address. Returns with HL pointing to the overlay's entry point.
4C3A
LD A,(4315H) 3A 15 43
Load Register A with the BREAK key handler state from 4315H. This byte contains the first byte of a JP instruction (C3H) when the BREAK handler is enabled, or C9H (RET) when disabled.
4C3D
LD (4C4DH),A 32 4D 4C
Self-Modifying Code
Store the current BREAK handler state into 4C4DH. This saves the BREAK state so it can be restored after the overlay executes. The byte at 4C4DH is the operand of the LD A,00H instruction at 4C4CH.
4C40
LD A,0C9H 3E C9
Load Register A with C9H (the opcode for RET). This will temporarily disable the BREAK key handler during overlay execution.
4C42
LD (4315H),A 32 15 43
Store C9H into 4315H, disabling the BREAK key handler. This prevents BREAK from interrupting overlay execution, which could leave the system in an inconsistent state.
4C45
LD A,(430EH) 3A 0E 43
Load Register A with the current overlay ID from 430EH. This is the SVC code that was just loaded by the overlay loader at 4C53H.
4C48
CALL 0000H CD 00 00
Self-Modifying Code
CALL 0000H. The target address at 4C49H-4C4AH was patched by the overlay loader at 4CA4H (LD (4C49H),HL) to hold the computed overlay entry point address. At runtime this becomes CALL nnnnH where nnnn is the overlay's entry point (e.g., 4E00H + function offset). Register A holds the overlay ID for the overlay to use.
4C4B
PUSH AF F5
Save the return flags and error code from the overlay call onto the stack.
4C4C
LD A,00H 3E 00
Self-Modifying Code
Load Register A with 00H. The operand at 4C4DH was patched at 4C3DH to hold the original BREAK handler state (C3H if enabled, C9H if disabled). At runtime this restores the saved BREAK state.
4C4E
LD (4315H),A 32 15 43
Store the restored BREAK handler state back into 4315H. This re-enables (or keeps disabled) the BREAK key handler now that overlay execution is complete.
4C51
POP AF F1
Restore the overlay's return flags and error code from the stack.
4C52
RET C9
Return to the original caller (whose address was saved in 4310H and restored to the stack by the EX (SP),HL mechanism). Register A holds the overlay's return code and flags reflect success/failure.

4C53H - Overlay Loader Core

The core overlay loading routine. Saves the caller's context, determines which overlay file to load based on the SVC code in Register A, checks if the requested overlay is already cached in memory, and if not, reads it from disk into the overlay area at 4E00H. Computes the entry point address from the function number encoded in the SVC code. Called from the RST 28H handler at 4C37H and also directly from the extent chain continuation at 4AA8H.

4C53
PUSH HL E5
Save Register Pair HL (the caller's return address) onto the stack.
4C54
LD H,A 67
Load Register H with Register A (the SVC code). Save the full SVC code in H for later use.
4C55
LD A,B 78
Load Register A with Register B. Register B holds context information from the caller that needs to be preserved across the overlay load.
4C56
LD (4C99H),A 32 99 4C
Self-Modifying Code
Store Register B's value into 4C99H. This patches the LD B,00H instruction at 4C98H to preserve and restore Register B after the overlay is loaded.
4C59
LD A,H 7C
Load Register A with Register H (the SVC code) back into A for processing.
4C5A
CP A,88H FE 88
Compare Register A against 88H. SVC code 88H is a special case that forces a fresh load from disk, bypassing the overlay cache check. This is used when the overlay area must be reloaded regardless of what is currently in memory.
4C5C
If the Z FLAG (Zero) has been set (SVC code is 88H, forced reload), JUMP to 4C6DH to skip the cache check and load directly from disk.
4C5E
LD A,(430EH) 3A 0E 43
Load Register A with the currently loaded overlay ID from 430EH. This is the SVC code of the overlay that is currently in the overlay area at 4E00H.
4C61
XOR A,H AC
XOR the current overlay ID with the requested SVC code (in H). If the lower 4 bits (overlay file number) match, the XOR will produce 00H in bits 0-3.
4C62
AND A,0FH E6 0F
Mask with 0FH to test only the lower 4 bits (overlay file number). If the result is zero, the same overlay file is already loaded.
4C64
LD A,H 7C
Load Register A with Register H (the requested SVC code), restoring the full code for further processing.
4C65
LD (430EH),A 32 0E 43
Store the requested SVC code into 430EH, updating the currently-loaded overlay ID to the new value.
4C68
LD HL,4E00H 21 00 4E
Load Register Pair HL with 4E00H, the base address of the overlay area in memory. This is the default entry point for overlays.
4C6B
If the Z FLAG (Zero) has been set (the requested overlay is already loaded in memory), JUMP to 4CA4H to skip the disk load and proceed directly to computing the entry point. The overlay code is already at 4E00H.

Overlay Disk Load
The requested overlay is not in memory. Load it from disk. The SVC code encodes the overlay file number in bits 0-3. Files 0-7 are directory entries directly; files 8-15 have 18H added to get the directory entry number.

4C6D
PUSH DE D5
Save Register Pair DE onto the stack (preserved across the disk load).
4C6E
PUSH BC C5
Save Register Pair BC onto the stack.
4C6F
AND A,0FH E6 0F
Mask Register A with 0FH to extract the overlay file number (bits 0-3) from the SVC code. This produces a value 0-15.
4C71
CP A,08H FE 08
Compare Register A against 08H. File numbers 0-7 are used directly as directory entry numbers. File numbers 8-15 require an offset.
4C73
If the CARRY FLAG has been set (file number < 8), JUMP to 4C77H to use the file number directly as the directory entry number.
4C75
ADD A,18H C6 18
ADD 18H (decimal 24) to Register A. For file numbers 8-15, this produces directory entry numbers 20H-27H (32-39 decimal), mapping to a different region of the directory.
4C77
LD (44A7H),A 32 A7 44
Store the computed directory entry number into 44A7H (overlay directory entry info). This tells the disk read routine which directory entry to look up.
4C7A
XOR A,A AF
Set Register A to ZERO and clear all flags.
4C7B
LD (44A1H),A 32 A1 44
Store 00H into 44A1H (overlay execution state flag). This clears the flag to indicate the overlay is being freshly loaded.
4C7E
SBC HL,HL ED 62
SUBtract HL from itself with borrow, producing HL = 0000H (or FFFFH if Carry was set, but Carry was cleared by XOR A). This zeros HL.
4C80
LD (44AAH),HL 22 AA 44
Store 0000H into 44AAH-44ABH (overlay cache markers). Setting both bytes to 00H invalidates the overlay cache.
4C83
LD C,A 4F
Load Register C with Register A (00H). Register C = drive 0, since overlays are loaded from the system disk.
4C84
LD A,(44A7H) 3A A7 44
Load Register A with the directory entry number from 44A7H.
4C87
LD B,A 47
Load Register B with the directory entry number for the disk read call.
4C88
GOSUB to the directory sector read routine at 4B10H. This reads the directory sector containing the overlay's directory entry into the directory buffer at 4200H. Register B holds the directory entry number, Register C holds the drive number (0).
4C8B
If the NZ FLAG (Not Zero) has been set (directory read failed), JUMP to 4CA9H to display a system error message and halt.
4C8D
LD A,L 7D
Load Register A with Register L. After the directory read, HL points to the directory entry in the buffer. L holds the offset within the 256-byte sector where the entry begins.
4C8E
ADD A,16H C6 16
ADD 16H (decimal 22) to Register A. This advances past the filename, extension, and other header fields to reach the load address field at offset 16H-17H within the directory entry.
4C90
LD L,A 6F
Load Register L with the computed offset, pointing HL to the load address field in the directory entry.
4C91
LD A,(HL) 7E
Load Register A with the low byte of the overlay's load address from the directory entry.
4C92
INC L 2C
INCrement L to point to the high byte of the load address.
4C93
LD H,(HL) 66
Load Register H with the high byte of the overlay's load address.
4C94
LD L,A 6F
Load Register L with the low byte of the load address (saved in A). Register Pair HL now holds the overlay's load address from the directory entry.
4C95
LD (44AEH),HL 22 AE 44
Store the overlay's load address into 44AEH-44AFH (overlay load address). This records where the overlay should be loaded in memory.
4C98
LD B,00H 06 00
Self-Modifying Code
Load Register B with 00H. The operand at 4C99H was patched at 4C56H to hold the caller's original Register B value. At runtime this becomes LD B,nn, restoring the saved value.
4C9A
LD DE,44A0H 11 A0 44
Load Register Pair DE with 44A0H, pointing to the overlay parameter block. This block holds context information passed to the overlay file loader.
4C9D
GOSUB to the program load core at 4CF8H. This reads the overlay file from disk sector by sector and loads it into memory at the address specified by the directory entry (typically 4E00H). Returns with Z set on success, NZ on error.
4CA0
If the NZ FLAG (Not Zero) has been set (overlay load failed), JUMP to 4CA9H to display a system error and halt.
4CA2
POP BC C1
Restore Register Pair BC from the stack.
4CA3
POP DE D1
Restore Register Pair DE from the stack.
4CA4
LD (4C49H),HL 22 49 4C
Self-Modifying Code
Store Register Pair HL (the overlay entry point address, typically 4E00H) into the operand bytes at 4C49H-4C4AH. This patches the CALL 0000H instruction at 4C48H to become CALL nnnnH, where nnnn is the overlay's entry point.
4CA7
POP HL E1
Restore the caller's return address from the stack (saved at 4C53H).
4CA8
RET C9
Return to the RST 28H handler at 4C37H (or direct caller). The overlay is now loaded at 4E00H and the CALL instruction at 4C48H has been patched with the correct entry point.

4CA9H - Overlay Load Error Display

Displays a "SYSTEM ERROR XX" message and halts when an overlay cannot be loaded from disk. This is a fatal error - the system cannot continue without the requested overlay. The error code from the failed disk read is converted to a 2-digit hex value and inserted into the error message string.

4CA9
AND A,3FH E6 3F
Mask the error code with 3FH (binary 00111111) to clear the upper two bits, producing a clean error number for display.
4CAB
LD HL,4CC8H 21 C8 4C
Point Register Pair HL to 4CC8H, which is the position within the error message string where the 2-digit hex error code ("XX") should be written.
4CAE
GOSUB to the hex display routine at 4DECH. This converts the error code in Register A into two ASCII hex digits and writes them into the message buffer at (HL). The string at 4CC8H will have the "XX" replaced with the actual error code.
4CB1
LD HL,4CBAH 21 BA 4C
Point Register Pair HL to 4CBAH, the start of the error message string (0AH + "SYSTEM ERROR XX" + 0DH).
4CB4
GOSUB to the display message routine at 447BH. This outputs the error message string to the screen, character by character until the 03H terminator.
4CB7
JUMP to the error exit at 4030H. This is a dead-end routine that halts the system or returns to the DOS error handler. After a fatal overlay load failure, the system cannot continue normally.

4CBAH - "SYSTEM ERROR XX" Message Data

ASCII string data for the system error message. The string begins with 0AH (line feed), followed by the text "SYSTEM ERROR XX" (where XX will be overwritten with the hex error code), and ends with 0DH (carriage return). The 03H terminator marks the end of the string for the display routine.

4CBA-4CCA
DEFM 0AH,"SYSTEM ERROR XX",0DH,03H 0A 53 59 53 54 45 4D 20 45 52 52 4F 52 20 58 58 0D
Message string: line feed (0AH) + "SYSTEM ERROR XX" + carriage return (0DH). The bytes at 4CC8H-4CC9H (the "XX" characters, initially 58H 58H) are overwritten by the hex display routine at 4CAEH with the actual 2-digit error code before display. Terminated by 03H at 4CCAH.

4CCBH - Load and Execute Program

This routine loads a program file from disk and optionally executes it. It sets a flag in the system mode register (430FH) to indicate a program load is in progress, then calls the file open and load routine. After loading, it checks the system mode flags to determine whether to return to the caller, enter DEBUG, or transfer control to the loaded program. Called from the jump table entries at 4430H (JP 4CE4H for load) and 4430H (JP 4CCBH for load-and-execute).

4CCB
PUSH HL E5
Save Register Pair HL onto the stack. HL points to the filename or filespec to load.
4CCC
LD HL,430FH 21 0F 43
Point Register Pair HL to 430FH, the system mode flags byte. Bit 2 of this byte indicates "program load in progress".
4CCF
SET 2,(HL) CB D6
Set bit 2 of the system mode flags at 430FH. This marks that a program load operation is active, which affects how errors are handled and how the system behaves after loading.
4CD1
GOSUB to the program loader entry at 4CE4H (via JP at 4430H). This opens the file and loads the program from disk into memory. Returns with Z flag on success, NZ on error.
4CD4
If the NZ FLAG (Not Zero) has been set (program load failed), JUMP to 4409H (the DOS error exit). This reports the error and returns to DOS READY.
4CD7
EX (SP),HL E3
Exchange the top of stack with HL. The stack held the original HL (filespec pointer), and HL now holds the program's entry point address (returned by the loader). The filespec pointer is discarded.
4CD8
LD A,(430FH) 3A 0F 43
Load Register A with the system mode flags from 430FH.
4CDB
BIT 1,A CB 4F
Test bit 1 of the system mode flags. Bit 1 indicates "return to caller after load" mode. If set, the loaded program should not be executed immediately.
4CDD
RET NZ C0
If the NZ FLAG (Not Zero) has been set (bit 1 set = return-to-caller mode), return to the caller. HL on the stack holds the program's entry point, which the caller can use to execute it later.
4CDE
BIT 7,A CB 7F
Test bit 7 of the system mode flags. Bit 7 indicates DEBUG mode is active.
4CE0
If the NZ FLAG (Not Zero) has been set (DEBUG mode active), JUMP to 400FH (the RST 30H / DEBUG entry point). This enters the debugger with the loaded program ready for examination.
4CE3
RET C9
Return to the caller. The program's entry point address is on the stack, so this RET transfers control to the loaded program's entry point, beginning execution.

4CE4H - Program Loader Entry

Opens a file by name and loads it into memory. This is the common entry point for both "load only" (4430H) and "load and execute" (4CCBH) operations. Opens the file using the DOS OPEN existing file call (4424H), then invokes the program load core to read the file contents into memory.

4CE4
LD B,00H 06 00
Load Register B with 00H. This specifies drive 0 (system disk) for the file open operation, or a default file mode.
4CE6
LD HL,4200H 21 00 42
Point Register Pair HL to 4200H, the directory/FCB buffer area. This buffer will receive the FCB data when the file is opened.
4CE9
GOSUB to the "open existing file" routine at 4424H. This searches the directory for the file specified by the filespec, opens it, and populates the FCB at 4200H. Returns with Z flag on success, NZ on failure (file not found or other error).
4CEC
If the Z FLAG (Zero) has been set (file opened successfully), GOSUB to the program load core at 4CF8H to read the file contents from disk into memory.
4CEF
RET Z C8
If the Z FLAG (Zero) has been set (load succeeded), return to the caller with Z set and HL holding the program's entry point address.
4CF0
SET 6,A CB F7
Set bit 6 of Register A (the error code). This modifies the error code to indicate the error occurred during a program load operation, distinguishing it from other error types.
4CF2
CP A,58H FE 58
Compare Register A against 58H. This checks for a specific error code (58H = file not found, with bit 6 set).
4CF4
RET NZ C0
If the NZ FLAG (Not Zero) has been set (error is not file-not-found), return with the modified error code in Register A.
4CF5
ADD A,07H C6 07
ADD 07H to Register A (58H + 07H = 5FH). This converts the error code to 5FH, a more specific "file not found during program load" error code.
4CF7
RET C9
Return to the caller with Register A = 5FH (file not found during program load) and NZ flag set.

4CF8H - Program Load Core

The core program loading routine. Reads a program file sector by sector from disk, parsing the standard TRS-80 load module format. Load modules consist of typed blocks: Type 01H (data block - load bytes into memory), Type 02H (entry point - specifies where execution begins), Type 04H (end of file marker), Type 08H (extended header), and Type 20H+ (treated as invalid module type). Returns with Z set and HL = entry point on success, or NZ with error code on failure.

4CF8
LD A,B 78
Load Register A with Register B (context/mode byte from the caller).
4CF9
LD (4D67H),A 32 67 4D
Self-Modifying Code
Store Register A into the operand at 4D67H. This patches the CP A,00H instruction at 4D66H to compare against the caller's mode byte. This determines how certain block types are handled during loading.
4CFC
LD BC,42FFH 01 FF 42
Load Register Pair BC with 42FFH. Register B = 42H (the high byte of the sector buffer page). Register C = FFH, used as a sector byte counter initialized to the end of a sector, which forces the first call to 4D8EH to read a fresh sector.
4CFF
GOSUB to the "get next byte from program file" routine at 4D8EH. This returns the next byte from the file in Register A, automatically reading new sectors as needed. The first byte should be a block type indicator.
4D02
CP A,01H FE 01
Compare Register A against 01H. Type 01H is a data block: load bytes into memory at a specified address.
4D04
If the Z FLAG (Zero) has been set (block type 01H - data block), JUMP to 4D36H to process the data block.
4D06
CP A,02H FE 02
Compare Register A against 02H. Type 02H is an entry point record: specifies the execution start address.
4D08
If the Z FLAG (Zero) has been set (block type 02H - entry point), JUMP to 4D29H to process the entry point record.
4D0A
CP A,04H FE 04
Compare Register A against 04H. Type 04H is an end-of-file marker.
4D0C
If the Z FLAG (Zero) has been set (block type 04H - EOF marker), JUMP to 4D5CH to handle end of file (POP HL and RET).
4D0E
CP A,08H FE 08
Compare Register A against 08H. Type 08H is an extended header block.
4D10
If the Z FLAG (Zero) has been set (block type 08H - extended header), JUMP to 4D5EH to process the extended header.
4D12
CP A,0AH FE 0A
Compare Register A against 0AH. Type 0AH is another valid block type (patch/overlay block).
4D14
If the Z FLAG (Zero) has been set (block type 0AH), JUMP to 4D1AH to return with error 22H (invalid module type for this context).
4D16
CP A,20H FE 20
Compare Register A against 20H. Any block type >= 20H is unrecognized.
4D18
If the CARRY FLAG has been set (block type < 20H but not one of the recognized types), JUMP to 4D1EH to skip the unknown block by reading and discarding its bytes.
4D1A
LD A,22H 3E 22
Load Register A with 22H (error code 34 decimal: "invalid module type"). This error is returned for unrecognized or unsupported block types.
4D1C
OR A,A B7
OR Register A with itself to set the NZ flag, indicating an error condition.
4D1D
RET C9
Return to the caller with Register A = 22H (invalid module type) and NZ flag set.

Skip Unknown Block
For unrecognized block types < 20H (but not the specific types handled above), read and discard the block's length byte, then skip that many data bytes to advance to the next block.

4D1E
GOSUB to get the next byte from the file. This reads the block length byte.
4D21
LD B,A 47
Load Register B with the block length byte. This is the number of data bytes to skip.
4D22
GOSUB to get the next byte from the file, discarding it.
4D25
DECrement B and loop back to 4D22H if not zero. This skips all data bytes in the unknown block.
4D27
LOOP BACK to 4CFFH to read the next block type byte and continue processing.

4D29H - Process Entry Point Record (Type 02H)

Processes a Type 02H entry point record from the load module. Reads the 2-byte length field (expected to be 02H), then reads the 2-byte entry point address. Returns with Z set and HL = entry point address.

4D29
GOSUB to get the next byte: the length byte of the entry point record (expected to be 02H since the entry point is 2 bytes).
4D2C
GOSUB to get the next byte: the low byte of the entry point address.
4D2F
LD L,A 6F
Load Register L with the low byte of the entry point address.
4D30
GOSUB to get the next byte: the high byte of the entry point address.
4D33
LD H,A 67
Load Register H with the high byte. Register Pair HL now holds the program's entry point address.
4D34
XOR A,A AF
Set Register A to ZERO and set the Z flag, indicating success.
4D35
RET C9
Return to the caller with Z set (success) and HL = entry point address.

4D36H - Process Data Block (Type 01H)

Processes a Type 01H data block from the load module. Reads the block length, then the 2-byte load address, then copies the specified number of data bytes into memory at the load address. Performs a write-verify check on each byte to detect non-writable memory (ROM areas or missing RAM). Returns to the main block loop at 4CFFH on success, or returns with an error code if the write verify fails.

4D36
GOSUB to get the next byte: the block length (number of data bytes + 2 for the address).
4D39
LD B,A 47
Load Register B with the block length. This counts down as bytes are processed.
4D3A
GOSUB to get the next byte: the low byte of the load address.
4D3D
LD L,A 6F
Load Register L with the low byte of the load address.
4D3E
DEC B 05
DECrement B (block length counter) by 1 to account for the low address byte consumed.
4D3F
GOSUB to get the next byte: the high byte of the load address.
4D42
LD H,A 67
Load Register H with the high byte. Register Pair HL now holds the target address where data bytes will be written.
4D43
DEC B 05
DECrement B by 1 to account for the high address byte consumed. B now holds the count of actual data bytes to load.

Data Byte Load Loop
Read each data byte from the file and write it to the target memory address. After writing, verify the byte was stored correctly by reading it back and comparing. This detects attempts to write to ROM or non-existent RAM.

4D44
GOSUB to get the next data byte from the file.
4D47
LD (HL),A 77
Store the data byte into the target memory address pointed to by HL.
4D48
CP A,(HL) BE
Compare Register A against the byte just written at (HL). If the write succeeded, the values will match (Z set). If the address is ROM or non-existent, the read-back will differ (NZ set).
4D49
If the NZ FLAG (Not Zero) has been set (write verify failed - byte did not store correctly), JUMP to 4D50H to attempt a secondary verification.
4D4B
INC HL 23
INCrement HL to point to the next target memory address.
4D4C
DECrement B and loop back to 4D44H if not zero. Continues until all data bytes in this block have been written.
4D4E
LOOP BACK to 4CFFH to read the next block type and continue processing the load module.

Write Verify Failure Handler
The byte written to memory did not read back correctly. Try writing the complement to determine if this is a ROM address (completely non-writable) or a memory error (partially writable).

4D50
LD B,(HL) 46
Load Register B with the value currently at the target address (what the hardware returned instead of what we wrote).
4D51
CPL 2F
Complement Register A (invert all bits). This produces the bitwise NOT of the original data byte.
4D52
LD (HL),A 77
Write the complemented byte to the target address.
4D53
LD A,(HL) 7E
Read the byte back from the target address.
4D54
CP A,B B8
Compare the read-back value against Register B (the value that was at the address before the complement write). If they are equal, the address is completely non-writable (ROM).
4D55
LD A,63H 3E 63
Load Register A with 63H (error code 99 decimal: "memory write error"). This is returned when the target address is non-writable (ROM or missing RAM).
4D57
RET NZ C0
If the NZ FLAG (Not Zero) has been set (the complement also failed to write, confirming non-writable memory), return with error 63H.
4D58
LD A,64H 3E 64
Load Register A with 64H (error code 100 decimal: "memory write verify error"). This is returned when the address is writable but the original data byte did not store correctly.
4D5A
OR A,A B7
OR Register A with itself to set the NZ flag.
4D5B
RET C9
Return to the caller with Register A = 64H (memory write verify error) and NZ flag set.

4D5CH - End of File Block (Type 04H)

Handles the Type 04H end-of-file marker. Pops the return address and returns, ending the load process.

4D5C
POP HL E1
POP the return address from the stack into HL. This discards the return to the block processing loop and returns to the original caller of the load core.
4D5D
RET C9
Return to the caller (4CECH or 4C9DH) with whatever flags and entry point address were last set.

4D5EH - Process Extended Header Block (Type 08H)

Processes a Type 08H extended header block. Reads the block length and a mode byte. If the mode byte matches the caller's expected mode (stored at 4D67H via self-modifying code), the block data is skipped. Otherwise, the block contains file positioning information: a sector count and a record position, which are used to reposition the file before continuing the load.

4D5E
GOSUB to get the next byte: the block length.
4D61
LD B,A 47
Load Register B with the block length.
4D62
GOSUB to get the next byte: the mode/flag byte.
4D65
DEC B 05
DECrement B to account for the mode byte consumed.
4D66
CP A,00H FE 00
Self-Modifying Code
Compare Register A against 00H. The operand at 4D67H was patched at 4CF9H to hold the caller's mode byte. At runtime this becomes CP A,nn, checking whether the block's mode matches the expected mode.
4D68
If the NZ FLAG (Not Zero) has been set (mode does not match), JUMP to 4D22H to skip the remaining bytes in this block and continue to the next block.

File Repositioning
The mode matched. This block contains a sector count and file position that need to be applied. Read the sector count (2 bytes), the record position (2 bytes), and a positioning mode byte, then use the DOS file positioning routine to reposition the file before continuing the load.

4D6A
GOSUB to get the next byte: low byte of the sector count.
4D6D
LD L,A 6F
Load Register L with the low byte of the sector count.
4D6E
GOSUB to get the next byte: high byte of the sector count.
4D71
LD H,A 67
Load Register H with the high byte. HL = sector count for positioning.
4D72
PUSH HL E5
Save the sector count onto the stack.
4D73
GOSUB to get the next byte: low byte of the record position.
4D76
LD L,A 6F
Load Register L with the low byte of the record position.
4D77
GOSUB to get the next byte: high byte of the record position.
4D7A
LD H,A 67
Load Register H with the high byte. HL = record position.
4D7B
GOSUB to get the next byte: the positioning mode byte.
4D7E
LD C,A 4F
Load Register C with the positioning mode byte.
4D7F
PUSH BC C5
Save Register Pair BC onto the stack.
4D80
LD B,H 44
Load Register B with Register H (high byte of record position).
4D81
LD C,L 4D
Load Register C with Register L (low byte of record position). BC = record position for the DOS call.
4D82
GOSUB to the DOS "position to record" routine at 4442H. This repositions the file to the specified sector/record position (BC = record number). Returns Z on success.
4D85
POP BC C1
Restore Register Pair BC from the stack.
4D86
If the NZ FLAG (Not Zero) has been set (positioning failed), JUMP to 4DA2H to return with the error code (with bit 6 set).
4D88
GOSUB to the sector read helper at 4D91H. This reads the next sector from the repositioned file into the sector buffer.
4D8B
JUMP back to 4D02H to continue processing blocks from the new file position.

4D8EH - Get Next Byte from Program File

Returns the next byte from the program file being loaded. Uses Register C as a sector byte counter (0-255). When the counter reaches 00H (all bytes in the current sector consumed), a new sector is read from disk. The sector buffer is at address B*256 (e.g., 4200H when B=42H). Returns the byte in Register A.

4D8E
INC C 0C
INCrement Register C (sector byte counter) by 1. If C was FFH, it wraps to 00H (Z set), indicating all 256 bytes in the current sector have been consumed and a new sector must be read.
4D8F
If the NZ FLAG (Not Zero) has been set (sector byte counter has not wrapped, bytes remain in the current sector), JUMP to 4DA5H to read the next byte from the buffer.

4D91H - Sector Read Helper

Reads the next sector from the file into the sector buffer. Preserves all registers except AF across the read operation by saving/restoring IX, HL, DE, and BC. Uses the DOS read record routine at 4949H to perform the actual disk read.

4D91
PUSH DE D5
Save Register Pair DE onto the stack.
4D92
EX (SP),IX DD E3
Exchange the top of stack (DE value) with IX. This saves IX onto the stack and loads IX with the saved DE value. This is a technique to save IX without using 4 bytes for PUSH IX / ... / POP IX.
4D94
PUSH HL E5
Save Register Pair HL onto the stack.
4D95
PUSH DE D5
Save Register Pair DE (which now holds the original IX value) onto the stack.
4D96
PUSH BC C5
Save Register Pair BC onto the stack.
4D97
GOSUB to the DOS read record routine at 4949H. This reads the next sector from the file into the sector buffer. The file position is automatically advanced.
4D9A
POP BC C1
Restore Register Pair BC from the stack.
4D9B
POP DE D1
Restore Register Pair DE (original IX value) from the stack.
4D9C
POP HL E1
Restore Register Pair HL from the stack.
4D9D
POP IX DD E1
Restore IX from the stack (the original DE value was there, but the EX (SP),IX at 4D92H placed IX's original value there).
4D9F
If the Z FLAG (Zero) has been set (sector read succeeded), JUMP to 4DA5H to retrieve the first byte from the newly loaded sector.

Read Error During Program Load
The sector read failed. Pop the return address from the program load core's call chain and return with the error code.

4DA1
POP BC C1
POP and discard the return address from the stack. This unwinds past the CALL 4D8EH that invoked this routine, returning directly to the load core's caller.
4DA2
OR A,40H F6 40
OR Register A (error code) with 40H. This sets bit 6 of the error code, marking it as an error that occurred during program loading (to distinguish from other error sources).
4DA4
RET C9
Return to the caller with the modified error code in Register A and NZ flag set.

Read Byte from Sector Buffer
The sector buffer contains valid data. Retrieve the byte at the current position (indexed by Register C) and return it.

4DA5
PUSH BC C5
Save Register Pair BC onto the stack (preserving the sector byte counter in C and the buffer page in B).
4DA6
LD B,42H 06 42
Load Register B with 42H. This is the high byte of the sector buffer address (4200H). Combined with C (the byte offset), BC forms the full buffer address.
4DA8
LD A,(BC) 0A
Load Register A with the byte at address BC (42xxH where xx = Register C). This retrieves the current byte from the sector buffer.
4DA9
POP BC C1
Restore Register Pair BC from the stack.
4DAA
RET C9
Return to the caller with the file byte in Register A.

4DABH - Timer Interrupt Dispatch Entry

This is the entry point called from the timer interrupt callback list. The 2-byte entry at 4DABH-4DACH is a pointer (AEH, 4DH = 4DAEH) to the actual clock display routine. This pointer is registered in the timer callback slot table during initialization.

4DAB-4DAC
DEFW 4DAEH AE 4D
Timer callback pointer. This 2-byte address (4DAEH) is registered in the timer interrupt dispatch table at 41E5H during boot initialization. When the timer interrupt fires, the dispatcher calls through this pointer to the clock tick handler at 4DAEH.

4DAEH - Clock Tick Handler

Called from the timer interrupt at regular intervals. Decrements a prescaler counter at IX+02H. When the prescaler reaches zero, it reloads the count (05H = divide by 5) and calls the clock display routine to update the time shown on screen. IX points to the timer callback data block for this handler.

4DAE
DEC (IX+02H) DD 35 02
DECrement the prescaler counter at IX+02H by 1. This divides the interrupt rate by the prescaler value to produce a slower clock update rate.
4DB1
RET NZ C0
If the NZ FLAG (Not Zero) has been set (prescaler has not reached zero), return without updating the display. The clock display is only refreshed every Nth interrupt.
4DB2
LD (IX+02H),05H DD 36 02 05
Reload the prescaler counter at IX+02H with 05H (decimal 5). The clock display will be updated every 5th timer interrupt tick.

4DB6H - Convert Time to HH:MM:SS Display

Converts the binary time values stored at 4041H (seconds), 4042H (minutes), and 4043H (hours) into ASCII digit pairs and writes them directly to video RAM starting at 3C35H. The separator character (stored in Register C) is written between each pair. Uses a repeated divide-by-10 algorithm to extract tens and units digits. This routine is also the target of the jump table entry at 446DH.

4DB6
LD HL,3C35H 21 35 3C
Point Register Pair HL to 3C35H, the video RAM position where the clock display begins. On the Model I, video RAM runs from 3C00H-3FFFH (64 columns x 16 rows). Position 3C35H is column 53 of the top row.
4DB9
LD DE,4043H 11 43 40
Point Register Pair DE to 4043H, the hours counter. The time values are stored as hours (4043H), minutes (4042H), seconds (4041H), accessed in descending address order by DECrementing DE.
4DBC
LD C,3AH 0E 3A
Load Register C with 3AH (ASCII :). This is the separator character displayed between hours, minutes, and seconds (HH:MM:SS).
4DBE
LD B,03H 06 03
Load Register B with 03H (decimal 3). This is the loop counter: process hours, minutes, and seconds (3 pairs of digits).

Time Digit Conversion Loop
For each time component (hours, minutes, seconds), read the binary value from the time counter, convert to two ASCII digits using repeated subtraction of 10, and write both digits to video RAM. Write the separator character after each pair except the last.

4DC0
LD A,(DE) 1A
Load Register A with the current time component value from the address pointed to by DE (hours first at 4043H, then minutes at 4042H, then seconds at 4041H).
4DC1
DEC DE 1B
DECrement DE to point to the next time component (from hours to minutes, from minutes to seconds).

Tens Digit Extraction
Extract the tens digit by repeatedly subtracting 10 and counting. The video RAM byte at (HL) is used as the counter, starting at 2FH (ASCII /, which is one less than 0).

4DC2
LD (HL),2FH 36 2F
Store 2FH (ASCII /) into video RAM at (HL). This initializes the tens digit position to one less than 0. Each successful subtraction of 10 will INCrement this to the next digit character.
4DC4
INC (HL) 34
INCrement the byte at (HL). This advances the tens digit character from / to 0, 0 to 1, etc.
4DC5
SUB A,0AH D6 0A
SUBtract 0AH (decimal 10) from Register A. If A >= 10, Carry is clear and we loop again. If A < 10, Carry is set and the remaining value is the units digit.
4DC7
If the NO CARRY FLAG has been set (A was >= 10, subtraction succeeded), LOOP BACK to 4DC4H to increment the tens digit and subtract again.
4DC9
ADD A,3AH C6 3A
ADD 3AH to Register A. After the loop, A holds the units value minus 10 (a negative number from -10 to -1). Adding 3AH (= 30H + 0AH) converts this to the correct ASCII digit: the +0AH compensates for the extra SUB, and +30H converts to ASCII.
4DCB
INC HL 23
INCrement HL to point to the units digit position in video RAM.
4DCC
LD (HL),A 77
Store the ASCII units digit into video RAM at (HL).
4DCD
INC HL 23
INCrement HL to point to the separator position in video RAM.
4DCE
DEC B 05
DECrement Register B (loop counter) by 1.
4DCF
RET Z C8
If the Z FLAG (Zero) has been set (all 3 time components processed), return. The clock display is now updated in video RAM.
4DD0
LD (HL),C 71
Store the separator character (Register C = 3AH = :) into video RAM at (HL), placing the colon between digit pairs.
4DD1
INC HL 23
INCrement HL to point to the next digit pair position.
4DD2
LOOP BACK to 4DC0H to process the next time component.

4DD4H - Convert Date to MM/DD/YY Display

Converts the binary date values stored at 4044H (day), 4045H (month), and 4046H (year) into ASCII digit pairs and writes them to video RAM. Uses the same digit conversion algorithm as the time display but with a / separator instead of :. This routine is the target of the jump table entry at 4470H.

4DD4
LD DE,4046H 11 46 40
Point Register Pair DE to 4046H, the year counter. The date values are stored as year (4046H), month (4045H), day (4044H), accessed in descending address order.
4DD7
LD C,2FH 0E 2F
Load Register C with 2FH (ASCII /). This is the separator character displayed between month, day, and year (MM/DD/YY).
4DD9
JUMP to 4DBEH to share the same display loop as the time routine. The only difference is the separator character (/ instead of :) and the source data (date counters instead of time counters). The video RAM position will be set by the caller or by the shared code at 4DB6H.

4DDBH - Hex Display Setup

Retrieves a 16-bit value from deep in the stack and displays it as 4 hex digits at video RAM position 3C2EH. This is used during debugging or status display to show register values. The value is extracted from offset 12H (18 decimal) into the stack, which corresponds to a saved register pair from a deeply nested interrupt or subroutine context.

4DDB
LD C,IXL DD 4D
Load Register C with the low byte of IX. This preserves the IX low byte across the stack access below.
4DDD
LD HL,0012H 21 12 00
Load Register Pair HL with 0012H (decimal 18). This is the offset into the stack where the target value is stored.
4DE0
ADD HL,SP 39
ADD the current stack pointer (SP) to HL. HL now points to the saved register value at SP+18, which is buried under 9 levels of PUSH or CALL frames.
4DE1
LD E,(HL) 5E
Load Register E with the low byte of the saved value from the stack.
4DE2
INC HL 23
INCrement HL to point to the high byte.
4DE3
LD D,(HL) 56
Load Register D with the high byte. Register Pair DE now holds the 16-bit value to display.
4DE4
LD HL,3C2EH 21 2E 3C
Point Register Pair HL to 3C2EH, the video RAM position where the hex display is written. This is column 46 of the top row.
4DE7
LD A,D 7A
Load Register A with Register D (high byte of the value). Display the high byte first.
4DE8
GOSUB to the hex byte display routine at 4DECH to convert the high byte into two hex ASCII digits and write them to video RAM at (HL). HL advances by 2.
4DEB
LD A,E 7B
Load Register A with Register E (low byte of the value). Fall through to display the low byte.

4DECH - Hex Byte to ASCII Display

Converts the byte in Register A into two ASCII hexadecimal digit characters and writes them to consecutive video RAM locations at (HL). HL is advanced by 2 after writing. Uses the standard DAA-based hex-to-ASCII conversion algorithm.

4DEC
PUSH AF F5
Save Register A (the byte to convert) onto the stack. The high nibble will be processed first, then the low nibble will be restored.
4DED
RRA 1F
Rotate Register A right through Carry. This begins shifting the high nibble into the low nibble position.
4DEE
RRA 1F
Rotate right again.
4DEF
RRA 1F
Rotate right again.
4DF0
RRA 1F
Rotate right a fourth time. The high nibble is now in the low nibble position (bits 0-3). The high nibble may contain garbage from the Carry rotations.
4DF1
GOSUB to 4DF5H to convert the high nibble to ASCII and write it to video RAM.
4DF4
POP AF F1
Restore the original byte from the stack. The low nibble is now in bits 0-3.

Nibble to ASCII Hex Conversion
Convert the low 4 bits of Register A into an ASCII hex character (0-9 = 30H-39H, A-F = 41H-46H) using the standard DAA trick: ADD 90H sets Carry for values >= 10, then DAA adjusts; ADC 40H and DAA complete the conversion.

4DF5
AND A,0FH E6 0F
Mask Register A with 0FH to isolate the low nibble (0-15).
4DF7
ADD A,90H C6 90
ADD 90H to Register A. For values 0-9, this produces 90H-99H (no Carry). For values 10-15 (A-F), this produces 9AH-9FH which overflows past 99H in BCD, setting the Carry flag after DAA.
4DF9
DAA 27
Decimal Adjust Accumulator. Adjusts A to valid BCD. For 0-9: A becomes 90H-99H (valid BCD), Carry clear. For A-F: A adjusts and Carry is set from the BCD overflow.
4DFA
ADC A,40H CE 40
ADD 40H plus the Carry flag to Register A. For 0-9: 90H-99H + 40H = D0H-D9H (no carry in). For A-F: adjusted value + 40H + 1 (carry) produces the correct ASCII A-F range.
4DFC
DAA 27
Decimal Adjust Accumulator again. This final DAA completes the conversion: 0-9 becomes 30H-39H (ASCII 0-9), A-F becomes 41H-46H (ASCII A-F).
4DFD
LD (HL),A 77
Store the ASCII hex digit into video RAM at the position pointed to by HL.
4DFE
INC HL 23
INCrement HL to point to the next video RAM position for the next digit.
4DFF
RET C9
Return to the caller. When called from 4DF1H (high nibble), returns to 4DF4H to process the low nibble. When called for the low nibble, returns to the original caller.

4E00H - Cold Boot Entry Point

This is the program entry point for SYS0/SYS (PROGRAM ENTRY = 4E00H). Execution arrives here after BOOT/SYS loads SYS0 into memory and jumps to this address. The cold boot sequence disables interrupts, sets interrupt mode 1, initializes the stack pointer, and then jumps to the main initialization code at 4E14H. The code at 4E09H-4E13H is a subroutine used during banner display (NEG-encoded text decoding) that is placed here to fill the gap between the entry point and the initialization code.

4E00
DI F3
Disable Interrupts. No interrupts can fire during the critical initialization phase.
4E01
IM 1 ED 56
Set Interrupt Mode 1. In this mode, all maskable interrupts are vectored to address 0038H (RST 38H). The interrupt handler at 0038H will jump through the DOS vector at 4012H.
4E03
LD SP,41E0H 31 E0 41
Set the Stack Pointer to 41E0H. This establishes the system stack in the DOS work area, below the directory buffer at 4200H. The stack grows downward from 41E0H.
4E06
JUMP to 4E14H to continue the cold boot initialization. The gap at 4E09H-4E13H contains a subroutine used by the banner display code.

4E09H - NEG-Encoded Text Decode Subroutine

Decodes NEG-encoded text strings. Each byte in the string is stored as (0 - ASCII_char) AND FFH. The decode loop reads each byte, applies the NEG instruction to recover the original ASCII character, writes it to the screen position, and continues until a space character (20H) is encountered. This subroutine is called from the banner display code to output the boot banner text.

4E09
INC A 3C
INCrement Register A by 1. This is an alternate entry that adjusts the pointer before the decode loop begins.
4E0A
LD A,(HL) 7E
Load Register A with the encoded byte from the string at (HL).
4E0B
NEG ED 44
Negate Register A (A = 0 - A). This reverses the NEG encoding, recovering the original ASCII character value.
4E0D
LD (HL),A 77
Store the decoded ASCII character back into the memory location at (HL). This overwrites the encoded byte with the decoded character, effectively decoding the string in place.
4E0E
INC HL 23
INCrement HL to point to the next encoded byte.
4E0F
LD A,(HL) 7E
Load the next encoded byte into Register A.
4E10
CP A,20H FE 20
Compare Register A against 20H (ASCII SPACE). A space character marks the end of a word or the end of the encoded string segment.
4E12
If the NZ FLAG (Not Zero) has been set (not a space), LOOP BACK to 4E0BH to decode the next character.

4E14H - System Initialization

Main initialization routine reached from the cold boot entry at 4E00H. Initializes the high memory pointer, detects RAM size, sets up the expansion interface, copies I/O driver data, initializes keyboard and interrupt vectors, configures the system mode flags, enables interrupts, and proceeds to date/calendar initialization. This is the heart of the VTOS cold start sequence.

4E14
LD HL,5200H 21 00 52
Load Register Pair HL with 5200H. This is the default base address for user programs. All memory from 5200H upward is available for user programs.
4E17
LD (4047H),HL 22 47 40
Store 5200H into the high memory limit pointer at 4047H-4048H. This tells the system where user-available memory begins.

RAM Size Detection
Detect the amount of installed RAM by testing memory in 1K (400H byte) increments from the top of the address space downward. For each page, read a byte, complement it, write it back, and check if the write was successful. The first page that fails to write establishes the RAM top boundary.

4E1A
LD HL,0FFFFH 21 FF FF
Load Register Pair HL with FFFFH, the highest possible memory address. Start testing from the top of the 64K address space.
4E1D
LD A,(HL) 7E
Load Register A with the byte at the current test address (HL).
4E1E
LD B,A 47
Save the original byte value in Register B for restoration after the test.
4E1F
CPL 2F
Complement Register A (invert all bits). Writing the complement and reading it back tests whether the memory location is writable.
4E20
LD (HL),A 77
Write the complemented value to the test address.
4E21
CP A,(HL) BE
Compare Register A against the byte at (HL). If the write succeeded, the values match (Z set). If this is ROM, I/O, or non-existent memory, they will differ (NZ set).
4E22
LD (HL),B 70
Restore the original byte value (from Register B) to the test address.
4E23
If the Z FLAG (Zero) has been set (write succeeded, this is writable RAM), JUMP to 4E2BH to store this as the detected RAM top.
4E25
LD A,H 7C
Load Register A with the high byte of the current test address.
4E26
SUB A,04H D6 04
SUBtract 04H from the high byte, moving down by 1K (400H bytes) to test the next lower memory page.
4E28
LD H,A 67
Load Register H with the decremented page value.
4E29
LOOP BACK to 4E1DH to test the next lower memory page.
4E2B
LD (4049H),HL 22 49 40
Store the detected RAM top address into 4049H-404AH. This records the highest writable RAM address in the system.

Expansion Interface and I/O Initialization
Initialize the expansion interface, disable NMI interrupts, copy the drive activity timeout handler code to RAM, and set up the I/O device tables from the boot sector parameter area.

4E2E
XOR A,A AF
Set Register A to ZERO.
4E2F
LD (37E4H),A 32 E4 37
Store 00H to the expansion interface NMI mask register at 37E4H. This disables all NMI interrupt sources during initialization. On the Model I, 37E4H is a write-only register that controls which expansion interface signals can generate NMI.
4E32
LD A,0FFH 3E FF
Load Register A with FFH.
4E34
OUT (0EAH),A D3 EA
Output FFH to port EAH. This initializes an I/O port, clearing any pending conditions. The exact function of port EAH depends on the expansion interface hardware configuration.
4E36
LD HL,50E0H 21 E0 50
Point Register Pair HL to 50E0H, the source address for the drive activity timeout handler code. This small routine is copied to RAM so it can be modified at runtime.
4E39
LD DE,41E5H 11 E5 41
Point Register Pair DE to 41E5H, the destination in the DOS work area where the drive activity timeout code will be placed.
4E3C
LD BC,001BH 01 1B 00
Load Register Pair BC with 001BH (decimal 27). This is the number of bytes to copy for the drive activity timeout handler.
4E3F
LDIR ED B0
Block Move (Incrementing)
Copy 27 bytes from 50E0H (drive activity timeout source) to 41E5H (RAM work area). Source = 50E0H, Destination = 41E5H, Count = 27 bytes. This places the timeout handler in writable RAM where its timer values can be modified at runtime.
4E41
LD A,(3840H) 3A 40 38
Load Register A with the keyboard scan of row 6 at 3840H. This row contains ENTER, CLEAR, BREAK, and arrow keys. This checks if a key is being held during boot.
4E44
AND A,08H E6 08
Mask with 08H to test bit 3 (the UP arrow key). If the UP arrow is held during boot, a special initialization path is taken.
4E46
If the NZ FLAG (Not Zero) has been set (UP arrow is held during boot), JUMP to 4E4EH to skip overriding the default I/O return address. This allows an alternate boot path.
4E48
LD HL,439CH 21 9C 43
Load Register Pair HL with 439CH. This is an alternate system return address used when the UP arrow is not held during boot.
4E4B
LD (4016H),HL 22 16 40
Store the alternate return address into 4016H-4017H, overriding the default value set by BOOT/SYS.
4E4E
LD HL,(4016H) 2A 16 40
Load Register Pair HL with the system return address from 4016H-4017H (either the default from BOOT/SYS or the override from 4E4BH).
4E51
LD (43B8H),HL 22 B8 43
Store the return address into 43B8H-43B9H. This is the first entry in the I/O device address table, typically the display driver entry point.
4E54
LD HL,(401EH) 2A 1E 40
Load Register Pair HL with the value from 401EH-401FH. This address was set by BOOT/SYS and contains a pointer to an I/O driver or buffer address.
4E57
LD (43BAH),HL 22 BA 43
Store the I/O pointer into 43BAH-43BBH, the second entry in the I/O device address table.
4E5A
LD HL,(4026H) 2A 26 40
Load Register Pair HL with the value from 4026H-4027H, another I/O driver address set by BOOT/SYS.
4E5D
LD (43BCH),HL 22 BC 43
Store the I/O pointer into 43BCH-43BDH, the third entry in the I/O device address table.
4E60
LD HL,0000H 21 00 00
Load Register Pair HL with 0000H.
4E63
LD (43BEH),HL 22 BE 43
Store 0000H into 43BEH-43BFH, the fourth entry in the I/O device address table. A null entry indicates no device is assigned to this slot.

Work Area Clear and IY Copy
Clear the DOS work area from 43D8H-43FFH and copy the IY register contents (set by BOOT/SYS) to the I/O driver table if IY is non-zero.

4E66
LD HL,43D8H 21 D8 43
Point Register Pair HL to 43D8H, the start of a 40-byte work area that needs to be zeroed during initialization.
4E69
LD (HL),00H 36 00
Store 00H at the current work area byte.
4E6B
INC L 2C
INCrement Register L. Since HL is 43xxH, incrementing L walks through the work area byte by byte. When L wraps from FFH to 00H, all 40 bytes (43D8H-43FFH) have been zeroed.
4E6C
If the NZ FLAG (Not Zero) has been set (L has not wrapped to 00H), LOOP BACK to 4E69H to clear the next byte.
4E6E
PUSH IY FD E5
Save Register Pair IY onto the stack. IY was set by BOOT/SYS to point to additional I/O driver data.
4E70
POP HL E1
POP the IY value into HL. This is a technique to copy IY into HL without a direct transfer instruction.
4E71
LD A,H 7C
Load Register A with Register H (high byte of IY value).
4E72
OR A,L B5
OR Register A with Register L. This tests whether IY was 0000H (no additional I/O driver data). If both bytes are zero, Z is set.
4E73
If the Z FLAG (Zero) has been set (IY was 0000H, no additional driver data), JUMP to 4E7DH to skip the driver copy.
4E75
LD DE,4700H 11 00 47
Point Register Pair DE to 4700H, the drive parameter table area. The IY data from BOOT/SYS will be copied here.
4E78
LD BC,000AH 01 0A 00
Load Register Pair BC with 000AH (decimal 10). Copy 10 bytes of I/O driver data from the address that was in IY to the parameter table at 4700H.
4E7B
LDIR ED B0
Block Move (Incrementing)
Copy 10 bytes from (HL) to 4700H. Source = IY value (boot sector driver data), Destination = 4700H (drive parameter table), Count = 10 bytes. This initializes the drive 0 parameters from boot sector data.

System Flags and Mode Initialization
Set up the BREAK key handler, system capability flags, and system mode register.

4E7D
LD A,(37ECH) 3A EC 37
Load Register A from 37ECH, the FDC command/status register. On the Model I this is memory-mapped. Reading it during initialization serves to clear any pending FDC status.
4E80
LD A,0C9H 3E C9
Load Register A with C9H (the opcode for RET).
4E82
LD (4315H),A 32 15 43
Store C9H into 4315H, the BREAK key handler entry point. Writing C9H (RET) here effectively disables the BREAK handler - any call to 4315H will immediately return.
4E85
LD HL,404CH 21 4C 40
Point Register Pair HL to 404CH, the system capability flags byte.
4E88
LD (HL),00H 36 00
Store 00H into the capability flags, clearing all bits.
4E8A
SET 7,(HL) CB FE
Set bit 7 of the capability flags at 404CH. Bit 7 indicates that a keyboard is present and active.
4E8C
SET 6,(HL) CB F6
Set bit 6 of the capability flags. Bit 6 indicates that disk I/O is available.
4E8E
LD A,01H 3E 01
Load Register A with 01H.
4E90
LD (430FH),A 32 0F 43
Store 01H into 430FH (system mode flags). Bit 0 set indicates the system is in command mode (DOS READY state).
4E93
EI FB
Enable Interrupts. The system is now sufficiently initialized that interrupt handlers can safely fire. The clock tick counter at 4040H will begin incrementing.

4E94H - Date/Calendar Initialization

Decodes the packed date stored in the CONFIG/SYS area (4306H-4307H) into the individual year, month, and day counters at 4044H-4046H. The packed format encodes the year, month, and day into two bytes using bit fields. Also computes the day-of-week from the date using a calendar algorithm involving month offset tables and modulo-7 arithmetic, then displays the day name, date, and boot banner.

4E94
LD A,(4306H) 3A 06 43
Load Register A with the first packed date byte from 4306H. This byte was loaded from the CONFIG/SYS data during BOOT/SYS execution.
4E97
XOR A,50H EE 50
XOR Register A with 50H. This decodes the year value from the packed format. The XOR reverses an encoding applied when the date was stored, recovering the raw year number.
4E99
LD (4046H),A 32 46 40
Store the decoded year into the year counter at 4046H.
4E9C
LD B,A 47
Save the year value in Register B for the calendar computation below.
4E9D
DEC A 3D
DECrement Register A (year) by 1. This adjusts for the calendar algorithm which needs the year minus 1.
4E9E
CP A,0CH FE 0C
Compare Register A against 0CH (decimal 12). This checks if the adjusted year value is valid (less than 12 would indicate a very early year, possibly invalid).
4EA0
If the NO CARRY FLAG has been set (year >= 12, valid range), JUMP to 4F4FH. Actually, if year < 12 (Carry set), we continue. If year >= 12 (valid), we jump to 4F4FH which clears the date and prompts for a new one. This validates the stored date.

Month Extraction
Extract the month from the second packed date byte at 4307H. The month is stored in bits 3-7 of that byte (shifted right by 3 positions).

4EA3
LD A,(4307H) 3A 07 43
Load Register A with the second packed date byte from 4307H.
4EA6
LD E,A 5F
Save the full byte in Register E for day extraction later.
4EA7
AND A,03H E6 03
Mask with 03H to extract bits 0-1. These bits contribute to the month calculation.
4EA9
If the NZ FLAG (Not Zero) has been set (bits 0-1 are non-zero), JUMP to 4EAFH to continue month processing.
4EAB
LD HL,5022H 21 22 50
Point Register Pair HL to 5022H, a byte in the days-per-month table. When bits 0-1 of the date byte are zero, this adjusts for leap year by incrementing the February entry.
4EAE
INC (HL) 34
INCrement the byte at 5022H. This adds one day to February's count (changing 28 to 29) for leap year handling.
4EAF
LD A,E 7B
Load Register A with Register E (the full second date byte).
4EB0
RRCA 0F
Rotate Register A right. This begins shifting the month field into position.
4EB1
RRCA 0F
Rotate right again.
4EB2
RRCA 0F
Rotate right a third time. The month field from bits 3-7 is now in bits 0-4.
4EB3
AND A,1FH E6 1F
Mask with 1FH to isolate the 5-bit month value (1-12).
4EB5
LD (4045H),A 32 45 40
Store the month value into the month counter at 4045H.

Day Extraction
Extract the day-of-month from the combined date bytes. The day value spans across the two bytes.

4EB8
LD A,(4307H) 3A 07 43
Load Register A with the second packed date byte from 4307H again.
4EBB
AND A,07H E6 07
Mask with 07H to extract bits 0-2 of the second byte. These contribute to the day-of-month value.
4EBD
LD E,A 5F
Save the partial day value in Register E.
4EBE
ADD A,50H C6 50
ADD 50H to Register A. This decodes the day value from the packed encoding.
4EC0
LD (4044H),A 32 44 40
Store the decoded day-of-month into the day counter at 4044H.

Day-of-Week Calculation
Compute which day of the week this date falls on, using the year, month, and accumulated day counts from the month offset table at 5020H. The result (0-6) indexes into the day name string table at 502CH.

4EC3
LD HL,0000H 21 00 00
Load Register Pair HL with 0000H. HL will accumulate the total day count for the day-of-week calculation.
4EC6
LD A,E 7B
Load Register A with Register E (partial day value).
4EC7
ADD A,03H C6 03
ADD 03H to Register A. This is a calendar offset constant that adjusts the day-of-week calculation to align with the correct starting day.
4EC9
RRCA 0F
Rotate A right. This is part of the year-to-day conversion: dividing by 4 gives the number of leap years to add.
4ECA
RRCA 0F
Rotate A right again. A now holds (E+3)/4, the leap year correction factor.
4ECB
AND A,03H E6 03
Mask with 03H to isolate the lower 2 bits of the leap year factor.
4ECD
ADD A,E 83
ADD Register E (partial day value) to Register A. This combines the base day with the leap year adjustment.
4ECE
LD E,A 5F
Store the combined value back in Register E.
4ECF
LD A,(4045H) 3A 45 40
Load Register A with the month value from 4045H.
4ED2
ADD A,E 83
ADD the combined day/leap value (Register E) to the month. This produces an intermediate total for the day-of-week formula.
4ED3
LD E,A 5F
Store the total in Register E.
4ED4
LD D,00H 16 00
Load Register D with 00H. Register Pair DE now holds the accumulated day count as a 16-bit value.
4ED6
ADD HL,DE 19
ADD DE to HL, accumulating the total day count.
4ED7
LD DE,5020H 11 20 50
Point Register Pair DE to 5020H, the month offset table. This table contains cumulative day counts for each month, used to convert month+day to a total day number.
4EDA
LD A,(DE) 1A
Load Register A with the month offset byte from the table.
4EDB
INC DE 13
INCrement DE to point to the next table entry.
4EDC
ADD A,L 85
ADD the table value to Register L (low byte of accumulator).
4EDD
LD L,A 6F
Store the result back in L.
4EDE
ADC A,H 8C
ADD Register H plus Carry to Register A. This propagates any carry from the low byte addition.
4EDF
SUB A,L 95
SUBtract L from A. This isolates just the carry/overflow from the addition, producing the new H value.
4EE0
LD H,A 67
Store the result in Register H. HL now holds the updated accumulator.
4EE1
DECrement B (year counter) and loop back to 4EDAH if not zero. This accumulates the day counts for each year up to the current year.

Modulo-7 Reduction
Reduce the total day count modulo 7 to get the day of the week (0=Monday through 6=Sunday, or similar mapping depending on the epoch).

4EE3
LD BC,0007H 01 07 00
Load Register Pair BC with 0007H. This is the divisor for the modulo-7 operation (7 days in a week).
4EE6
XOR A,A AF
Set Register A to ZERO and clear Carry for the SBC instruction.
4EE7
SBC HL,BC ED 42
SUBtract 7 from HL. Repeatedly subtracting 7 until HL goes negative produces the remainder (day of week).
4EE9
If the NO CARRY FLAG has been set (HL >= 0 after subtraction), LOOP BACK to 4EE7H to subtract 7 again.
4EEB
LD A,L 7D
Load Register A with Register L. After the loop, L holds a value from -7 to -1 (since the last subtraction made it negative).
4EEC
ADD A,08H C6 08
ADD 08H to Register A. This converts the negative remainder (-7 to -1) into a positive day-of-week index (1 to 7). Adding 8 instead of 7 accounts for an epoch adjustment.
4EEE
LD B,A 47
Save the day-of-week index (1-7) in Register B for the day name lookup below.

Day Name Display
Use the day-of-week index to look up and display the day name string from the day name table at 502CH. Each day name is a fixed-length string terminated by 03H.

4EEF
LD HL,3ED7H 21 D7 3E
Point Register Pair HL to 3ED7H, a video RAM position where the day name will be displayed. This is on the bottom portion of the screen.
4EF2
LD (4020H),HL 22 20 40
Store the display position into 4020H-4021H. This variable holds the current cursor/output position for the message display routine.
4EF5
LD HL,502CH 21 2C 50
Point Register Pair HL to 502CH, the start of the day name string table. This table contains 7 day names, each terminated by 03H.
4EF8
GOSUB to the indexed string display routine at 4F36H. This skips B-1 strings in the table (each terminated by 03H) and then displays the Bth string. This prints the day name (e.g., "MONDAY", "TUESDAY").

Year and Month Name Display
Display the year number and month name. The year is displayed as a 2-digit decimal number, and the month name is looked up from the month name table at 5065H.

4EFB
LD A,(4046H) 3A 46 40
Load Register A with the year value from 4046H.
4EFE
LD B,A 47
Load Register B with the year value for the month name lookup counter.
4EFF
LD HL,3F14H 21 14 3F
Point Register Pair HL to 3F14H, the video RAM position for the year/month display.
4F02
LD (4020H),HL 22 20 40
Store the display position into the cursor variable at 4020H.
4F05
LD HL,5065H 21 65 50
Point Register Pair HL to 5065H, the month name string table. This table contains 12 month names, each terminated by 03H.
4F08
GOSUB to the indexed string display at 4F36H. Skips B-1 month name strings and displays the Bth month name (e.g., "JANUARY", "FEBRUARY").

Day-of-Month Display
Display the day of the month as a 1 or 2 digit decimal number.

4F0B
LD A,(4045H) 3A 45 40
Load Register A with the month value from 4045H. (Note: this appears to load the month, but contextually this is the day - the packed date may store day and month in swapped positions relative to the counter addresses.)
4F0E
LD B,0FFH 06 FF
Load Register B with FFH. This initializes the tens digit counter to -1 (FFH). The first INC B will bring it to 00H.
4F10
INC B 04
INCrement Register B (tens digit counter).
4F11
SUB A,0AH D6 0A
SUBtract 10 from Register A. If A >= 10, Carry is clear and we loop. If A < 10, Carry is set and the remaining value is the units digit.
4F13
If the NO CARRY FLAG has been set (A was >= 10), LOOP BACK to 4F10H to count another tens digit.
4F15
PUSH AF F5
Save the units digit remainder and flags onto the stack.
4F16
LD A,B 78
Load Register A with Register B (the tens digit, 0-9).
4F17
ADD A,30H C6 30
ADD 30H to convert the tens digit to ASCII (30H = 0).
4F19
CP A,30H FE 30
Compare Register A against 30H (ASCII 0). This checks if the tens digit is zero (suppressing leading zero display).
4F1B
If the NZ FLAG (Not Zero) has been set (tens digit is non-zero), GOSUB to the ROM character output routine at 0033H to display the tens digit. Leading zeros are suppressed.
4F1E
POP AF F1
Restore the units digit remainder from the stack.
4F1F
ADD A,3AH C6 3A
ADD 3AH to the units remainder. After the SUB 0AH loop, A is in range -10 to -1. Adding 3AH (= 30H + 0AH) converts to ASCII: the +0AH compensates for the extra SUB, and +30H converts to ASCII.
4F21
GOSUB to the ROM character output routine at 0033H to display the units digit.
4F24
LD HL,500EH 21 0E 50
Point Register Pair HL to 500EH, a string in the data area. This string contains ", 198" or similar year prefix text followed by the format for the full date display.
4F27
GOSUB to the display message routine at 4467H to output the date suffix string.
4F2A
LD A,(4307H) 3A 07 43
Load Register A with the second packed date byte from 4307H.
4F2D
AND A,03H E6 03
Mask with 03H to extract bits 0-1. This gives a 2-bit value (0-3) that represents the last digit or part of the year for display.
4F2F
ADD A,30H C6 30
ADD 30H to convert to ASCII digit.
4F31
GOSUB to the ROM character output at 0033H to display the year's last digit.
4F34
JUMP to 4F6BH to continue with CONFIG/SYS processing and final boot steps.

4F36H - Indexed String Display

Displays the Bth string from a table of 03H-terminated strings pointed to by HL. Skips B-1 strings by scanning for 03H terminators, then calls the display message routine to output the target string. If B = 1, displays the first string. If B = 0, jumps directly to the display routine.

4F36
DEC B 05
DECrement Register B (string index) by 1. If B was 1 (display the first string), this produces 0 and Z is set.
4F37
If the Z FLAG (Zero) has been set (index reached 0), JUMP to the display message routine at 4467H to output the current string at (HL).
4F3A
LD A,(HL) 7E
Load Register A with the byte at (HL), scanning through the string table.
4F3B
INC HL 23
INCrement HL to the next byte in the string table.
4F3C
CP A,03H FE 03
Compare Register A against 03H, the string terminator. When a terminator is found, one complete string has been skipped.
4F3E
If the NZ FLAG (Not Zero) has been set (not a terminator), LOOP BACK to 4F3AH to continue scanning.
4F40
LOOP BACK to 4F36H to decrement B and check if we have skipped enough strings.

4F42H - CONFIG/SYS Sector Read and Validate

Reads a CONFIG/SYS sector from disk and validates its contents by checking a signature pattern. This routine reads a sector into the buffer at 4200H, then scans the buffer for a specific byte pattern to confirm the sector contains valid configuration data. Register C holds the drive number, Register Pair DE holds the disk address.

4F42
GOSUB to the disk I/O dispatcher at 4777H to read the sector at disk address DE from drive C into the buffer at HL.
4F45
LD L,0C0H 2E C0
Load Register L with C0H. This points HL to offset C0H within the buffer (42C0H), the location of the CONFIG/SYS signature pattern.
4F47
LD A,C 79
Load Register A with Register C (the drive number, used as a validation counter or reference).
4F48
DEC A 3D
DECrement Register A by 1.
4F49
XOR A,(HL) AE
XOR Register A with the byte at (HL). This is part of a running XOR checksum or pattern validation across the CONFIG/SYS signature area.
4F4A
INC L 2C
INCrement L to advance to the next byte in the signature area.
4F4B
If the NZ FLAG (Not Zero) has been set (not reached the end of the page at L=00H), LOOP BACK to 4F49H to continue the XOR scan.
4F4D
OR A,A B7
OR Register A with itself. If the running XOR produced 00H, the signature is valid (Z set). Otherwise the CONFIG/SYS data is corrupt (NZ set).
4F4E
RET Z C8
If the Z FLAG (Zero) has been set (valid CONFIG/SYS signature), return with Z set indicating success.

4F4FH - Invalid Date Handler (Clear Date and Prompt)

Reached when the stored date is invalid or out of range. Clears all date-related counters and the CONFIG/SYS bytes, then displays a "NO DATE" prompt and continues to the CONFIG/SYS processing.

4F4F
XOR A,A AF
Set Register A to ZERO.
4F50
LD (4306H),A 32 06 43
Store 00H into 4306H, clearing the first packed date byte.
4F53
LD (4307H),A 32 07 43
Store 00H into 4307H, clearing the second packed date byte.
4F56
LD (4044H),A 32 44 40
Store 00H into the day counter at 4044H.
4F59
LD (4045H),A 32 45 40
Store 00H into the month counter at 4045H.
4F5C
LD (4046H),A 32 46 40
Store 00H into the year counter at 4046H.
4F5F
LD HL,3F17H 21 17 3F
Point Register Pair HL to 3F17H, a video RAM position for displaying the "NO DATE" message.
4F62
LD (4020H),HL 22 20 40
Store the display position into the cursor variable at 4020H.
4F65
LD HL,5014H 21 14 50
Point Register Pair HL to 5014H, a string in the data area containing the "NO DATE" message text (terminated by 03H).
4F68
GOSUB to the display message routine at 4467H to output the "NO DATE" message.

4F6BH - CONFIG/SYS Processing and Boot Finalization

Reads the CONFIG/SYS file from disk, processes its contents (including the system startup command string), and completes the boot sequence. The CONFIG/SYS file is read from drive 0 at a fixed disk location. If the file contains a startup command (prefixed with an asterisk *), that command is executed automatically. Otherwise, the system checks for keyboard input during boot and either enters DEBUG or proceeds to DOS READY.

4F6B
LD C,00H 0E 00
Load Register C with 00H (drive 0, the system disk).
4F6D
LD DE,0002H 11 02 00
Load Register Pair DE with 0002H (track 0, sector 2). The CONFIG/SYS data resides at track 0, sector 2 on the system disk.
4F70
LD HL,4200H 21 00 42
Point Register Pair HL to 4200H, the sector buffer where the CONFIG/SYS data will be loaded.
4F73
GOSUB to the CONFIG/SYS reader at 4F42H. Reads the sector and validates the signature. Returns Z on success.
4F76
LD A,(4201H) 3A 01 42
Load Register A with the byte at 4201H (offset 01H in the CONFIG/SYS sector). This byte contains configuration flags or a version indicator.
4F79
LD (4FE1H),A 32 E1 4F
Self-Modifying Code
Store the CONFIG/SYS flag byte into 4FE1H. This patches the RET instruction at 4FE1H in the startup finalization block. If the flag is C9H (RET), execution returns normally after copying the startup command. If it is a different opcode, it alters the final boot behavior.
4F7C
LD HL,42C0H 21 C0 42
Point Register Pair HL to 42C0H, the CONFIG/SYS data block at offset C0H in the sector buffer. This area contains 8 bytes of system configuration data.
4F7F
LD DE,3CDEH 11 DE 3C
Point Register Pair DE to 3CDEH, a video RAM position. The 8 configuration bytes will be copied to the screen for display during boot.
4F82
LD BC,0008H 01 08 00
Load Register Pair BC with 0008H (8 bytes to copy).
4F85
LDIR ED B0
Block Move (Incrementing)
Copy 8 bytes from 42C0H (CONFIG/SYS data) to 3CDEH (video RAM). Source = CONFIG/SYS configuration area, Destination = screen display position, Count = 8 bytes. This displays configuration information during the boot process.
4F87
LD HL,422AH 21 2A 42
Point Register Pair HL to 422AH, the CONFIG/SYS drive parameter data block at offset 2AH in the sector buffer.
4F8A
LD DE,470AH 11 0A 47
Point Register Pair DE to 470AH, the drive parameter table area. The CONFIG/SYS drive parameters will be copied here, populating drive configuration entries beyond drive 0.
4F8D
LD BC,0046H 01 46 00
Load Register Pair BC with 0046H (decimal 70). Copy 70 bytes of drive parameter data.
4F90
LDIR ED B0
Block Move (Incrementing)
Copy 70 bytes from 422AH (CONFIG/SYS drive parameters) to 470AH (drive parameter table). This populates the drive configuration for drives 1-7 (or however many drives the system supports). Drive 0 was initialized earlier from the BOOT/SYS IY data.

Timer Callback Registration
Register the clock display routine as a timer interrupt callback so the time display updates automatically.

4F92
LD DE,45C7H 11 C7 45
Load Register Pair DE with 45C7H, the address of the timer callback data block for the clock display handler.
4F95
LD A,07H 3E 07
Load Register A with 07H, the callback slot identifier for the clock tick handler.
4F97
GOSUB to the "enqueue timer callback" routine at 4410H. This registers the clock display routine (pointed to by the data block at 45C7H) in the timer interrupt dispatch table, so it will be called on every timer interrupt.

Second CONFIG/SYS Sector Read
Read a second CONFIG/SYS sector (track 0, sector 17) which contains additional configuration data including the auto-execute command string.

4F9A
LD C,00H 0E 00
Load Register C with 00H (drive 0).
4F9C
LD DE,1100H 11 00 11
Load Register Pair DE with 1100H (track 11H = 17 decimal, sector 00H). This is the disk address of the second CONFIG/SYS sector.
4F9F
LD HL,4200H 21 00 42
Point Register Pair HL to 4200H, the sector buffer.
4FA2
GOSUB to the sector read routine at 4B45H to read track 17, sector 0 into the buffer.
4FA5
If the NZ FLAG (Not Zero) has been set (read failed), JUMP to the DOS error exit at 4409H.

Auto-Execute Command Check
Check if the CONFIG/SYS sector contains an auto-execute command string (prefixed by * at offset E0H). If found, copy it to the command buffer and prepare for automatic execution.

4FA8
LD HL,42E0H 21 E0 42
Point Register Pair HL to 42E0H, offset E0H in the sector buffer. This is where the auto-execute command string is stored in CONFIG/SYS.
4FAB
LD A,(HL) 7E
Load Register A with the first byte of the auto-execute area.
4FAC
CP A,2AH FE 2A
Compare Register A against 2AH (ASCII *). An asterisk as the first character indicates that an auto-execute command is present.
4FAE
If the NZ FLAG (Not Zero) has been set (first byte is not *, no auto-execute command), JUMP to 4FC0H to check for keyboard input during boot.
4FB0
INC HL 23
INCrement HL to skip past the * prefix and point to the actual command string.
4FB1
LD A,(430FH) 3A 0F 43
Load Register A with the system mode flags from 430FH.
4FB4
OR A,10H F6 10
Set bit 4 of the system mode flags. Bit 4 indicates that an auto-execute command is pending.
4FB6
LD (430FH),A 32 0F 43
Store the updated mode flags back to 430FH.
4FB9
LD A,0C8H 3E C8
Load Register A with C8H (the opcode for RET Z).
4FBB
LD (4C2DH),A 32 2D 4C
Self-Modifying Code
Store C8H into the NOP placeholder at 4C2DH in the RST 28H overlay loader. This patches the NOP to become RET Z, which modifies the overlay loader's behavior: when A = 01H after DEC (producing Z), the loader will return immediately instead of loading an overlay. This is used to control auto-execute behavior.
4FBE
JUMP to 4FCDH to copy the command string to the DOS command buffer and finalize boot.

Keyboard Check During Boot
No auto-execute command was found. Check if the user is pressing keys during boot: the ENTER key on keyboard row 3841H triggers DEBUG entry, and other specific keys trigger alternate behavior.

4FC0
LD A,(3841H) 3A 41 38
Load Register A with keyboard row 1 at 3841H. This row contains keys H through O.
4FC3
BIT 4,A CB 67
Test bit 4 of the keyboard scan. Bit 4 of row 3841H corresponds to the L key. If held during boot, enter DEBUG mode.
4FC5
If the NZ FLAG (Not Zero) has been set (the key is held during boot), JUMP to 440DH to enter DEBUG mode.
4FC8
BIT 0,A CB 47
Test bit 0 of the keyboard scan. Bit 0 of row 3841H corresponds to the H key.
4FCA
If the NZ FLAG (Not Zero) has been set (the H key is held during boot), GOSUB to the patch routine at 50D0H. This applies a runtime patch for alternate boot behavior.

Copy Command String and Finalize Boot
Copy the auto-execute command string (if any) from the CONFIG/SYS buffer to the DOS command buffer at 4318H, then finalize the boot by setting up the DOS READY entry point and jumping to it.

4FCD
LD DE,4318H 11 18 43
Point Register Pair DE to 4318H, the DOS command input buffer. The auto-execute command string will be copied here.
4FD0
LD BC,0020H 01 20 00
Load Register Pair BC with 0020H (decimal 32). Copy up to 32 bytes of command string.
4FD3
LDIR ED B0
Block Move (Incrementing)
Copy 32 bytes from (HL) to the command buffer at 4318H. If an auto-execute command was found, HL points to it. Otherwise, HL may point to an empty area, resulting in a blank command buffer.
4FD5
LD HL,4FEEH 21 EE 4F
Point Register Pair HL to 4FEEH, the start of the system startup finalization code block. This block will be copied to 405DH and executed as the final boot step.
4FD8
LD DE,405DH 11 5D 40
Point Register Pair DE to 405DH, the target address in the DOS vector area where the finalization code will be placed.
4FDB
PUSH DE D5
Save the target address (405DH) onto the stack. This will be used as the return address - when the current routine returns, execution will jump to the copied finalization code at 405DH.
4FDC
LD BC,0020H 01 20 00
Load Register Pair BC with 0020H (decimal 32). Copy 32 bytes of finalization code.
4FDF
LDIR ED B0
Block Move (Incrementing)
Copy 32 bytes from 4FEEH (finalization code) to 405DH (vector area). This places executable code at 405DH that performs the final boot steps.
4FE1
RET C9
Self-Modifying Code
RET. This byte at 4FE1H was potentially patched at 4F79H with a value from CONFIG/SYS. If it remains C9H (RET), execution transfers to the finalization code at 405DH (pushed onto the stack at 4FDBH). If patched to another opcode, it alters the boot flow.

4FE2H - Startup Finalization Data Block

This data block at 4FE2H-4FEDH is NOT executable code at this address - it is a block of bytes that gets copied to 405DH-407DH via the LDIR at 4FDFH, where it becomes executable code. The code, when executed at its destination address 405DH, checks system flags and either executes the auto-command or enters DOS READY. This is combined with the block at 4FEEH below as a single 32-byte copy source.

Relocated Code Block
The bytes from 4FEEH to 500DH are copied to 405DH-407CH at runtime. When executed at 405DH, this code: checks bit 3 of the system mode flags (430FH); if set, outputs 01H to port FEH; sets the cursor position to 3F40H; checks whether the command buffer at 4318H contains a command (first byte = 0DH means empty); and either jumps to 4400H (DOS READY) or calls the message display routine and then the command executor.

4FEE-500D
DEFB (relocated startup code, 32 bytes) 3A 0F 43 CB 5F 28 04 3E 01 D3 FE 21 40 3F 22 20 40 21 18 43 7E FE 0D CA 00 44 CD 67 44 C3 05 44
32 bytes of code that will be copied to 405DH and executed there. When run at its destination address, the code performs: (1) LD A,(430FH) - load system mode flags; (2) BIT 3,A - test bit 3 (auto-execute pending); (3) JR Z,skip - if not set, skip port output; (4) LD A,01H / OUT (FEH),A - output to port FEH (expansion interface control); (5) LD HL,3F40H / LD (4020H),HL - set cursor to bottom-left of screen; (6) LD HL,4318H / LD A,(HL) / CP A,0DH - check if command buffer starts with carriage return (empty); (7) JP Z,4400H - if empty, jump to DOS READY; (8) CALL 4467H - display the command string; (9) JP 4405H - jump to command executor.

500EH - Date/Day/Month Name String Data

ASCII string data tables used by the date display and calendar routines. Contains date format strings, day-of-week names, and month names, all terminated by 03H. Also contains the cumulative days-per-month offset table used by the day-of-week calculation.

500E-5013
DEFM ", 198",03H 2C 20 31 39 38 03
Date suffix string: ", 198" followed by 03H terminator. The final year digit is appended dynamically by the code at 4F2FH.
5014-501F
DEFM "--NO DATE--",03H 2D 2D 4E 4F 20 44 41 54 45 2D 2D 03
No-date message string: "--NO DATE--" followed by 03H terminator. Displayed when the stored date is invalid.
5020-502B
DEFB 00H,1FH,1CH,1FH,1EH,1FH,1EH,1FH,1FH,1EH,1FH,1EH 00 1F 1C 1F 1E 1F 1E 1F 1F 1E 1F 1E
Days-per-month offset table (12 entries). Byte 0: 00H (unused/epoch offset). Bytes 1-12: days per month for January through December: 31(1FH), 28(1CH), 31(1FH), 30(1EH), 31(1FH), 30(1EH), 31(1FH), 31(1FH), 30(1EH), 31(1FH), 30(1EH). The February entry at 5022H is incremented at runtime for leap years.
502C-5064
DEFM day name strings 4D 4F 4E 44 41 59 03 ... 53 55 4E 44 41 59 03
Day-of-week name table: 7 strings, each terminated by 03H. The strings are: "MONDAY" 03H, "TUESDAY" 03H, "WEDNESDAY" 03H, "THURSDAY" 03H, "FRIDAY" 03H, "SATURDAY" 03H, "SUNDAY" 03H. Indexed by the day-of-week calculation result at 4EF8H.
5065-50CF
DEFM month name strings 4A 41 4E 55 41 52 59 20 03 ... 44 45 43 45 4D 42 45 52 20 03
Month name table: 12 strings, each terminated by 03H. The strings are: "JANUARY " 03H, "FEBRUARY " 03H, "MARCH " 03H, "APRIL " 03H, "MAY " 03H, "JUNE " 03H, "JULY " 03H, "AUGUST " 03H, "SEPTEMBER " 03H, "OCTOBER " 03H, "NOVEMBER " 03H, "DECEMBER " 03H. Note trailing spaces pad shorter names. Indexed by month number at 4F08H.

50D0H - Boot Patch Routine

A small patch routine called during boot when the H key is held (at 4FCAH). It modifies the relocated startup code by writing C3H (the JP opcode) into address 5005H. This changes a conditional instruction in the startup finalization block to an unconditional JP, altering the auto-execute behavior.

50D0
LD A,0C3H 3E C3
Load Register A with C3H, the opcode for the JP (unconditional jump) instruction.
50D2
LD (5005H),A 32 05 50
Self-Modifying Code
Store C3H into 5005H. This patches a JP Z instruction in the startup finalization data block (at 5005H, which corresponds to a conditional jump in the relocated code) to become an unconditional JP. This forces the system to always jump to DOS READY (4400H) regardless of whether the command buffer is empty, effectively bypassing auto-execute.
50D5
RET C9
Return to the caller at 4FCDH to continue boot finalization.

50E0H - Drive Activity Timeout Handler (Source)

This is the source copy of the drive activity timeout handler. During initialization at 4E36H, these 27 bytes are copied via LDIR to 41E5H where they execute as a timer interrupt callback. The handler checks whether a drive motor should be turned off after a period of inactivity. IX+03H holds the last drive activity key code and IX+04H holds the timeout counter. When the timeout expires, the drive motor is deselected.

50E0
LD A,(IX+03H) DD 7E 03
Load Register A with the drive activity flag from IX+03H. This flag is non-zero when a drive motor is currently running.
50E3
OR A,A B7
OR Register A with itself to test if any drive is active.
50E4
RET Z C8
If the Z FLAG (Zero) has been set (no drive is active), return immediately. Nothing to do.
50E5
LD A,(4040H) 3A 40 40
Load Register A with the master tick counter from 4040H.
50E8
SUB A,(IX+04H) DD 96 04
SUBtract the timeout target value (IX+04H) from the current tick counter. If the result is zero, the timeout period has not yet elapsed.
50EB
If the Z FLAG (Zero) has been set (tick counter matches timeout target), JUMP to 50F3H to refresh the timeout without turning off the motor yet.
50ED
SUB A,1EH D6 1E
SUBtract 1EH (decimal 30) from the difference. This checks if 30 ticks have elapsed since the timeout target was set.
50EF
If the CARRY FLAG has been set (fewer than 30 ticks have elapsed), JUMP to 50F3H. The motor has not been idle long enough to turn off yet.

Motor Timeout Expired
The drive has been idle for at least 30 ticks. Turn off the motor by clearing the activity flag and returning A = 00H.

50F1
XOR A,A AF
Set Register A to ZERO. This clears the drive activity flag.
50F2
RET C9
Return with A = 00H. The caller (interrupt handler) will write this value back to the drive select latch to deselect all drives, turning off the motor.
50F3
LD A,(4040H) 3A 40 40
Load Register A with the current master tick counter from 4040H.
50F6
ADD A,03H C6 03
ADD 03H to the current tick counter. This sets a new timeout target 3 ticks in the future.
50F8
JUMP to 4C11H to store the new timeout value into IX+04H. The address 4C11H contains LD (IX+04H),A / ... which stores the timeout target. This is the final instruction of the SYS0/SYS file.