TRS-80 DOS - VTOS 4.0.2 - SYS8/SYS Disassembled

Page Customization

Introduction:

VTOS 4.0 SYS8/SYS Disassembly - Granule Allocation and Directory Update Overlay (Model I)

SYS8/SYS is the VTOS 4.0 overlay responsible for granule allocation, directory entry construction, and the low-level sector read/write stubs that the DOS kernel calls when extending or writing files. It loads at 4E00H and occupies approximately 663 bytes through 5096H.

The overlay is structured in two major sections. The upper section (4E00H-4FEFH) implements the granule-allocation engine: it walks the drive's Granule Allocation Table (GAT), locates free granules, constructs directory extent records, and writes updated directory sectors back to disk. The lower section (5000H-5096H) provides a set of compact sector I/O stubs - read-sector, write-sector, and read/write-directory-sector wrappers - that the kernel calls through a fixed jump table.

Key operations performed by this overlay include:

  • Scanning the GAT sector (loaded at 5100H) for free granules, subject to a per-drive granule-count limit.
  • Constructing extent records in directory entries: encoding the starting granule, sector count, and granule count into the two-byte extent format used by VTOS.
  • Writing new extent data into an existing directory entry and updating the directory sector on disk.
  • Providing read-sector and write-sector stubs (with optional read-after-write verify) for both the GAT/directory area and the main data area.

This overlay interacts heavily with SYS0 routines at 4797H (read directory sector), 4B10H (directory entry validation), 4B1FH (directory entry write), 4B45H (read sector), 4B65H (directory I/O setup), 4B6CH (24-bit multiply), 4B7BH (multiply by sectors-per-granule), 4768H (write sector), and 4772H (write-verify).

Variable / Work Area Reference

Address Range
Bytes
Purpose
4E4AH
1 byte
Self-Modifying Operand - Remaining Count
Written at 4E12H with the computed number of free granules remaining in the current extent. Read back as the operand of a CP instruction at 4F3BH-4F3CH to limit granule scanning.
4F88H
1 byte
Current Directory Track/Sector Info Byte
Saved at 4E54H from (IX+07H) (the directory info byte for the active drive). Read back at 4FB5H and 4FD3H to re-read the correct directory sector when writing updated entries.
4FE0H
1 byte
Self-Modifying Operand - Starting Granule
Written at 4FA0H with the starting granule number (L at that point). Read back as the operand of a LD A,nn instruction at 4FDF-4FE0H when constructing the final extent byte.
5014H
1 byte
Self-Modifying Operand - Total Sector Count
Written at 4FFFH with the computed total sector count for the extent (result of the 24-bit multiply via 4B6CH). Read back as the operand of a CP or LD instruction at 5013H-5014H during sector-count validation in the GAT scan loop.
5032H
1 byte
Self-Modifying Operand - Encoded Granule Byte (SET form)
Written at 502EH with the granule index shifted and OR'd with 40H. Used as the operand to SET a bit in the GAT bitmap byte at 5031H-5032H.
503EH
1 byte
Self-Modifying Operand - Encoded Granule Byte (RES form)
Written at 5039H with the granule index shifted and OR'd with C7H. Used as the operand to RES a bit in the GAT bitmap byte at 503DH-503EH.
5100H-51FFH
256 bytes
GAT Sector Buffer
One full sector (256 bytes) used as the working buffer when the overlay reads the GAT sector via 4B45H. The granule bitmap occupies the first N bytes of this buffer, where N is the number of granules on the drive divided by 8.

Major Routine Reference

AddressName and Purpose
4E00HGranule Availability Check
Entry: A = command/status byte, B = drive info, C = drive number, DE = requested granule count, HL = ?. Checks if the drive has enough free granules; returns Z set if the request can be satisfied, Carry set with count in A on partial availability.
4E4CHDirectory Entry Scan and Extent Allocator
Entry: IX points to drive parameter block, BC = requested record count, DE = target address. Scans the directory for a matching entry, calls 4B10H for validation, computes available extent space, and either records a partial fill or loops for more extents.
4E97HDirectory Sector Read Helper
Entry: IX points to drive parameter block, BC preserved. Reads the directory sector for the active drive into the working area and validates the entry. Returns NZ on error.
4F80HDirectory Sector Write - Data Area
Writes the updated sector (containing new extent data) back to disk using the write stub at 5053H. On success, sets B=00H and calls 4B1FH to update the directory entry.
4F8CHDirectory Sector Write - Directory Area
Reads the directory sector (via 506CH), extracts the drive's max-granule count from (IX+07H), calls 4FE8H to compute the sector count, constructs the two-byte extent encoding, and writes the directory sector.
4FE8HSector Count Calculator
Entry: A = granule count for the extent. Calls 4797H to read the GAT sector info, then uses 4B6CH (24-bit multiply) to convert granule count to total sector count. Returns result in HL:A (24-bit).
5000HGAT Bit Set / Clear Utility
Restores AF from stack, places byte value in L, then falls into the GAT scan entry at 500BH. Marks a granule as used or free in the GAT bitmap buffer at 5100H.
500BHGAT Bitmap Scanner
Scans the GAT buffer at 5100H for a free granule, applying the self-modifying limit at 5014H. Returns Z set if a suitable free granule is found.
5027HGAT Bit Set Encoder
Entry: A = granule index (bits 2-0 = bit position, bits 7-3 = byte offset). Encodes the SET-bit instruction operand into 5032H and checks the current bit state via BIT 0,B.
5034HGAT Bit Clear Encoder
Entry: A = granule index. Encodes the RES-bit instruction operand into 503EH. Returns with the encoded byte in A, CF clear.
5040HRead Sector Stub (Data Area)
Entry: (IX+06H) = drive/sector info. Calls 4B65H to set up the track/sector, then calls 4B45H to read one sector into the buffer at 5100H. Returns Z on success, A=14H on error.
5053HWrite Sector Stub (Data Area)
Calls 4B65H, then 4768H to write the sector. Optionally calls 4772H for read-after-write verify if the first write succeeds. Returns A=15H.
506CHRead Directory Sector Stub
Entry: (IX+06H) = drive info. Calls 4B65H, then reads the directory sector at E=01H into the buffer at 4200H via 4B45H. Returns Z on success, A=16H on error.
507EHWrite Directory Sector Stub
Calls 4B65H, then 4768H to write the directory sector from 4200H. Optionally verifies. Returns A=17H.

Cross-Reference Notes

Called by: SYS0 kernel dispatches to this overlay via the RST 28H overlay mechanism whenever a file-write or file-extend operation requires granule allocation or directory update. SYS2 also references the sector I/O stubs.

Calls into SYS0: 4797H (read directory sector by info byte), 4B10H (directory entry validation), 4B1FH (directory entry write/update), 4B45H (read sector), 4B65H (directory I/O setup), 4B6CH (24-bit multiply), 4B7BH (multiply by sectors-per-granule), 4768H (write sector), 4772H (write-verify).

Disassembly:

4E00H - Granule Availability / Drive State Check

Entry point for the granule allocation overlay. Checks the command/status byte and dispatches to the main allocation engine if the drive is in the correct state.

4E00
AND A,70H E6 70
Mask Register A with 70H (binary 01110000), isolating bits 6-4. These bits encode the drive state or command class. Bits 7, 3-0 are discarded.
4E02
CP A,10H FE 10
Compare the masked value against 10H. If bits 6-4 of the original byte were 001 (i.e., only bit 4 was set), the Z FLAG is set. Any other pattern clears Z.
4E04
If the Z FLAG is set (drive state byte had pattern 001 in bits 6-4), jump forward to 4E07H to proceed with the allocation engine. Otherwise fall through.
4E06
RET C9
The drive state does not match the expected pattern. Return to the caller immediately without performing any allocation.
4E07
GOSUB to the Directory Entry Scan and Extent Allocator at 4E4CH. This routine walks the directory, validates the entry, and attempts to fill the requested extent. On return, Z is set if the entry was handled (fully or partially), NZ on error.
4E0A
RET NZ C0
If the NZ FLAG is set (the allocator returned an error), return immediately to propagate the error to the caller.
4E0B
PUSH HL E5
Save Register Pair HL onto the stack. HL at this point holds the directory entry address or a pointer returned by 4E4CH.
4E0C
LD H,B 60
Load Register H with Register B. BC holds the requested record count passed in by the caller. This moves the high byte of the count into H.
4E0D
LD L,C 69
Load Register L with Register C. HL now holds the full 16-bit requested record count (originally in BC).
4E0E
XOR A,A AF
Set Register A to zero and clear all flags. This prepares for a 16-bit subtraction using SBC HL,DE.
4E0F
SBC HL,DE ED 52
Subtract DE from HL with borrow (Carry=0 after XOR A). DE holds the count of granules already allocated or the remaining demand. The result in HL is (requested count - allocated count), i.e., how many records are still unfilled.
4E11
LD A,L 7D
Load Register A with the low byte of the remaining-count result in HL.
4E12
LD (4E4AH),A 32 4A 4E
Self-Modifying Code
Store the remaining record count (Register A) into address 4E4AH. This address is the operand byte of a CP instruction at 4E49H-4E4AH inside the allocator loop. At runtime, this CP will compare the loop's progress against the dynamically computed remaining demand.
4E15
POP HL E1
Restore Register Pair HL from the stack (the directory entry pointer saved at 4E0BH).
4E16
PUSH DE D5
Save Register Pair DE onto the stack. DE holds the allocated-count or target address that must be preserved across the following IX manipulation.
4E17
PUSH IX DD E5
Save Register IX onto the stack. IX points to the current drive parameter block (10-byte block in the table at 4700H, selected by the drive number). It must be preserved while HL is temporarily used for pointer arithmetic.
4E19
EX (SP),HL E3
Exchange Register Pair HL with the value on top of the stack (which is the just-pushed IX value). After this: HL = IX (the drive parameter block pointer), and the stack top holds the old HL (directory entry pointer).
4E1A
LD DE,000EH 11 0E 00
Load Register Pair DE with 000EH (decimal 14). This is an offset into the drive parameter block. The drive parameter block is 10 bytes, but the surrounding FCB/directory structure has additional fields. Offset 0EH points to the extent descriptor area within the structure that IX references.
4E1D
ADD HL,DE 19
Add DE (000EH) to HL (the drive parameter block pointer / IX value). HL now points 14 bytes past the base of the drive parameter block, addressing the first extent descriptor slot.
4E1E
POP DE D1
Restore Register Pair DE from the stack. The stack top now holds the old HL (directory entry pointer), so DE = the old HL (directory entry address).
4E1F
LD B,05H 06 05
Load Register B with 05H. This initialises a loop counter of 5, corresponding to the maximum number of extent records that can be searched in the directory entry. VTOS directory entries hold up to 5 extents.

Loop Start
The following loop (4E21H-4E32H) walks up to 5 extent slots in the directory entry, comparing the extent's drive/track byte against DE to find the correct entry, and checking granule availability.

4E21
LD A,(HL) 7E
Fetch the first byte of the current extent slot (pointed to by HL) into Register A. In the VTOS extent format, this byte encodes the starting granule number and flags.
4E22
INC HL 23
Advance HL to point to the second byte of the current extent slot (the granule count / sector count byte).
4E23
CP A,E BB
Compare Register A (first extent byte) against Register E (low byte of the directory entry address / target value from DE). If they match, the Z FLAG is set, indicating this extent slot corresponds to the drive or entry being sought.
4E24
If the NZ FLAG is set (this extent slot does not match), jump forward to 4E2CH to skip the second-byte check and advance to the next slot.
4E26
LD A,(HL) 7E
Fetch the second byte of the current extent slot (granule count / sector count) into Register A. This byte is tested to determine how many granules remain available in this extent.
4E27
XOR A,D AA
XOR Register A (second extent byte) with Register D (high byte of DE, the drive/track reference). This tests whether the upper 5 bits (E0H mask applied next) of the second byte match D's pattern.
4E28
AND A,0E0H E6 E0
Mask the XOR result with 0E0H (binary 11100000), isolating the three most-significant bits. If the result is zero, the upper 3 bits of the second extent byte matched D exactly, indicating this extent is on the correct drive and has compatible granule-count encoding.
4E2A
If the Z FLAG is set (upper 3 bits matched), jump to 4E45H to record this extent as the allocation target and update the directory.
4E2C
DEC B 05
Decrement the extent-slot loop counter in Register B. B started at 5 and counts down one per slot examined.
4E2D
If the Z FLAG is set (B has reached zero, all 5 extent slots exhausted without a match), jump to 4E34H to handle the no-match condition (shift the extent block down and return with Carry set).
4E2F
INC HL 23
Advance HL by 1, skipping the second byte of the current extent slot.
4E30
INC HL 23
Advance HL by 1 again.
4E31
INC HL 23
Advance HL by 1 again. After the three INC HL instructions (4E2FH-4E31H), HL has moved 3 bytes forward from the second byte of the current slot. Combined with the INC HL at 4E22H (which already advanced past the first byte), HL is now positioned at the first byte of the next 4-byte extent slot.
4E32
Loop End
Loop back unconditionally to 4E21H to examine the next extent slot.

No-Match Path
All 5 extent slots were examined without finding a matching entry. The following code at 4E34H shifts the extent block toward the start of the structure using LDDR, then returns with Carry set and Z clear to signal "partial or no allocation".

4E34
PUSH DE D5
Save Register Pair DE (the directory entry pointer) onto the stack. DE must be preserved while HL and DE are reused for the LDDR block move.
4E35
EX DE,HL EB
Exchange Register Pairs DE and HL. After the exchange: DE points to the current (end) position in the extent block (just past the last slot), and HL holds the old DE (directory entry pointer).
4E36
LD HL,0FFFCH 21 FC FF
Load Register Pair HL with 0FFFCH. This is -4 in two's complement (16-bit). When added to DE (the end-of-block pointer), it produces a pointer 4 bytes before the end, i.e., the start of the last extent slot.
4E39
ADD HL,DE 19
Add DE to HL. HL = (end-of-block pointer) + 0FFFCH = end-of-block minus 4, pointing to the start of the last (5th) extent slot. DE still points to the end of the block. This sets up source and destination for the LDDR block copy.
4E3A
LD BC,000CH 01 0C 00
Load Register Pair BC with 000CH (decimal 12). This is the byte count for the LDDR block move: 3 slots x 4 bytes each = 12 bytes. The move will shift the four oldest extent slots one position toward the end of the block, discarding the oldest and making room at the start.
4E3D
LDDR ED B8
Block move: copy BC bytes from (HL) to (DE), decrementing both HL and DE after each byte, until BC reaches zero. This shifts 12 bytes of extent data upward (toward higher addresses) by 4 bytes, effectively dropping the oldest extent slot and opening a fresh slot at the low end of the block.
4E3F
EX DE,HL EB
Exchange Register Pairs DE and HL. After the LDDR, DE points one byte below the destination end. Swapping recovers a usable pointer for subsequent cleanup.
4E40
POP BC C1
Restore the saved DE value (the directory entry pointer, pushed at 4E34H) into BC. It was pushed as DE but popped into BC here because the caller needs it in BC for the return convention.
4E41
POP DE D1
Restore the next saved value from the stack into DE. This recovers the allocated-count or target address saved at 4E16H.
4E42
XOR A,A AF
Clear Register A and all flags. This clears the Carry flag before the SCF below, ensuring a clean state.
4E43
SCF 37
Set the Carry Flag. The Carry flag on return signals to the caller that the allocation could not be fully satisfied from the existing extent slots - a partial or null result.
4E44
RET C9
Return to the caller with Carry set (no match found in extent block, extent block has been rotated to make room for a new entry).

Match Found Path
A matching extent slot was found at 4E2AH. The following code at 4E45H writes D into (HL) to update the granule-count byte of the matched extent slot, then recovers the stack and returns with Z set and Carry clear.

4E45
LD (HL),D 72
Store Register D into the memory location pointed to by HL. HL currently points to the second byte of the matched extent slot (the granule count / sector count byte). D holds the updated count value computed from DE. This records the new granule count for this extent.
4E46
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now holds the old DE value (the directory entry pointer or allocated-count). DE now points into the extent block at the matched slot's second byte.
4E47
POP AF F1
Restore the top of stack into AF. This discards the saved IX value that was placed on the stack at 4E17H (and partially exchanged by EX (SP),HL at 4E19H) - it is no longer needed since the match was found.
4E48
XOR A,A AF
Clear Register A to zero and set the Z FLAG. Z set on return signals success: the extent slot was matched and updated.
4E49
LD A,00H 3E 00
Self-Modifying Code
Load Register A with the byte at the operand position (4E4AH). The operand byte 00H is the cold-start default; at runtime it is overwritten by the store at 4E12H with the computed remaining-count value. This CP effectively tests whether the remaining demand has been satisfied.
4E4B
RET C9
Return to the caller. On this path, Z is set (from XOR A,A at 4E48H) and Carry is clear, signalling a successful extent update.

4E4CH - Directory Entry Scan and Extent Allocator

Main allocation engine. Called with IX = drive parameter block pointer, BC = requested record count, DE = target address. Reads the directory, validates the entry via 4B10H, computes available granules, and fills or partially fills the extent.

4E4C
PUSH BC C5
Save Register Pair BC (the requested record count) onto the stack. BC must be preserved across the calls to 4B10H and 4797H that follow.
4E4D
LD DE,0000H 11 00 00
Load Register Pair DE with 0000H. DE is initialised to zero here; it will accumulate the allocated granule or sector count as the routine progresses through the GAT bitmap.
4E50
LD B,(IX+07H) DD 46 07
Load Register B with the byte at offset +07H in the drive parameter block pointed to by IX. Offset +07H holds the directory info byte (sectors-per-track encoding and interleave) for the current drive. This value is used to select which directory sector to read.
4E53
LD A,B 78
Copy Register B (the directory info byte just loaded) into Register A, making it available for both the self-modifying store below and the CALL to 4797H.
4E54
LD (4F88H),A 32 88 4F
Self-Modifying Code
Store Register A (the directory info byte) into address 4F88H. This address is the operand byte of LD A,(4F88H) instructions at 4FB5H and 4FD3H. At runtime those instructions will reload the same directory info byte to re-read the correct directory sector during the write-back phase.
4E57
LD C,(IX+06H) DD 4E 06
Load Register C with the byte at offset +06H in the drive parameter block (IX). Offset +06H holds the drive number for the current drive. C now holds the drive number, required by the 4B10H directory entry validation call.
4E5A
GOSUB to SYS0 routine at 4B10H: Directory Entry Validation. Entry: C = drive number, IX = drive parameter block. On return, HL points to the matched directory entry, Z is set if valid, NZ if the entry is invalid or not found.
4E5D
LD BC,0016H 01 16 00
Load Register Pair BC with 0016H (decimal 22). This is the byte offset to the extent chain data within a 32-byte directory entry (offset +16H = start of extent chain, per the directory entry layout in Section 2K).
4E60
ADD HL,BC 09
Add BC (0016H) to HL (the directory entry base pointer from 4B10H). HL now points to the first extent record at offset +16H within the directory entry.
4E61
EX DE,HL EB
Exchange Register Pairs DE and HL. DE now points to the first extent record (+16H into the directory entry). HL recovers the previous DE value (which was 0000H, the accumulator initialised at 4E4DH).
4E62
POP BC C1
Restore Register Pair BC from the stack (the requested record count saved at 4E4CH).
4E63
RET NZ C0
If the NZ FLAG is set (4B10H returned NZ, indicating the directory entry is invalid or not found), return immediately to propagate the error.

Extent Chain Walk Loop
The following code (4E64H-4E87H) walks the extent chain in the directory entry. Each extent is 2 bytes. The loop reads each 2-byte extent pair, decodes the granule start and count, accumulates the total sector count, and either finds the current end-of-file extent or steps to the next one.

4E64
LD A,(DE) 1A
Fetch the first byte of the current extent record (pointed to by DE) into Register A. In the VTOS extent format, this byte encodes the starting granule number (bits 4-0) and the drive/sector flags (bits 7-5).
4E65
CP A,0FEH FE FE
Compare Register A against 0FEH. Values 0FEH and 0FFH are sentinel values marking the end of the extent chain (0FEH = last extent with data, 0FFH = empty/unused slot). If A >= 0FEH, the Carry flag is clear.
4E67
If the NO CARRY FLAG is set (A >= 0FEH, end-of-chain sentinel found), jump to 4E88H to handle the terminal extent or start a new one.
4E69
INC DE 13
Advance DE to point to the second byte of the current extent record (the granule count / sector count byte).
4E6A
LD A,(DE) 1A
Fetch the second byte of the current extent record into Register A. This byte encodes the granule count for this extent in bits 4-0, and additional flags in bits 7-5.
4E6B
PUSH HL E5
Save Register Pair HL (the running sector-count accumulator) onto the stack. HL must be preserved while the sector count for this extent is computed and added.
4E6C
AND A,1FH E6 1F
Mask Register A with 1FH (binary 00011111), extracting bits 4-0: the raw granule count for this extent. Bits 7-5 (drive flags) are discarded.
4E6E
INC A 3C
Increment Register A by 1. The granule count is stored as (actual count - 1) in the directory, so adding 1 recovers the true granule count for this extent.
4E6F
ADD A,L 85
Add Register L (low byte of the running accumulator in HL) to Register A (granule count for this extent). This begins accumulating the total granule count across all extents seen so far.
4E70
LD L,A 6F
Store the updated low-byte accumulator back into Register L.
4E71
If the NO CARRY FLAG is set (no overflow from the ADD A,L), jump to 4E74H, skipping the INC H carry propagation.
4E73
INC H 24
Carry propagation: increment Register H (the high byte of the accumulator in HL) because the ADD A,L at 4E6FH produced a carry out of bit 7.
4E74
PUSH HL E5
Save the updated running accumulator (HL) onto the stack so it can be compared against BC (the requested count) next.
4E75
DEC HL 2B
Decrement HL by 1. This adjusts the accumulator for the comparison: because the granule count was incremented at 4E6EH (stored as count-1), subtracting 1 here aligns the accumulated total back to a zero-based index for the SBC comparison.
4E76
XOR A,A AF
Clear Register A and the Carry flag, preparing for a 16-bit subtraction SBC HL,BC.
4E77
SBC HL,BC ED 42
Subtract BC (the requested record count) from HL (accumulated granule total minus 1). If HL >= BC, the result is non-negative and the Carry flag is clear, meaning the accumulated extents already meet or exceed the request. If HL < BC, Carry is set, meaning more extents are needed.
4E79
POP HL E1
Restore the updated accumulator HL from the stack (the value pushed at 4E74H, before the DEC HL and SBC).
4E7A
If the NO CARRY FLAG is set (accumulated count >= requested count), jump to 4E80H. The current extent chain already satisfies the full request; proceed to extract the final extent's address and granule pointer.
4E7C
INC DE 13
Advance DE past the second byte of the current extent to point to the first byte of the next extent record.
4E7D
POP AF F1
Discard the HL value that was saved at 4E6BH (the accumulator snapshot before adding this extent's count). It is no longer needed since the running total in HL already incorporates it.
4E7E
Loop Back
Unconditionally loop back to 4E64H to process the next extent record in the chain.

Extent Found - Extract Address
The accumulated extent count has met or exceeded the requested count. The following code at 4E80H recovers the pointer to the matching extent's two-byte record from the saved HL, and returns the extent's granule-block pointer.

4E80
POP HL E1
Restore HL from the stack (the accumulator snapshot saved at 4E6BH, before the current extent's count was added). This recovers the running total at the end of the previous extent.
4E81
EX DE,HL EB
Exchange DE and HL. HL now points to the second byte of the matched extent record (DE was pointing there after the INC DE at 4E69H). DE holds the old running accumulator.
4E82
LD A,(HL) 7E
Fetch the second byte of the matched extent (the granule count / sector count byte) into Register A.
4E83
DEC HL 2B
Decrement HL to point back to the first byte of the matched extent (the starting granule byte).
4E84
LD L,(HL) 6E
Load Register L with the byte at the current HL address (the first byte of the matched extent: the starting granule number and flags). HL now has L = starting granule byte, H = unchanged.
4E85
LD H,A 67
Load Register H with Register A (the second byte of the matched extent, the granule count byte fetched at 4E82H). HL is now fully loaded: H = count byte, L = start byte, forming the 16-bit extent descriptor for the matched entry.
4E86
XOR A,A AF
Clear Register A and set Z FLAG. Z set on return indicates success: a matching extent was found and its descriptor is returned in HL.
4E87
RET C9
Return to the caller (4E07H) with Z set, HL = the matched extent's two-byte descriptor (H = count byte, L = start byte), and DE = the running accumulator up to the previous extent.

End-of-Chain Handler
A sentinel byte (0FEH or 0FFH) was found at 4E67H, indicating the end of the existing extent chain. The following code at 4E88H handles both the case of reaching a terminal entry (0FEH: the last written extent) and the case of finding an empty slot (0FFH: no further extents exist), dispatching to either a new-extent allocation path or the main loop continuation.

4E88
PUSH BC C5
Save Register Pair BC (the requested record count) onto the stack. BC must be preserved while the end-of-chain logic executes.
4E89
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now holds the address of the sentinel byte in the extent chain (where DE was pointing). DE recovers the previous accumulator value in HL.
4E8A
If the NZ FLAG is set (the sentinel byte was 0FFH, an unused/empty slot rather than 0FEH), jump to 4E90H to allocate a brand-new extent starting at this slot.
4E8C
INC HL 23
The sentinel was 0FEH (terminal entry with data). Advance HL past the sentinel byte to point to the second byte of this terminal extent record (the granule count byte of the last real extent).
4E8D
LD B,(HL) 46
Load Register B with the second byte of the terminal extent (the granule count byte of the last written extent). This value is needed to reconstruct the extent descriptor for the write-back path.
4E8E
Jump back to 4E53H to re-enter the allocation engine with B holding the terminal extent's count byte. This re-reads the directory info byte and restarts the allocation pass using the last extent as the starting point for further granule allocation.
4E90
GOSUB to the Directory Sector Read Helper at 4E97H. This reads the directory sector for the current drive into the working buffer and validates the entry, returning NZ on error.
4E93
POP BC C1
Restore Register Pair BC (the requested record count) from the stack saved at 4E88H.
4E94
RET NZ C0
If the NZ FLAG is set (the directory read helper returned an error), return immediately to propagate the error.
4E95
Jump back to 4E4CH (the start of the Directory Entry Scan and Extent Allocator). After having read the directory sector and validated the entry, restart the full allocation scan from the beginning.

4E97H - Directory Sector Read Helper

Reads the directory sector for the current drive via 5040H and validates the directory entry via 4B10H. Returns NZ on error.

4E97
PUSH BC C5
Save Register Pair BC (the requested record count) onto the stack. BC must be preserved across the calls to 5040H and 4B10H.
4E98
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from offset +06H of the drive parameter block (IX). C = drive number, required by both the sector read stub at 5040H and the directory entry validation at 4B10H.
4E9B
GOSUB to the Read Sector Stub (Data Area) at 5040H. This calls 4B65H to set up the track/sector, then calls 4B45H to read the sector for drive C into the buffer at 5100H. Returns Z on success, NZ with A=14H on error.
4E9E
POP BC C1
Restore Register Pair BC from the stack.
4E9F
RET NZ C0
If the NZ FLAG is set (the sector read returned an error), return immediately with NZ to signal the error to the caller.
4EA0
PUSH HL E5
Save Register Pair HL onto the stack. HL holds the sector buffer pointer or accumulator returned by 5040H; it must be preserved while BC is reloaded for the 4B10H call.
4EA1
LD H,B 60
Load Register H with Register B (high byte of the requested record count in BC). Together with 4EA2H this copies BC into HL for the 16-bit arithmetic that follows.
4EA2
LD L,C 69
Load Register L with Register C (low byte of BC). HL now holds the requested record count.
4EA3
XOR A,A AF
Clear Register A and Carry flag in preparation for the 16-bit subtraction SBC HL,DE.
4EA4
SBC HL,DE ED 52
Subtract DE (the running accumulator - granules already accounted for) from HL (the requested record count). The result in HL is the remaining unfilled demand.
4EA6
LD B,H 44
Load Register B with Register H (high byte of the remaining demand). BC will carry the remaining demand forward to the next call.
4EA7
LD C,L 4D
Load Register C with Register L (low byte of the remaining demand). BC = remaining unfilled granule/record count.
4EA8
INC BC 03
Increment BC by 1. This adjusts the remaining demand by 1 to compensate for the zero-based encoding used in the extent count field (count stored as actual-1).
4EA9
POP DE D1
Restore the HL value saved at 4EA0H into DE. DE now holds the sector buffer pointer or accumulator from the 5040H call.
4EAA
INC DE 13
Increment DE by 1 to advance the sector buffer pointer to the next position.
4EAB
PUSH BC C5
Save Register Pair BC (the remaining unfilled demand, adjusted at 4EA8H) onto the stack. BC must be preserved across the upcoming calls to 4797H and 4B10H.
4EAC
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from offset +06H of the drive parameter block (IX). C = drive number, required by 4797H and 4B10H.
4EAF
LD A,06H 3E 06
Load Register A with 06H. This is the directory sector info byte passed to 4797H, selecting directory sector 6 (the GAT/directory sector index) for the current drive.
4EB1
GOSUB to SYS0 routine at 4797H: Read Directory Sector by Info Byte. Entry: A = sector info byte, C = drive number. Reads the specified directory sector into the buffer and returns A = sector data byte. On return A holds the GAT byte read from the directory sector.
4EB4
INC A 3C
Increment Register A by 1. The GAT byte returned by 4797H is tested: if it was 0FFH (empty/unallocated), incrementing it produces 00H and sets the Z flag. Any other value produces a non-zero result.
4EB5
POP BC C1
Restore Register Pair BC (the remaining demand) from the stack saved at 4EABH.
4EB6
CP A,0CBH FE CB
Compare Register A against 0CBH. After the INC A at 4EB4H, A holds (GAT byte + 1). 0CBH is the upper limit for valid granule counts in this context. If A >= 0CBH the drive is nearly or completely full.
4EB8
If the CARRY FLAG is set (A < 0CBH, the granule count is within valid range), jump to 4EBCH skipping the clamp instruction.
4EBA
LD A,0CBH 3E CB
Clamp Register A to 0CBH. If the GAT byte indicated a count at or beyond the limit, force A to the maximum valid value 0CBH to prevent overrun.
4EBC
LD L,A 6F
Load Register L with Register A (the clamped or valid granule count + 1). L will be used as the upper bound for the granule scan loop that follows.
4EBD
LD A,(IX+07H) DD 7E 07
Load Register A with the byte at offset +07H in the drive parameter block (IX). Offset +07H holds the sectors-per-track and interleave encoding for the drive. The lower 5 bits (masked at 4EF6H) give the maximum granule count for the drive.
4EC0
CP A,L BD
Compare Register A (sectors/track byte from drive parameter) against Register L (the clamped GAT-derived count). If A >= L the drive's physical limit is not exceeded by the GAT count.
4EC1
If the CARRY FLAG is set (A < L, the sectors/track byte is smaller than the GAT count), jump to 4EC7H to use A as the limiting value after halving it.
4EC3
SRL A CB 3F
Shift Register A right logically by 1 bit (divide by 2). The sectors-per-track byte encodes the count in the upper bits; shifting right extracts the usable granule limit.
4EC5
Loop back to 4EC0H to re-compare the halved value against L. This loop repeatedly halves A until A >= L, converging on the correct granule limit.
4EC7
LD H,51H 26 51
Load Register H with 51H. Combined with L (which will be set to the starting granule offset), HL will address into the GAT sector buffer at 5100H-51FFH. Setting H=51H points the high byte of HL into the 5100H page.
4EC9
PUSH BC C5
Save Register Pair BC (the remaining demand) onto the stack. BC must be preserved while the GAT bitmap scan executes.
4ECA
LD A,E 7B
Load Register A with Register E (the low byte of DE, the sector buffer pointer / accumulated count from 4EAAH). E holds the current scan position or byte offset into the GAT buffer.
4ECB
AND A,1EH E6 1E
Mask Register A with 1EH (binary 00011110), extracting bits 4-1. These bits encode the granule byte offset within the GAT buffer (each byte of the GAT bitmap covers 8 granules; bits 4-1 give the byte index x2, i.e., the even-byte position).
4ECD
CP A,16H FE 16
Compare the masked offset against 16H (decimal 22). If the current scan position is at or past offset 16H within the GAT byte group, the extent boundary has been reached and the routine must jump to start a new extent group.
4ECF
If the Z FLAG is set (offset equals 16H exactly, boundary reached), jump to 4F24H to read a new directory sector and restart the extent scan at the next directory position.
4ED1
DEC E 1D
Decrement Register E by 1 to step back one position in the extent chain pointer.
4ED2
DEC E 1D
Decrement Register E by 1 again. After two decrements, E points to the first byte of the previous extent record (two bytes back from the current scan position).
4ED3
LD A,(DE) 1A
Fetch the first byte of the previous extent record (pointed to by DE after the two DEC E operations) into Register A. This is the starting granule byte of the extent currently being filled.
4ED4
AND A,1FH E6 1F
Mask Register A with 1FH, extracting bits 4-0: the raw starting granule number (0-based index) within the GAT bitmap. Bits 7-5 (drive/sector flags) are discarded.
4ED6
INC A 3C
Increment Register A by 1. The starting granule is stored as (actual - 1) in the directory; adding 1 recovers the true starting granule index for use as an offset into the GAT buffer at 5100H.
4ED7
LD C,A 4F
Load Register C with the true starting granule index (Register A). C will be used as the loop counter / current granule pointer in the GAT scan loop below.
4ED8
CP A,20H FE 20
Compare the starting granule index against 20H (decimal 32). If the starting granule is 20H or beyond, this extent group is fully committed; jump to 4F0DH to advance to the next extent pair.
4EDA
If the Z FLAG is set (starting granule index == 20H), jump to 4F0DH to advance the extent pointer to the next pair.
4EDC
LD A,(DE) 1A
Re-fetch the first byte of the current extent record from (DE) into Register A. This re-reads the starting granule / flags byte to extract the drive/sector bits in bits 7-5 separately from the granule index bits.
4EDD
AND A,0E0H E6 E0
Mask Register A with 0E0H (binary 11100000), extracting bits 7-5: the drive/sector flags portion of the extent's first byte.
4EDF
RLCA 07
Rotate Register A Left Circular by 1 bit. Bit 7 rotates into bit 0 and into the Carry flag. This begins shifting the 3-bit drive/sector field from bits 7-5 toward the low bits.
4EE0
RLCA 07
Rotate Register A Left Circular by 1 bit again (second rotation).
4EE1
RLCA 07
Rotate Register A Left Circular by 1 bit a third time. After three RLCA operations, the original bits 7-5 have been rotated to bits 2-0. A now holds the 3-bit drive/sector field in bits 2-0 with the upper bits zeroed by the earlier AND A,0E0H.
4EE2
ADD A,C 81
Add Register C (the true starting granule index) to Register A (the 3-bit drive/sector field now in bits 2-0). This combines the granule index with the sector group offset to form the full GAT byte offset for this extent.
4EE3
PUSH DE D5
Save Register Pair DE (the extent record pointer) onto the stack. DE must be preserved while the GAT sector for this drive/sector group is read.
4EE4
LD E,A 5F
Load Register E with Register A (the computed GAT byte offset). DE is now used as the sector info byte for the 4797H call: E = GAT byte offset / sector index.
4EE5
LD A,08H 3E 08
Load Register A with 08H. This is the directory sector info byte passed to 4797H, selecting directory sector 8 (the main GAT sector page) for the current drive.
4EE7
PUSH BC C5
Save Register Pair BC (the remaining demand and starting granule) onto the stack.
4EE8
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from offset +06H of the drive parameter block (IX).
4EEB
GOSUB to SYS0 routine at 4797H: Read Directory Sector by Info Byte. Entry: A = 08H (GAT sector), C = drive number, E = granule byte offset. Returns A = the GAT byte value at the specified offset for the current drive.
4EEE
POP BC C1
Restore Register Pair BC from the stack (the remaining demand and granule pointer).
4EEF
RLCA 07
Rotate Register A (the GAT byte returned by 4797H) Left Circular by 1 bit. This begins extracting the granule-count field from the GAT byte.
4EF0
RLCA 07
Rotate Register A Left Circular by 1 bit (second rotation).
4EF1
RLCA 07
Rotate Register A Left Circular by 1 bit (third rotation). After three RLCAs, the 3-bit granule-count field originally in bits 7-5 of the GAT byte is now in bits 2-0 of A.
4EF2
AND A,07H E6 07
Mask Register A with 07H (binary 00000111), isolating the 3-bit granule-count field in bits 2-0. Bits 7-3 are cleared.
4EF4
INC A 3C
Increment Register A by 1. The GAT granule-count is stored as (actual - 1); adding 1 recovers the true count.
4EF5
GOSUB to SYS0 routine at 4B7BH: Multiply A by Sectors-Per-Granule. Entry: A = granule count. Returns A = total sector count for those granules, computed using the drive's sectors-per-granule value from the parameter block.
4EF8
LD B,A 47
Load Register B with Register A (the total sector count for the granules in this extent group). B will be used as the byte count for the subsequent GAT bitmap scan loop.
4EF9
LD C,E 4B
Load Register C with Register E (the low byte of DE, which holds the GAT byte offset / sector index). C now holds the current position within the GAT scan.
4EFA
POP DE D1
Restore Register Pair DE from the stack (the extent record pointer saved at 4EE3H).
4EFB
DEC DE 1B
Decrement DE by 1, stepping back one byte in the extent record pointer to point to the first byte of the current extent (the starting granule byte).
4EFC
LD A,(DE) 1A
Fetch the first byte of the current extent record (the starting granule / flags byte) from (DE) into Register A.
4EFD
INC DE 13
Increment DE back by 1 to restore it to the second byte of the current extent record (the granule count byte), ready for any subsequent write-back.
4EFE
ADD A,B 80
Add Register B (the total sector count for this group's granules) to Register A (the starting granule byte from the extent record). This combines the starting position with the granule span to compute the GAT byte address for the end of this extent group.
4EFF
LD L,A 6F
Load Register L with Register A (the computed GAT end-byte address). With H already set to 51H (at 4EC7H), HL now points into the GAT buffer at 5100H + A, addressing the byte in the GAT bitmap corresponding to the end of this extent.
4F00
LD H,51H 26 51
Reload Register H with 51H, confirming the high byte of the GAT buffer address. HL = 5100H + L, pointing into the GAT sector buffer.
4F02
CP A,0CBH FE CB
Compare Register A (the end-byte address just computed) against 0CBH. If A >= 0CBH, the computed address would overrun the valid portion of the GAT buffer; the extent cannot be extended further.
4F04
If the NO CARRY FLAG is set (A >= 0CBH, overrun detected), jump to 4F0DH to advance to the next extent pair without scanning this one.
4F06
LD A,C 79
Load Register A with Register C (the current GAT scan position, set at 4EF9H). A is the granule index being examined in the GAT bitmap.
4F07
LD B,(HL) 46
Load Register B with the byte at (HL): the GAT bitmap byte at the address corresponding to the current granule. Each bit in this byte represents one granule; a zero bit means the granule is free.
4F08
GOSUB to the GAT Bit Set Encoder at 5027H. Entry: A = granule index. This routine encodes the SET-bit instruction operand into 5032H and tests whether the specified bit in B (the GAT bitmap byte) is set (granule in use) or clear (granule free). Returns Z set if the granule is free.
4F0B
If the Z FLAG is set (the granule is free), jump to 4F66H to allocate it: mark it used in the GAT bitmap and update the directory entry.
4F0D
INC E 1C
Increment Register E by 1, advancing the extent chain pointer by one byte (to the second byte of the current extent pair, or to the first byte of the next).
4F0E
INC E 1C
Increment Register E by 1 again. After two INC E operations, E has advanced by 2 bytes, stepping over the full 2-byte extent pair to point at the first byte of the next extent record.
4F0F
LD A,E 7B
Load Register A with Register E (the updated extent chain pointer byte).
4F10
AND A,1EH E6 1E
Mask Register A with 1EH, extracting bits 4-1: the byte-aligned offset within the current extent group. This checks whether the pointer has reached the boundary of a 16-byte (0EH) extent group block.
4F12
CP A,1EH FE 1E
Compare the masked offset against 1EH (decimal 30). If the pointer has reached offset 1EH within the extent group, all 15 extent pairs in the current directory sector have been examined.
4F14
If the NZ FLAG is set (offset is not 1EH, still within the current extent group), jump to 4F24H to read the next directory sector and continue the scan.

When the offset equals 1EH, all extent pairs in this directory sector are exhausted. The following code calls 4F80H to write the current sector back to disk, then either returns an error or reads the next directory sector and loops.

4F16
GOSUB to the Directory Sector Write (Data Area) routine at 4F80H. This writes the updated directory sector back to disk and calls 4B1FH to update the directory entry. Returns Z on success, NZ on error.
4F19
POP BC C1
Restore Register Pair BC (the remaining demand) from the stack saved at 4EC9H.
4F1A
RET NZ C0
If the NZ FLAG is set (the write returned an error), return immediately to propagate the error.
4F1B
PUSH BC C5
Re-save Register Pair BC (the remaining demand) onto the stack for the next pass through the loop.
4F1C
GOSUB to the Directory Sector Write (Directory Area) routine at 4F8CH. This reads the next directory sector, constructs the extent encoding, and writes it back. Returns Z on success, NZ on error.
4F1F
POP BC C1
Restore Register Pair BC (the remaining demand) from the stack.
4F20
RET NZ C0
If the NZ FLAG is set (the directory write returned an error), return immediately.
4F21
Jump to 4E4CH to restart the full Directory Entry Scan and Extent Allocator. After writing out the current directory sector and advancing to the next, the entire allocation pass is repeated from the beginning for the remaining demand.
4F24
LD A,06H 3E 06
Load Register A with 06H. This is the directory sector info byte for 4797H, selecting directory sector 6 for the next read pass.
4F26
PUSH BC C5
Save Register Pair BC (the remaining demand) onto the stack.
4F27
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from offset +06H of the drive parameter block (IX).
4F2A
GOSUB to SYS0 routine at 4797H: Read Directory Sector by Info Byte. Entry: A = 06H, C = drive number. Reads the directory sector and returns A = the data byte from the directory sector at the specified offset.
4F2D
INC A 3C
Increment Register A by 1. Tests whether the returned byte was 0FFH (unused sentinel): if so, A becomes 00H and Z is set.
4F2E
POP BC C1
Restore Register Pair BC (the remaining demand) from the stack.
4F2F
CP A,0CBH FE CB
Compare Register A (directory data byte + 1) against 0CBH. Clamps the value to the valid range as before.
4F31
If the CARRY FLAG is set (A < 0CBH, value is within valid range), jump to 4F35H skipping the clamp instruction.
4F33
LD A,0CBH 3E CB
Clamp Register A to 0CBH. The directory byte indicated a count at or beyond the valid limit; force A to the maximum valid value to prevent overrun.
4F35
LD (4F3CH),A 32 3C 4F
Self-Modifying Code
Store Register A (the clamped upper-bound value) into address 4F3CH. This address is the operand byte of the CP A,00H instruction at 4F3BH-4F3CH. At runtime that CP will compare the loop's granule scan index against this dynamically set upper limit, terminating the scan when the index reaches the bound.
4F38
LD B,02H 06 02
Load Register B with 02H. This initialises a loop counter of 2 for the granule scan sub-loop that follows. The loop walks up to 2 bytes of the GAT bitmap looking for a free granule slot.
4F3A
LD A,L 7D
Load Register A with Register L (the current GAT byte address low byte, i.e. the offset into the 5100H buffer). L tracks the current scan position within the GAT bitmap.

GAT Bitmap Scan Inner Loop
The loop at 4F3BH-4F48H scans the GAT buffer at 5100H for a free granule. Each iteration checks whether the current granule byte (HL) contains an available slot by comparing L against the self-modified upper bound. If L is within range and the entry at (HL) is 0FFH (used), the loop advances to the next GAT byte.

4F3B
CP A,00H FE 00
Self-Modifying Code Target
Compare Register A (the current scan offset in L) against the operand at 4F3CH. The operand byte (initially 00H, written at runtime by the store at 4F35H) is the upper limit for the GAT scan. If A equals this limit, the Z flag is set and the scan is complete.
4F3D
If the NO CARRY FLAG is set (A >= the upper limit, scan has reached or passed the end of the valid GAT range), jump to 4F46H to reset the scan position to zero and continue with the outer loop.
4F3F
LD A,(HL) 7E
Fetch the GAT bitmap byte at the current scan position (HL) into Register A. Each bit in this byte represents one granule on the disk: 0 = free, 1 = allocated.
4F40
INC A 3C
Increment Register A by 1. If the byte was 0FFH (all 8 granules in this byte are allocated), the result is 00H and Z is set. If any granule is free, the result is non-zero.
4F41
If the NZ FLAG is set (at least one granule in this GAT byte is free), jump to 4F53H to process this byte and allocate a free granule from it.
4F43
INC L 2C
Increment Register L by 1, advancing HL to the next byte in the GAT buffer at 5100H. Each byte covers 8 granules; incrementing L moves to the next group of 8.
4F44
Loop Back
Jump back to 4F3AH to reload A from L and re-test the upper bound. This continues the scan through the GAT buffer until a free granule is found or the limit is reached.
4F46
LD L,00H 2E 00
Load Register L with 00H, resetting the GAT buffer scan offset to the beginning of the 5100H page. When the scan has reached the upper limit without finding a free granule in the current pass, it wraps back to the start of the GAT bitmap.
4F48
Decrement Register B and jump to 4F3BH if B is not zero. B was initialised to 02H at 4F38H, so the outer scan is attempted at most twice: once from the current position to the limit, and once from position zero to the limit. If both passes find no free granule, B reaches zero and execution falls through.
4F4A
POP BC C1
Restore Register Pair BC (the remaining demand) from the stack saved at 4EC9H. The GAT scan has exhausted all available granules without finding a free one.
4F4B
GOSUB to the Directory Sector Write (Data Area) routine at 4F80H. Even though no free granule was found, the current directory sector state is written back to preserve any partial updates made during this pass.
4F4E
RET NZ C0
If the NZ FLAG is set (the sector write returned an error), return immediately to propagate the error.
4F4F
LD A,1BH 3E 1B
Load Register A with 1BH. This is the VTOS error code for "Disk Full" or "No Free Granules Available", indicating that the allocation request could not be satisfied because the disk has no free granules.
4F51
OR A,A B7
OR Register A with itself. This sets the S and NZ flags based on the value in A (1BH is non-zero), ensuring NZ is set for the return to signal the error condition to the caller.
4F52
RET C9
Return to the caller with A = 1BH (Disk Full error code) and NZ set.

Free Granule Found - Allocate
A GAT bitmap byte with at least one free granule was found at 4F41H. The following code at 4F53H walks the 8 bits of that byte to find the specific free bit, marks it allocated, and updates the directory entry extent chain.

4F53
LD A,0FFH 3E FF
Load Register A with 0FFH. This value is written into the extent chain to mark the granule as the last entry in the chain (the sentinel for "end of allocated extents"). It is placed at the start of the allocation process before the actual granule index is filled in.
4F55
LD (DE),A 12
Store 0FFH into the memory location pointed to by Register Pair DE (the current extent chain pointer in the directory entry). This pre-marks the extent slot as the end-of-chain sentinel before the granule details are written.
4F56
LD C,00H 0E 00
Load Register C with 00H. C is used as the bit-scan counter within the current GAT byte: it counts which bit position (0-7) within the byte corresponds to a free granule. Initialised to 0 (bit 0) to begin scanning from the least-significant bit.
4F58
LD B,(HL) 46
Load Register B with the GAT bitmap byte at (HL): the byte identified at 4F3FH as containing at least one free granule. B is the bitmask being scanned bit by bit.
4F59
LD A,C 79
Load Register A with Register C (the current bit-scan position, 0-7).

Bit Scan Loop
The following loop (4F5AH-4F64H) scans bits 0-7 of the GAT byte in B to find the first zero bit (free granule). C counts the bit position; 5027H encodes the SET instruction for that position and tests the bit in B.

4F5A
GOSUB to the GAT Bit Set Encoder at 5027H. Entry: A = bit position (0-7), B = GAT bitmap byte. Encodes the SET-bit instruction operand into 5032H and tests whether bit A of B is set (granule in use). Returns Z set if the bit is clear (granule is free).
4F5D
If the Z FLAG is set (this bit is clear, the granule is free), jump to 4F66H to allocate this specific granule.
4F5F
LD A,(DE) 1A
Fetch the byte at (DE) (the current extent chain position) into Register A. This re-reads the extent byte to verify the pre-written sentinel is still in place before advancing.
4F60
ADD A,20H C6 20
Add 20H to Register A. Within the extent byte encoding, adding 20H advances the granule group index by one unit (each group of 8 granules is represented by a 20H increment in the extent byte). This steps the extent pointer to the next group of 8 within the same GAT byte.
4F62
LD (DE),A 12
Store the updated extent byte back into (DE). The extent chain entry is updated to reflect the new granule group position.
4F63
INC C 0C
Increment Register C by 1, advancing the bit-scan counter to the next bit position (next granule within the current GAT byte).
4F64
Loop Back
Jump back to 4F59H to load A from C and test the next bit. The loop continues until a free bit (zero bit) is found in the GAT bitmap byte.

Granule Allocation - Write GAT and Directory
A free granule (bit position in C, byte at HL) has been identified. The following code at 4F66H marks the granule as allocated in the GAT bitmap, updates the directory extent chain, then writes both the GAT sector and the directory sector back to disk.

4F66
LD A,C 79
Load Register A with Register C (the bit position of the free granule, 0-7, within the current GAT bitmap byte).
4F67
GOSUB to the GAT Bit Clear Encoder at 5034H. Entry: A = bit position (0-7). This routine encodes the RES-bit instruction operand into 503EH, which when executed will clear the identified bit in the GAT bitmap byte at (HL), marking the granule as allocated.
4F6A
OR A,(HL) B6
OR Register A with the GAT bitmap byte at (HL). A already holds the encoded granule byte (from 5034H); ORing with the existing bitmap byte merges the new allocation into the existing state.
4F6B
LD (HL),A 77
Store the updated bitmap byte back into (HL) in the GAT buffer at 5100H. The granule is now marked as allocated in the in-memory GAT bitmap.
4F6C
DEC E 1D
Decrement Register E by 1, stepping DE back one byte to point to the first byte of the current extent record in the directory entry.
4F6D
LD A,(DE) 1A
Fetch the first byte of the current extent record from (DE) into Register A. This is the starting granule / flags byte that was written earlier; it will be used to construct the final extent encoding.
4F6E
INC A 3C
Increment Register A by 1. Tests whether the extent's first byte is 0FFH (the end-of-chain sentinel written at 4F55H): if so, A becomes 00H and Z is set, meaning this is a brand-new extent slot that needs its starting granule filled in.
4F6F
If the NZ FLAG is set (the first byte is not 0FFH, this extent slot already has a valid starting granule encoded), jump to 4F73H to skip writing the starting granule and go directly to updating the second byte.
4F71
LD A,L 7D
Load Register A with Register L (the low byte of HL, which is the offset into the GAT buffer 5100H and thus the GAT byte index for the newly allocated granule). This value encodes the starting granule's position for the extent's first byte.
4F72
LD (DE),A 12
Store Register A (the starting granule index from L) into (DE), writing the first byte of the new extent record. The extent now has its starting granule correctly encoded.
4F73
INC E 1C
Increment Register E by 1, advancing DE to point to the second byte of the current extent record (the granule count byte).
4F74
LD A,(DE) 1A
Fetch the second byte of the current extent record (the existing granule count byte) from (DE) into Register A.
4F75
INC A 3C
Increment Register A by 1, adding one to the granule count for this extent. The count is stored as (actual - 1) in the directory; incrementing the stored value records the addition of one more granule.
4F76
LD (DE),A 12
Store the updated granule count back into (DE), writing the second byte of the extent record with the incremented count. The directory entry extent now reflects one additional allocated granule.
4F77
POP BC C1
Restore Register Pair BC (the remaining demand) from the stack saved at 4EC9H.
4F78
DEC BC 0B
Decrement BC by 1, reducing the remaining unfilled demand by 1 to account for the granule just allocated.
4F79
PUSH BC C5
Save the updated remaining demand (BC) back onto the stack.
4F7A
LD A,B 78
Load Register A with Register B (high byte of the remaining demand).
4F7B
OR A,C B1
OR Register A with Register C (low byte of the remaining demand). If BC is zero (all demand satisfied), the result is zero and Z is set.
4F7C
If the NZ FLAG is set (BC is non-zero, more granules are still needed), jump back to 4ED3H to continue the GAT bitmap scan and allocate another granule. This forms the main allocation loop.
4F7F
POP BC C1
Restore Register Pair BC from the stack (remaining demand is now zero). The full allocation request has been satisfied.

4F80H - Directory Sector Write (Data Area)

Writes the updated directory data sector back to disk. Sets B=00H, then calls 4B1FH to update the directory entry. Called when an extent has been fully allocated and the directory must be committed.

4F80
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from offset +06H of the drive parameter block (IX). C = drive number, required by the sector write stub at 5053H.
4F83
GOSUB to the Write Sector Stub (Data Area) at 5053H. Calls 4B65H to set up the track/sector, then calls 4768H to write the sector. Optionally verifies via 4772H. Returns A = 15H; Z set on success, NZ on error.
4F86
RET NZ C0
If the NZ FLAG is set (the sector write returned an error), return immediately to propagate the error to the caller.
4F87
LD B,00H 06 00
Load Register B with 00H. B = 00H is the mode parameter required by the 4B1FH directory entry write routine, selecting the standard (non-extended) write mode.
4F89
Jump to SYS0 routine at 4B1FH: Directory Entry Write/Update. Entry: B = 00H (standard mode), IX = drive parameter block. This routine writes the updated directory entry to the directory sector. Returns Z on success, NZ on error. Using JP rather than CALL means 4B1FH's RET returns directly to 4F80H's caller.

4F8CH - Directory Sector Write (Directory Area)

Reads the directory sector via 506CH, computes the sector count for the current extent via 4FE8H, constructs the two-byte extent encoding, and writes the directory sector back via 507EH.

4F8C
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from offset +06H of the drive parameter block (IX). C = drive number for the directory sector read and write stubs.
4F8F
GOSUB to the Read Directory Sector Stub at 506CH. Calls 4B65H to set up the track/sector, then reads the directory sector at E=01H into the buffer at 4200H via 4B45H. Returns Z on success, A=16H on error.
4F92
RET NZ C0
If the NZ FLAG is set (the directory sector read returned an error), return immediately.
4F93
LD A,(IX+07H) DD 7E 07
Load Register A with the byte at offset +07H of the drive parameter block (IX): the sectors-per-track and interleave encoding for the drive. The lower 5 bits (masked next) give the maximum granule count.
4F96
AND A,1FH E6 1F
Mask Register A with 1FH (binary 00011111), extracting the 5-bit granule count field from the sectors-per-track byte. This gives the total number of granules on the drive.
4F98
GOSUB to the Sector Count Calculator at 4FE8H. Entry: A = granule count. Reads the GAT sector info via 4797H, then calls 4B6CH (24-bit multiply: HL:A = DE x C) to convert the granule count to a total sector count. Returns the result for use in constructing the extent byte.
4F9B
LD A,1EH 3E 1E
Load Register A with 1EH. This is a threshold value: if the computed sector count returned by 4FE8H is non-zero (NZ), the extent is too large to fit in the current directory page and the routine returns an error code.
4F9D
RET NZ C0
If the NZ FLAG is set (4FE8H indicated an overflow or error condition), return immediately with A = 1EH as the error code (Extent Overflow).
4F9E
LD B,L 45
Load Register B with Register L (the low byte of the sector count result from 4FE8H). B holds the computed sector count for this extent group, to be encoded into the directory entry.
4F9F
LD A,L 7D
Load Register A with Register L (same sector count value). A will be used for the self-modifying store below and subsequent extent byte construction.
4FA0
LD (4FE0H),A 32 E0 4F
Self-Modifying Code
Store Register A (the starting granule index / sector count from L) into address 4FE0H. This address is the operand byte of a LD A,nn instruction at 4FDFH-4FE0H later in this routine. At runtime that LD A will reload this value to construct the final extent first byte.
4FA3
LD D,H 54
Load Register D with Register H (the high byte of the HL result from 4FE8H). DE is being constructed to hold the 16-bit extent descriptor: D = high byte (sector count high), E = low byte (sector index).
4FA4
LD E,(IX+07H) DD 5E 07
Load Register E with the byte at offset +07H of the drive parameter block (IX): the directory sector info byte. This forms the low byte of the extent's second-byte encoding (the sector count / granule count field).
4FA7
LD A,(DE) 1A
Fetch the byte at (DE) from the directory sector buffer into Register A. DE now points into the 4200H buffer at the position corresponding to the extent being updated. This reads the existing extent second byte to merge with the new data.
4FA8
LD (HL),A 77
Store Register A (the existing extent byte read from DE) into (HL). HL points to the directory sector buffer position where the updated extent byte will be written, transferring the existing value as the base for the update.
4FA9
GOSUB to the Write Directory Sector Stub at 507EH. Calls 4B65H to set up the track/sector, then calls 4768H to write the directory sector from the buffer at 4200H back to disk. Optionally verifies. Returns A=17H; Z on success, NZ on error.
4FAC
RET NZ C0
If the NZ FLAG is set (the directory sector write returned an error), return immediately to propagate the error to the caller.
4FAD
GOSUB to SYS0 routine at 4B10H: Directory Entry Validation. Entry: C = drive number (set at 4F8CH), IX = drive parameter block. Validates the directory entry for the current file and returns HL pointing to the matched entry. Returns Z on success, NZ on error.
4FB0
RET NZ C0
If the NZ FLAG is set (directory entry validation failed), return immediately.
4FB1
LD (HL),90H 36 90
Store 90H into the memory location pointed to by HL (the first byte of the matched directory entry). 90H marks this entry as an active, allocated directory entry in the VTOS directory format (bit 7 set = entry in use, bits 4-0 = entry type 10H).
4FB3
INC L 2C
Increment Register L by 1, advancing HL to offset +01H within the directory entry (the flags/attribute byte).
4FB4
PUSH BC C5
Save Register Pair BC (the remaining demand) onto the stack. BC must be preserved while the directory entry is being constructed.
4FB5
LD A,(4F88H) 3A 88 4F
Self-Modifying Code Target
Load Register A with the byte at address 4F88H. This address holds the directory info byte saved at 4E54H by the store LD (4F88H),A. At runtime A receives the directory track/sector info byte for the current drive, which is written into the directory entry at offset +01H.
4FB8
LD (HL),A 77
Store Register A (the directory info byte) into (HL), writing the flags/attribute byte at offset +01H of the directory entry.
4FB9
INC L 2C
Increment Register L by 1, advancing HL to offset +02H within the directory entry (the date byte field).
4FBA
LD B,14H 06 14
Load Register B with 14H (decimal 20). This initialises a loop counter of 20, used to zero-fill 20 bytes of the directory entry starting at offset +02H, clearing the date, filename, extension, and extent chain fields to zero before the new extent data is written in.

Directory Entry Clear Loop
The following loop (4FBCH-4FBFH) fills 20 bytes of the directory entry with 00H, clearing fields +02H through +15H (date, reserved, filename, extension, and the start of the extent area).

4FBC
LD (HL),00H 36 00
Store 00H into the memory location at (HL), zeroing the current byte of the directory entry.
4FBE
INC L 2C
Increment Register L by 1, advancing HL to the next byte of the directory entry.
4FBF
Decrement Register B and jump to 4FBCH if B is not zero. Repeats the zero-fill for all 20 bytes (B started at 14H = 20). After this loop, HL points to offset +16H within the directory entry, the start of the extent chain data area.
4FC1
PUSH HL E5
Save Register Pair HL (pointing to the start of the extent chain area at offset +16H in the directory entry) onto the stack. HL is preserved while the next 10 bytes are filled with 0FFH sentinels.
4FC2
LD B,0AH 06 0A
Load Register B with 0AH (decimal 10). This initialises a loop counter of 10 for filling 10 bytes of the extent chain area (5 extent slots x 2 bytes each) with 0FFH end-of-chain sentinels.

Extent Chain Sentinel Fill Loop
The following loop (4FC4H-4FC7H) fills 10 bytes of the extent chain area with 0FFH, pre-marking all 5 extent slots as empty (end-of-chain).

4FC4
LD (HL),0FFH 36 FF
Store 0FFH into the memory location at (HL), marking this extent slot byte as an unused end-of-chain sentinel.
4FC6
INC L 2C
Increment Register L by 1, advancing HL to the next byte in the extent chain area.
4FC7
Decrement Register B and jump to 4FC4H if B is not zero. Repeats the 0FFH fill for all 10 bytes (B started at 0AH = 10). After this loop all 5 extent slots in the new directory entry are pre-filled with end-of-chain sentinels.
4FC9
POP DE D1
Restore the HL value saved at 4FC1H into Register Pair DE. DE now points to offset +16H (the start of the extent chain area) in the directory entry, where the first real extent record will be written.
4FCA
INC DE 13
Increment DE by 1, advancing to offset +17H: the second byte of the first extent slot. The first byte (the starting granule) will be written by the code below; DE is pre-positioned at the count byte.
4FCB
POP BC C1
Restore Register Pair BC (the remaining demand) from the stack saved at 4FB4H.
4FCC
GOSUB to SYS0 routine at 4B1FH: Directory Entry Write/Update. Entry: B = remaining demand high byte, IX = drive parameter block. Writes the constructed directory entry (with the zero-filled fields and sentinel extent chain) to the directory sector on disk. Returns Z on success, NZ on error.
4FCF
RET NZ C0
If the NZ FLAG is set (the directory entry write returned an error), return immediately.
4FD0
LD A,(4F88H) 3A 88 4F
Self-Modifying Code Target
Load Register A with the byte at address 4F88H (the directory info byte stored at 4E54H). This re-reads the saved directory track/sector info byte for use in the second directory sector write that follows.
4FD3
LD B,A 47
Load Register B with Register A (the directory info byte). B now holds the sector selector value needed by 4B10H for the second pass directory entry validation.
4FD4
GOSUB to SYS0 routine at 4B10H: Directory Entry Validation (second pass). This re-validates the directory entry after the first write to confirm the entry is still consistent. Returns HL pointing to the matched entry, Z on success, NZ on error.
4FD7
RET NZ C0
If the NZ FLAG is set (second validation failed), return immediately.
4FD8
LD A,L 7D
Load Register A with Register L (low byte of HL, the offset within the directory page of the matched entry). L will be used to compute the address of the extent chain area within this entry.
4FD9
ADD A,1EH C6 1E
Add 1EH (decimal 30) to Register A. Within the 32-byte directory entry, offset +1EH is the last 2 bytes of the extent chain (the final extent slot). This computes the address of the end of the extent chain for this entry.
4FDB
LD L,A 6F
Load Register L with Register A (the computed extent chain end offset). HL now points to the final extent slot (offset +1EH) within the directory entry in the buffer.
4FDC
LD (HL),0FEH 36 FE
Store 0FEH into the memory location at (HL) (offset +1EH of the directory entry, the first byte of the last extent slot). 0FEH is the VTOS terminal extent sentinel marking the last allocated extent in the chain.
4FDE
INC L 2C
Increment Register L by 1, advancing HL to offset +1FH (the second byte of the final extent slot, the granule count byte).
4FDF
LD A,00H 3E 00
Self-Modifying Code Target
Load Register A with the byte at the operand position 4FE0H. The operand byte (initially 00H, written at runtime by the store at 4FA0H) holds the starting granule index computed earlier. A now contains the starting granule for this extent's second byte encoding.
4FE1
LD (HL),A 77
Store Register A (the starting granule index from the self-modifying operand) into (HL) at offset +1FH of the directory entry. This writes the granule count/starting position byte into the final extent slot, completing the extent chain construction.
4FE2
GOSUB to SYS0 routine at 4B1FH: Directory Entry Write/Update (second pass). Writes the finalised directory entry (with the terminal extent sentinel at offset +1EH and the starting granule at offset +1FH) to disk. Returns Z on success, NZ on error.
4FE4
RET C9
Return to the caller. Z is set if 4B1FH succeeded (Z propagated from 4B1FH's return), NZ on error. The directory sector write (directory area) is now complete.

4FE8H - Sector Count Calculator

Converts a granule count (in A) into a total sector count using the drive's GAT data and the 24-bit multiply routine at 4B6CH. Called by 4F8CH to determine how many sectors an extent of A granules occupies.

4FE8
LD A,(4040H) 3A 40 40
Load Register A with the byte at address 4040H: the Master Tick Counter (heartbeat, incremented by the interrupt service routine). This value is used as a pseudo-random seed or timing reference for the subsequent GAT sector read.
4FEB
PUSH AF F5
Save Register Pair AF (A = tick counter byte, F = current flags) onto the stack. AF must be preserved while the GAT sector read executes, as the tick value is needed after the call returns.
4FEC
LD A,07H 3E 07
Load Register A with 07H. This is the directory sector info byte passed to 4797H, selecting directory sector 7 (the GAT data sector) for reading. The GAT sector at index 7 contains the per-drive granule count and sectors-per-granule encoding used by the multiply.
4FEE
PUSH DE D5
Save Register Pair DE onto the stack. DE must be preserved across the 4797H call as it holds context for the calling routine (4F8CH).
4FEF
LD D,A 57
Load Register D with Register A (07H, the GAT sector info byte). D is used to hold the sector selector while C is loaded with the drive number next.
4FF0
AND A,1FH E6 1F
Mask Register A with 1FH, extracting bits 4-0 from the sector info byte (07H AND 1FH = 07H). This isolates the 5-bit granule index field from the sector selector byte.
4FF2
LD E,A 5F
Load Register E with Register A (the masked granule index, 07H). DE = D:E = 07H:07H, forming the 16-bit parameter for the 4B6CH multiply call: E = multiplier (granule count), D = sector selector.
4FF3
INC E 1C
Increment Register E by 1, adjusting the granule count from its stored (actual-1) form to the true count. E = 08H after the increment.
4FF4
XOR A,D AA
XOR Register A with Register D (07H). Since A = 07H and D = 07H, the result is 00H. This clears A to zero while simultaneously testing that A and D held the same value (sanity check: result is zero only if the sector selector and granule index match).
4FF5
RLCA 07
Rotate Register A (00H) Left Circular by 1 bit. A remains 00H. This begins extracting the drive number or density bit from the GAT sector byte returned by 4797H, rotating the relevant bits into position.
4FF6
RLCA 07
Rotate Register A Left Circular by 1 bit (second rotation). A remains 00H.
4FF7
RLCA 07
Rotate Register A Left Circular by 1 bit (third rotation). After three RLCAs the original bits 7-5 of the GAT sector byte are in bits 2-0. A = 00H in this path (since the XOR at 4FF4H zeroed A).
4FF8
INC A 3C
Increment Register A by 1. A = 01H. This produces the sectors-per-granule multiplier: 1 sector per granule as the base case (adjusted by the drive parameter data returned from 4797H in the full path).
4FF9
GOSUB to SYS0 routine at 4B6CH: 24-bit Multiply (HL:A = DE x C). Entry: DE = granule count parameters, C = sectors-per-granule multiplier (A = 01H used to set up C before the call, or C was loaded previously). Returns HL:A = the 24-bit product: the total sector count for the extent.
4FFC
POP DE D1
Restore Register Pair DE from the stack saved at 4FEEH.
4FFD
SUB A,02H D6 02
Subtract 02H from Register A (the low byte of the 24-bit multiply result). This adjusts the total sector count by subtracting 2, accounting for the 2 reserved sectors at the start of each granule group (the GAT and directory sectors themselves are not counted as data sectors).
4FFF
LD (5014H),A 32 14 50
Self-Modifying Code
Store Register A (the adjusted sector count) into address 5014H. This address is the operand byte of a CP A,00H instruction at 5013H-5014H inside the GAT bitmap scanner at 500BH. At runtime that CP compares the scanner's progress index against this dynamically computed sector count limit.
5002
POP AF F1
Restore Register Pair AF from the stack saved at 4FEBH. A recovers the Master Tick Counter byte read from 4040H; F recovers the flags saved at that point.
5003
LD L,A 6F
Load Register L with Register A (the tick counter byte recovered from the stack). L now holds the pseudo-random starting offset for the GAT scan, directing the scanner to begin at a position derived from the system heartbeat rather than always starting at byte 0.
5004
GOSUB to the GAT Bitmap Scanner at 500BH. Entry: L = starting scan offset (pseudo-random from tick counter), H = 51H (GAT buffer page), self-modifying limit at 5014H. Scans the GAT buffer at 5100H for a free granule. Returns Z set if a free granule was found at offset L.
5007
RET Z C8
If the Z FLAG is set (a free granule was found by the scanner), return immediately with Z set and L holding the offset of the free granule in the GAT buffer.
5008
LD L,3FH 2E 3F
Load Register L with 3FH (decimal 63). The initial scan (starting at the pseudo-random tick offset) found no free granule. Reset L to 3FH and fall through to 500AH to begin a second scan pass starting just before the midpoint of the GAT buffer.
500A
INC L 2C
Increment Register L by 1. L becomes 40H. This positions the second scan pass at byte 40H within the 5100H GAT buffer (the start of the upper half of the bitmap), providing a different starting point for the retry scan.

500BH - GAT Bitmap Scanner

Scans the GAT buffer at 5100H for a free granule, starting at offset L and wrapping around. The self-modifying byte at 5014H provides the upper limit. Returns Z set with L = offset of the free granule if found, NZ if no free granule exists within the limit.

500B
LD A,L 7D
Load Register A with Register L (the current scan offset within the 5100H GAT buffer). A is used to test the offset against the upper bound before reading the GAT byte.
500C
AND A,0D8H E6 D8
Mask Register A with 0D8H (binary 11011000). This tests whether bits 7, 6, 4, or 3 are set in the current offset. If any of these bits are set, the offset is outside the valid GAT data range (the GAT bitmap occupies only the lower portion of the 5100H page); a non-zero result causes a jump to 501AH.
500E
If the Z FLAG is set (the masked result is zero, meaning bits 7/6/4/3 are all clear and the offset is within the valid GAT range), jump to 501AH to advance the offset and continue scanning. If NZ, the offset is out of range and the routine falls through to the boundary handling below.
5010
LD A,L 7D
Reload Register A with Register L (the current scan offset, unmasked) for the range comparison that follows.
5011
AND A,1FH E6 1F
Mask Register A with 1FH, extracting the 5-bit lower field of the offset. This isolates the byte index within the current granule group (0-31).
5013
CP A,00H FE 00
Self-Modifying Code Target
Compare Register A (the 5-bit byte index) against the operand at 5014H. The operand byte (initially 00H, written at runtime by the store at 4FFFH with the adjusted sector count) is the upper limit for the valid GAT range. If A < this limit (Carry set), the byte is within range; if A >= the limit (No Carry), the byte is beyond the valid GAT data.
5015
If the NO CARRY FLAG is set (the 5-bit index is at or beyond the self-modifying limit), jump to 5024H to return NZ (no free granule found within the valid range).
5017
LD A,(HL) 7E
Fetch the GAT bitmap byte at (HL) (H=51H, L=current offset) from the GAT buffer at 5100H into Register A. This is the bitmap byte for the current group of 8 granules.
5018
OR A,A B7
OR Register A with itself to set flags based on the GAT bitmap byte value. If A = 00H (all 8 granules in this byte are free), the Z FLAG is set. If A is non-zero (at least one granule is allocated), NZ is set.
5019
RET Z C8
If the Z FLAG is set (the GAT byte is 00H, meaning all 8 granules in this group are free), return immediately with Z set and L holding the offset of this fully-free GAT byte. The caller can allocate any granule from this group.
501A
LD A,L 7D
Load Register A with Register L (the current scan offset). A is used to compute the next scan position by advancing L by 20H (one granule group = 32 bytes).
501B
ADD A,20H C6 20
Add 20H (decimal 32) to Register A. This advances the scan by one full granule group (32 bytes = 8 granules x 4 bytes per granule in the GAT layout). If the addition produces a carry (wraps past FFH), the scan has wrapped around the end of the GAT buffer.
501D
LD L,A 6F
Load Register L with Register A (the advanced scan offset). HL now points to the GAT byte for the next granule group.
501E
If the NO CARRY FLAG is set (the ADD A,20H did not carry, the scan is still within the 5100H page), jump back to 500BH to check the next granule group. This forms the main scan loop.
5020
CP A,1FH FE 1F
Compare Register A (the wrapped scan offset) against 1FH. After wrapping (carry from ADD A,20H), A holds the offset modulo 100H. If A = 1FH the scan has wrapped to exactly the last byte of the lower GAT group, which is a boundary case requiring special handling.
5022
If the NZ FLAG is set (the wrapped offset is not exactly 1FH), jump back to 500AH to increment L by 1 and re-enter the scanner loop. This handles the wrap-around case by repositioning the scan pointer at the beginning of the GAT buffer and continuing.
5024
OR A,A B7
OR Register A with itself to set flags based on the current value of A. Since A is non-zero (the scan exhausted the valid GAT range without finding a free granule), the NZ FLAG is set.
5025
RET C9
Return to the caller with NZ set, indicating no free granule was found within the valid GAT range. The caller (4FE8H / 5004H path) will handle the disk-full condition.
5026
NOP 00
No Operation. A single padding byte between the scanner return and the GAT Bit Set Encoder entry point at 5027H.

5027H - GAT Bit Set Encoder

Encodes the SET-bit instruction operand for a given granule index into the self-modifying byte at 5032H, then tests whether that bit is currently set in Register B (the GAT bitmap byte). Returns Z if the bit is clear (granule free), NZ if set (granule in use).

5027
AND A,07H E6 07
Mask Register A with 07H (binary 00000111), extracting bits 2-0: the bit position within the current GAT bitmap byte (0-7) corresponding to the granule being tested. Bits 7-3 (the byte offset field) are discarded.
5029
RLCA 07
Rotate Register A Left Circular by 1 bit. This begins shifting the 3-bit position field from bits 2-0 toward bits 5-3, where the CB-prefix SET/RES instruction encodes the bit number.
502A
RLCA 07
Rotate Register A Left Circular by 1 bit (second rotation).
502B
RLCA 07
Rotate Register A Left Circular by 1 bit (third rotation). After three RLCAs, the original bits 2-0 are now in bits 5-3. This places the bit-number field in the correct position for a CB-prefix SET n,r opcode byte (where bits 5-3 = bit number, bits 2-0 = register).
502C
OR A,40H F6 40
OR Register A with 40H (binary 01000000). This sets bit 6, which combined with the bit-number field in bits 5-3 and register field in bits 2-0, forms a valid CB-prefix BIT n,B opcode byte (40H = BIT 0,B base; ORing in the shifted bit number gives BIT n,B for any n).
502E
LD (5032H),A 32 32 50
Self-Modifying Code
Store Register A (the encoded BIT n,B opcode byte) into address 5032H. This overwrites the second byte of the CB-prefix instruction at 5031H-5032H, dynamically changing which bit of Register B will be tested at runtime.
5031
BIT 0,B CB 40
Self-Modifying Code Target
Test bit 0 of Register B (the GAT bitmap byte). The second opcode byte at 5032H is overwritten at runtime by the store at 502EH to test the actual bit position (0-7) corresponding to the granule being checked. The cold-start value CB 40H tests bit 0; at runtime CB xxH tests bit n where xx was written by 502EH. Returns Z if the tested bit is clear (granule free), NZ if set (granule in use).
5033
RET C9
Return to the caller (4F08H or 4F5AH) with Z set if the granule is free, NZ if in use.

5034H - GAT Bit Clear Encoder

Encodes the RES-bit instruction operand for a given granule index into the self-modifying byte at 503EH, then executes the RES to clear that bit in Register A (marking the granule as allocated in the GAT bitmap). Returns with the modified byte in A.

5034
RLCA 07
Rotate Register A Left Circular by 1 bit. Entry: A = bit position (0-7) for the granule to be marked allocated. This begins shifting the 3-bit position field toward bits 5-3 for CB-prefix RES opcode encoding.
5035
RLCA 07
Rotate Register A Left Circular by 1 bit (second rotation).
5036
RLCA 07
Rotate Register A Left Circular by 1 bit (third rotation). After three RLCAs the original bits 2-0 are in bits 5-3, ready for CB-prefix opcode construction.
5037
OR A,0C7H F6 C7
OR Register A with 0C7H (binary 11000111). This sets bits 7, 6, and 2-0 while preserving bits 5-3 (the bit-number field). The result is a valid CB-prefix RES n,A opcode byte (0C7H = RES 0,A base; ORing in the shifted bit number gives RES n,A for any n). Note the target register is A (bits 2-0 = 111 = A), unlike 5027H which targets B.
5039
LD (503EH),A 32 3E 50
Self-Modifying Code
Store Register A (the encoded RES n,A opcode byte) into address 503EH. This overwrites the second byte of the CB-prefix instruction at 503DH-503EH, dynamically changing which bit of Register A will be cleared at runtime.
503C
XOR A,A AF
Clear Register A to zero. Register A is the target of the RES instruction that follows; it must be loaded with the current GAT bitmap byte before the RES clears the allocated-granule bit. However, since this routine is used to construct the mask byte rather than directly modify (HL), A is zeroed here and the RES produces the single-bit-clear mask in A.
503D
RES 0,A CB C7
Self-Modifying Code Target
Clear bit 0 of Register A. The second opcode byte at 503EH is overwritten at runtime by the store at 5039H to clear the actual bit position (0-7) corresponding to the granule being allocated. The cold-start value CB C7H clears bit 0 of A; at runtime CB xxH clears bit n of A where xx was written by 5039H. Since A was zeroed at 503CH and a RES on zero still produces zero, this effectively generates a single-bit mask with only the target bit clear and all others set (via OR with the bitmap byte in the caller at 4F6AH).
503F
RET C9
Return to the caller (4F67H) with A = the result of RES n,A (the bit-clear mask byte). The caller at 4F6AH will OR this with the existing GAT bitmap byte at (HL) to mark the granule as allocated.

5040H - Read Sector Stub (Data Area)

Sets up the track and sector via 4B65H, then reads one sector into the buffer at 5100H via 4B45H. Returns Z on success, A=14H (Read Error) on failure.

5040
PUSH DE D5
Save Register Pair DE onto the stack. DE holds context from the calling routine (the sector info or accumulator) and must be preserved across the 4B65H and 4B45H calls.
5041
PUSH HL E5
Save Register Pair HL onto the stack. HL holds the directory entry pointer or accumulator from the caller and must be preserved.
5042
GOSUB to SYS0 routine at 4B65H: Set Up Track/Sector for Directory I/O. Entry: C = drive number (from (IX+06H), loaded before this call). Sets up the FDC track and sector registers for the subsequent read operation. Returns with the drive selected and track/sector ready.
5045
LD E,00H 1E 00
Load Register E with 00H. E = 00H selects sector 0 (the first sector of the data area) for the 4B45H read call. The sector number 0 directs the read to the start of the data region on the current track.
5047
LD HL,5100H 21 00 51
Load Register Pair HL with 5100H. HL = the GAT sector buffer address. The sector read by 4B45H will be deposited into the 256-byte buffer at 5100H-51FFH.
504A
GOSUB to SYS0 routine at 4B45H: Read Sector From Disk. Entry: E = sector number (00H), HL = destination buffer address (5100H). Reads one sector from the current drive/track into the buffer at 5100H. Returns Z on success, NZ on read error.
504D
POP HL E1
Restore Register Pair HL from the stack saved at 5041H.
504E
POP DE D1
Restore Register Pair DE from the stack saved at 5040H.
504F
RET Z C8
If the Z FLAG is set (the sector read succeeded), return immediately with Z set to signal success to the caller.
5050
LD A,14H 3E 14
Load Register A with 14H. This is the VTOS error code for a Read Error, indicating the sector could not be read from disk. A = 14H with NZ set signals the error condition to the caller.
5052
RET C9
Return to the caller with A = 14H (Read Error) and NZ set.

5053H - Write Sector Stub (Data Area)

Sets up the track and sector via 4B65H, writes one sector via 4768H, and optionally performs a read-after-write verify via 4772H. Returns A=15H (Write Error) on failure, Z on success.

5053
PUSH DE D5
Save Register Pair DE onto the stack. DE holds context from the calling routine and must be preserved across the 4B65H and 4768H calls.
5054
PUSH HL E5
Save Register Pair HL onto the stack. HL holds the directory entry pointer or accumulator and must be preserved.
5055
GOSUB to SYS0 routine at 4B65H: Set Up Track/Sector for Directory I/O. Selects the drive and sets up the FDC track and sector registers for the write operation.
5058
LD E,00H 1E 00
Load Register E with 00H. E = 00H selects sector 0 (the first data sector) for the 4768H write call.
505A
LD HL,5100H 21 00 51
Load Register Pair HL with 5100H. HL = the GAT sector buffer address. The data in the buffer at 5100H-51FFH will be written to disk by 4768H.
505D
GOSUB to SYS0 routine at 4768H: Write Sector To Disk. Entry: E = sector number (00H), HL = source buffer address (5100H). Writes one sector from the buffer at 5100H to the current drive/track. Returns Z on success, NZ on write error.
5060
If the NZ FLAG is set (the write failed), jump to 5067H to skip the verify step and proceed to restore registers and return the error code.
5062
GOSUB to SYS0 routine at 4772H: Write-Verify (Read-After-Write). Reads the sector just written back from disk and compares it against the buffer at 5100H to verify the write was successful. Returns Z on verify success, NZ with A = verify error code on failure.
5065
CP A,06H FE 06
Compare Register A (the verify result code) against 06H. Error code 06H indicates a CRC or data mismatch during verify. If A = 06H the verify detected corruption; if A < 06H (Z or Carry set) the verify was clean.
5067
LD A,15H 3E 15
Load Register A with 15H. This is the VTOS error code for a Write Error. A = 15H is returned to the caller regardless of whether the failure was in the write itself or in the subsequent verify step. The flags from the CP at 5065H or the JR NZ at 5060H remain set, indicating success (Z) or failure (NZ) to the caller.
5069
POP HL E1
Restore Register Pair HL from the stack saved at 5054H.
506A
POP DE D1
Restore Register Pair DE from the stack saved at 5053H.
506B
RET C9
Return to the caller. Z is set if the write and verify both succeeded; NZ with A = 15H if either failed.

506CH - Read Directory Sector Stub

Sets up the track and sector via 4B65H, then reads the directory sector (E=01H) into the buffer at 4200H via 4B45H. Returns Z on success, A=16H (Directory Read Error) on failure.

506C
PUSH BC C5
Save Register Pair BC onto the stack. BC holds the requested record count or other caller context and must be preserved across the 4B65H and 4B45H calls.
506D
PUSH DE D5
Save Register Pair DE onto the stack. DE holds caller context and must be preserved.
506E
GOSUB to SYS0 routine at 4B65H: Set Up Track/Sector for Directory I/O. Selects the drive and configures the FDC for the directory sector read.
5071
LD E,01H 1E 01
Load Register E with 01H. E = 01H selects directory sector 1 (the main directory sector, as opposed to sector 0 which is the GAT sector) for the 4B45H read call.
5073
LD HL,4200H 21 00 42
Load Register Pair HL with 4200H. HL = the directory sector buffer address. The sector read by 4B45H will be deposited into the 256-byte buffer at 4200H-42FFH (the standard VTOS directory/GAT sector buffer in SYS0).
5076
GOSUB to SYS0 routine at 4B45H: Read Sector From Disk. Entry: E = 01H (directory sector), HL = 4200H (destination buffer). Reads the directory sector into the buffer at 4200H. Returns Z on success, NZ on read error.
5079
POP DE D1
Restore Register Pair DE from the stack saved at 506DH.
507A
POP BC C1
Restore Register Pair BC from the stack saved at 506CH.
507B
LD A,16H 3E 16
Load Register A with 16H. This is the VTOS error code for a Directory Read Error, returned in A on both success and failure paths. The Z flag from 4B45H distinguishes success (Z) from failure (NZ); A = 16H is present in both cases but only meaningful on the NZ (error) path.
507D
RET C9
Return to the caller. Z is set if the directory sector read succeeded; NZ with A = 16H if the read failed.

507EH - Write Directory Sector Stub

Sets up the track and sector via 4B65H, writes the directory sector from the buffer at 4200H via 4768H, and optionally verifies via 4772H. Returns A=17H (Directory Write Error) on failure, Z on success.

507E
PUSH BC C5
Save Register Pair BC onto the stack. BC holds the requested record count or other caller context and must be preserved.
507F
PUSH DE D5
Save Register Pair DE onto the stack. DE holds caller context and must be preserved.
5080
GOSUB to SYS0 routine at 4B65H: Set Up Track/Sector for Directory I/O. Selects the drive and configures the FDC for the directory sector write.
5083
LD E,01H 1E 01
Load Register E with 01H. E = 01H selects directory sector 1 for the 4768H write call, directing the write to the main directory sector.
5085
LD HL,4200H 21 00 42
Load Register Pair HL with 4200H. HL = the directory sector buffer address. The data in the buffer at 4200H-42FFH will be written to the directory sector on disk by 4768H.
5088
GOSUB to SYS0 routine at 4768H: Write Sector To Disk. Entry: E = 01H (directory sector), HL = 4200H (source buffer). Writes the directory sector from the buffer at 4200H to the current drive/track. Returns Z on success, NZ on write error.
508B
If the NZ FLAG is set (the write failed), jump to 5092H to skip the verify step and proceed to restore registers and return the error code.
508D
GOSUB to SYS0 routine at 4772H: Write-Verify (Read-After-Write). Reads the directory sector just written back from disk and verifies it against the buffer at 4200H. Returns Z on verify success, NZ with error code on failure.
5090
CP A,06H FE 06
Compare Register A (the verify result code) against 06H. Error code 06H indicates a CRC or data mismatch during the directory sector verify. If A = 06H verification detected corruption; if A < 06H the verify was clean and Z or Carry is set accordingly.
5092
LD A,17H 3E 17
Load Register A with 17H. This is the VTOS error code for a Directory Write Error, returned in A on both success and failure paths. The flags from the CP at 5090H or the JR NZ at 508BH remain set indicating success (Z) or failure (NZ) to the caller.
5094
POP DE D1
Restore Register Pair DE from the stack saved at 507FH.
5095
POP BC C1
Restore Register Pair BC from the stack saved at 507EH.
5096
RET C9
Return to the caller. Z is set if the directory sector write and verify both succeeded; NZ with A = 17H if either step failed. This is the final instruction of the SYS3/SYS overlay.