Carve answer: DESCRIPTION-FILE:DESC segment-entry field offsets¶
Answers: CARVE-BRIEF-DESC-FIELD-OFFSETS-2026-08-11.md (same directory)
Binary: nd-500-mon-j04.prog (MON-DEBUG:PROG, ND-500/5000 Loader/Debug Monitor J04)
Method: walked the code in the shipped nd-500-mon-j04.prog.asm; every load-bearing word
(instruction words, pool words, string pointers, constants) re-read from the raw bytes through
the Ghidra import of the same file. Ghidra program nd-500-mon-j04.prog now carries pre-comments
at every address named below.
Date: 2026-08-11
1. The prediction is CONFIRMED - all five offsets, from the monitor's own code¶
The monitor has a routine that reads one raw 192-byte segment entry from the DESC file into a
fixed buffer at bank-1 word 037705B and then prints the fields with their names. The name
strings sit in bank 2; each print block is: load label descriptor, print label, LDX the buffer
base from the literal pool word at 014716B (verified bytes: 3F C5 = 037705B), load the
field, print it. The pairing of label to load is unambiguous - each label print is immediately
followed by exactly one field load.
| Word offset | Bytes | Width | Label printed | Field load instruction | Verdict |
|---|---|---|---|---|---|
54B |
+88 | double | $PLB: (bank 2 040340B) |
014575B 026054 LDD ,X 54 |
CONFIRMED |
56B |
+92 | double | Psize: (040346B) |
014610B 026056 LDD ,X 56 |
CONFIRMED |
60B |
+96 | double | $DLB: (040362B) |
014636B 026060 LDD ,X 60 |
CONFIRMED |
62B |
+100 | double | Dsize: (040370B) |
014651B 026062 LDD ,X 62 |
CONFIRMED |
64B |
+104 | double | $Debuginfo: (040404B) |
014677B 026064 LDD ,X 64 |
CONFIRMED |
The instruction word at 014575B was verified against the raw bytes in Ghidra (2C 2C =
026054B = LDD ,X 54), as were the buffer pointer and the $PLB: string pointer
(040340B -> bank 2 byte 0x81C0 = $PLB:).
New fields the same routine names, past the known ones¶
The print run continues past DEBUGINFO. Same buffer base 037705B in every case:
| Word offset | Bytes | Width | Label | Field load |
|---|---|---|---|---|
66B |
+108 | double | Dlinkdate: |
014712B LDD ,X 66 |
70B |
+112 | single | Absfixad: |
014750B LDA ,X 70 |
71B |
+114 | single | $Lowlogfix: |
014763B LDA ,X 71 |
77B |
+126 | single | Plologfix: |
014623B LDA ,X 77 |
100B |
+128 | single | Puplogfix: |
014664B LDA ,X 100 |
(The odd print order - PLB, Psize, Plologfix, DLB, Dsize, Puplogfix, Debuginfo, Dlinkdate,
Absfixad, Lowlogfix - is because the output is three fields per line; $ in the label is the
new-line marker.)
This matches the brief's empirical table exactly: +104 is DEBUGINFO and it is a double,
and the "DEBUGINFO non-zero for real programs" observation now has a neighbour explanation -
+108 is a link date, which is also non-zero only for really linked programs.
And one more, at the very start of the entry¶
| Word offset | Bytes | Width | Meaning | Evidence |
|---|---|---|---|---|
0 |
+0 | double | byte position of the NEXT segment entry in the DESC file; 0 = end of chain | 016436B LDD ,X 0 off 037705B, value passed straight to the record reader as the seek position; loop at 016422B-016445B |
Segment entries are a linked list, not an indexed array. The head of the list is word 0 of the
owning domain entry (016412B LDD ,X 0 off the domain buffer 037651B, same treatment).
Verified against the real file bytes (2026-08-11, this session). Walked the structure with
a script over E:\Dev\Ronny\ND500UC\Floppy\210319H02-XX-01D\description-file.desc (the vendor
floppy's own copy) and the two copies under WSL ~/ND500USERS/:
- Domain entries found exactly where
56*i + 256*(i div 32 + 1)says:SCRATCH-DOMAIN'at byte 256,LINKAGE-LOAD-H02at byte 312. - Domain word 0 ->
0x4000and0x40C0; each is a real segment entry (names(210319H02:FLOPPY-USER)SCRATCH-SEG-01'/...LINKAGE-LOAD-H02', and the +88/+92/+96/+100 fields carry the known values: 0/4/0/1028 and 0/123988/75834/2109142). - Both chains terminate with word 0 = 0, no cycles.
- Silent control: no other 192-byte slot in the segment pages carries an in-file pointer at word 0 - only the chain targets do. The links are structure, not coincidence.
- Witness status: all three on-disk copies (floppy original,
FLOPPY-USER,SYSTEM) are byte-identical right now - theSYSTEMcopy that held the LED-B03 entries (the second independent witness inDESCRIPTION-FILE-FORMAT.md) was overwritten with the H02 content on 2026-08-10. The LED numbers in that doc predate the overwrite; re-verifying them needs the LED install re-run under nd500x. - The +100 value read here (
00 20 2E D6= 2,109,142) confirms the transcription-error correction already recorded inDESCRIPTION-FILE-FORMAT.mdsections 5-6:DLB + stored + 1 = 75834 + 2109142 + 1 = 2184977= the.dsegfile size, exactly.
2. How the record is read - the full chain¶
The brief guessed the DESC read goes through the 176740B RFILE helper. It does not. The
DESC path never uses RFILE at all:
013527BREAD-SEGMENT-ENTRY(D = byte position). Builds a read descriptor{buffer 037705B, last byte index 277B}- constants at013545B(000277B= 191, bytes verified00 BF) and013546B(037705B) - then calls:013406BSEEK+READ. Fetches the DESC file number from the global at040124B, calls the176624Bwrapper = MON 74 SETBT (set byte pointer) with the position, then loops calling the176262Bwrapper = MON 1 INBT (read one byte) and stores each byte into the buffer withSBYT(loop at013430B-013444B).- The file was opened by the descriptor-builder at
016200Bthe brief located (theDESCRIPTION-FILE/.DESCstrings), and the printer then indexes the buffer directly.
So the buffer holds the raw file record byte-for-byte, which is what makes the word-offset
to file-byte-offset mapping exact (word 54B = byte 88, and so on).
Domain entries have their own reader, 013454B READ-DOMAIN-ENTRY(index), which computes
position = 56*index + 256*(index div 32 + 1)
(multiply constant {0, 70B} = 56 at 013520B-013521B; divide by 40B = 32 and AAA 1
at 013473B-013475B; shift left 8 = 256 at 013476B) and reads 56 bytes (descriptor
last-index 67B = 55) into 037651B. That gives the DESC file layout:
2048-byte pages, each = 256-byte header/bitmap + 32 domain entries of 56 bytes, with the
first page's entries starting at byte 256. Segment entries live wherever the chain links point.
The exact routing of the divide (which half of the double goes through RDIV) is read from the
compiler idiom, not stepped through - but 5632 + 256 = 2048 exactly, so the geometry is not
in doubt.
3. Sizes as size-1: what the code does and does not say¶
- The monitor never adjusts the size fields. The printer prints the stored values raw - no
AAA 1anywhere on the path. So on a live system,Psize:in this listing displays the stored value, whatever it is. There is no read-side +1 in this program, and no other code in the image touches word56Bor62Bof the buffer (searched the whole disassembly - the fiveLDDs above are the only accesses past word50Bbesides the ones in this table). - But the same inclusive-bound convention shows up twice in the reader itself: the 192-byte
segment record is read with length word
277B= 191 = 192-1, and the 56-byte domain entry with67B= 55 = 56-1. These are PLANC-style inclusive last indexes (0:n-1ranges), the exact convention the brief measured in PSIZE/DSIZE. So "stored value = last byte index, not byte count" is the house style of the program that owns this file - consistent with, though not a write-side proof of, the empirical size-1 finding. - The writer of these fields is not in this program. MON-DEBUG only reads and displays. Write-side proof of the -1 must come from NLL (the linkage loader).
4. DLB and the LINKAGE-LOAD-H02 mismatch¶
Resolved - but by the file bytes, not by this program's code. MON-DEBUG only prints DLB
and Dsize side by side; it never combines them. The mismatch itself turned out to be a
transcription error in the empirical table: the stored +100 value is 2,109,142 (raw
00 20 2E D6), not 2,109,654, and then DLB + stored + 1 = .dseg file size holds exactly
for all four entries. Recorded in DESCRIPTION-FILE-FORMAT.md sections 5-6 and re-verified
byte-for-byte in this session (see the chain-verification notes in section 1 above).
Also observed in the same printer (lower offsets, for the format doc)¶
All off the segment-entry buffer 037705B:
- word
36B(byte 60): a flags word; individual bits are printed viaSHA n+SHA ZIN SHR 17bit tests at015743B-016052B(bits labelled by more bank-2 strings around byte0x8110-0x81C0, not decoded here). - word
37B(byte 62): a character count used for two byte-string prints: one string starting at word45B(byte 74), one at word50B(byte 80) (LBYTloops at015256B-015301Band015311B-015335B). - The domain-entry walker loops indexes 0..253 (limit
000376B= 254 at016504B). - Domain-entry fields printed from
037651Binclude doubles at words0, 17B, 21B, 23B, 25B, 30B, 32Bwith labels$Domain :,Start address:,$Owner:,Childindex:in the bank-2 string run at bytes0x80C6-0x8140- label-to-offset pairing for these was not worked through.
Traps confirmed (nothing new hit)¶
The brief's four traps all held. One addition: the shipped .asm prints pool words as
instructions in the 014714B-014735B, 013544B-013551B and 016462B-016506B regions
too - same literal-pool trap as 016264B-016277B.