OCTOBUS kick + mailbox GAP REGISTER¶
Date: 2026-07-30
Scope: the ND-100 <-> ND-5000 (SAMSON) octobus path in RetroCore. What SINTRAN and the real
B30 microcode do at the kick / mailbox layer, versus what OctobusND5000Station actually
implements.
Method: SINTRAN NPL as the requirement, the real B30 microcode executed as the behaviour
oracle, RetroCore source as the current state. No inference where execution could answer instead.
Companion: STOP-SYSTEM-ANALYSIS-AND-CLRKICK-GAP-2026-07-30.md (the shutdown path that exposed
gap G1).
STATUS AT A GLANCE (2026-08-01) - READ THIS FIRST¶
No gap in this register is open. This file has grown to twenty probes; the table is the answer, the probes are the evidence. Do not re-open an entry without reading its status line here first - several entries below still carry their original alarming headings, kept deliberately so the wrong version cannot be re-adopted, each with a correction banner.
| Gap | State | One-line reason |
|---|---|---|
| G1 kick 3 no-op | FIXED | ExecuteClearFunctions writes X5CLR:=0, X5CCL:=1, X5PRO:=-1. |
| G2 kick 6 no-op | FIXED | Writes X5PRO := -1, so TER51 completes instead of ESPTIMOUT. Independently confirmed by execution later. |
G3 OCB_CLNUP requeue |
ANSWERED - body is UNREACHABLE | A sneak cycle at 0o25571 deposits constant 0 into SC13; 0o25573 tests it for zero and returns. Structural, not circumstantial. Do not implement N5STA := 1. |
| G4 kick 2 | FIXED | Mapped to ACTIVATE. |
| G5 kicks 4/5 | CONTRACT MEASURED, deliberately NOT built | They set X5PRO := -1 and leave X5CLR/X5CCL alone. Still no known NPL sender - do not build ahead of one. |
| G6 unknown kicks swallowed | FIXED (logging); unlock NOT owed | Logging done. The UNLOCK_QUE must NOT be added - the station never takes the lock, and releasing one it does not hold could clear SINTRAN's. |
G7 X5ACT re-arm |
WITHDRAWN | Never a gap. |
G8 X5CCL |
CLOSED for every path that exists | Kick 3 is the only cache-clear path and it writes it. Re-opens as a requirement on G5 if kicks 4/5 land. |
G9 OCB_WAITSEX trigger |
RESOLVED | X5CLR bit 15 arms it - the earlier negative was a detection failure (the spin cell was pre-zeroed). Unimplemented: no caller sets bit 15. |
| G10 244B terminate | FIXED, and its premise REFUTED | 244B is a NORMAL bring-up step, not a timeout. The defect was _accpIdle outliving the microprogram restart. |
Two things were found along the way that matter beyond this register:
- A real emulator defect, fixed:
ReadAflagcomposed only AOBF and AIBF, so the four dispatch bitsSCAN_ACCPtests (5, 6, 11, 12) could never fire. Every ACCP poll in the emulator was reading a status word that could not report four of its conditions. - A kick-injection harness now exists and is validated against kick 3 as an oracle. It is what made G5's contract measurable and G3's answer reachable.
Three of my own claims in this file were retracted after testing - two "suspected emulator defects" that were measurement errors, and one wrong cause for a real observation. The retractions are left in place with banners rather than deleted.
0. The alignment result - kick 3 is now SETTLED by execution, not by reading¶
The blocker on implementing kick 3 was the acknowledge value. The carve summary
(ND5800-MICROCODE-ACCP-OCTOBUS-CATALOG.md correction 3) says "write X5CLR back with bit 15
cleared", which cannot be literally right: SINTRAN writes 0o77 = 0x003F, which already has
bit 15 clear, while ST0PSYS polls the cell for zero.
Resolved by running the real microcode (MICRO-5800-B30.DATA) from OCB_KICK03 with
X5CLR = 0o77, in the new test
E:\Dev\Repos\Ronny\RetroCore\Nuget\HackerCorpLabs.Emulation.CPU.ND5000\tests\MailboxClrKickTests.cs.
The write trace is unambiguous:
W: CS 025627: [ext+0x12] w2 := 00000001 X5CCL := 1 (cache-clear counter)
W: CS 014702..014736: [0x800..0x870] w4 := 0 (a block of 24 word writes - the PCB/context region)
W: CS 025536: [ext+0x10] w2 := 00000000 X5CLR := 0 <-- THE ACKNOWLEDGE
W: CS 025421: [ext+0x0C] w2 := 0000FFFF X5PRO := -1 (ND-500 IDLE = "forget process")
So the acknowledge is a plain zero write to X5CLR, not a bit-15 mask operation. The carve
wording was a compression of the listing. SINTRAN's poll-for-zero is satisfied, and the earlier
"unresolved contradiction" is closed.
Independently corroborated from the raw microwords (the rendered .md mis-renders ORCON/MARG, so
these are raw lo halves) and from the xref table MICRO-5800-B30.LABE:
OCB_KICK03 025522* <- referenced from 016433 (the OCB_DEC_K kick table)
MSG_CLEAR_1 016132* <- referenced from 025527 (cache clear by mask)
OCB_WAITSEX 025543* <- referenced from 025547 (the bit-15 spin)
OCB_KICK031 025550* OCB_KICK032 025552* OCB_KICK06 025561*
025525 lo=...2B564210 MARG 0x10 = X5CLR (the read)
025535 lo=...2B5E4210 MARG 0x10 = X5CLR (the write-back)
025543 lo=...2B644228 MARG 0x28 = GLOBAL header word 0o24 (the OCB_WAITSEX spin)
~~OCB_WAITSEX was not entered on either mask tested (the run with bit 15 set produced the same
26 writes), so the spin's trigger condition is still [OPEN] - recorded as an observation in the
test rather than encoded as a guess.~~
SUPERSEDED 2026-08-01 - this observation was a DETECTION FAILURE, see G9.
OCB_WAITSEXspins on global header word0o24until zero, and the setup used here pre-zeroes that word - so the spin was entered and left in the same instant, which is indistinguishable from never entering it. Re-run with the spin cell non-zero,X5CLRbit 15 DOES arm the spin and bit 15 clear never reaches it. The carved trigger was correct. Full table in the G9 entry.
0.1 The kick-3 contract our station must implement¶
Three mailbox side effects, all in the per-CPU extension block:
| Cell | Word | Byte | Value | Why |
|---|---|---|---|---|
X5CLR |
0o10 | +0x10 | 0 | ST0PSYS's wait loop exits only on zero, else ERRFATAL |
X5CCL |
0o11 | +0x12 | 1 | the cache-clear counter SINTRAN reads/compares |
X5PRO |
6 | +0x0C | -1 | ND-500 IDLE - the "forget process" half of the mask |
Plus: read the mask from X5CLR, never assume 0o77. LMPCLR
(MP-P2-N500.NPL:1222, commented "Clear-function; ND5000 only") takes it from the swapper's
message at SWMSG + 5DP2, so it is runtime data and bit 15 genuinely can be set.
0.2 CORRECTION (same day): fixing G1 does NOT fix stop-system - G10 sits above it¶
LATER STATUS, read this before acting on the section below: G10 is FIXED AND VERIFIED (2026-07-30). The analysis here is still correct about the kick being dropped by the
_accpIdleguard, but its conclusion - that some ACCP exchange timed out - is WRONG. 244B TERMINATE is a normal bring-up step sent after 3 fully-answered commands. See the corrected G10 entry below.
After implementing G1 the live harness STILL showed X5CLR = 0x003F after stop-system. Chased
with real observables rather than inference (the station's Log() never reaches TestContext, so
"I saw no log line" is not evidence - see the marker rule). Added KickCounts[64] +
KicksDroppedDisabled on the station and TxFrameCount / TxKickCount / TxLastKickDest /
TxLastKickRoute on the card. Measured, one run:
OCTOBUS TX before-stop-system: frames=840 kicks=0
OCTOBUS TX after-stop-system: frames=841 kicks=1 lastKickFrame=0xB843 lastKickDest=56 route=fabric
KICKS (station): NONE RECEIVED droppedDisabled=0 kicksEnabled=True
Reading it:
- SINTRAN sends exactly one kick in the whole run -
CLRKICKduringstop-system.0xB843decodes as C=1, K=1, M=0, station bits = 56 (70B), info = 3. Correctly formed, correctly aimed. route=fabric- a station IS registered at 56 andOctobusFabric.SendFramewas called. The delivery frame is0x8143(station field rewritten to the source, K preserved), soHandleFramereally did receive it.- The station still counted nothing, because the kick is dropped BEFORE the kick branch by this
guard in
OctobusND5000Station.HandleFrame:
if (_accpIdle) { // ACCP terminated (244B)
...
if (!accpMultibyte && !accpBodyByte) return null; // comment: "keep ignoring kicks"
}
This drop is AUTHENTIC, not a bug. _accpIdle is set in exactly one place - emergency
244B TERMINATE ACCP - which STOPS the microprogram (_microprogramRunning = false). A kick is
delivered to the microprogram via AOB + ATRAP + OMESS, so a stopped microprogram cannot execute it.
Real hardware behaves the same way: ST0PSYS asks a terminated CPU to clear its cache, nothing
answers, and the poll exhausts into ERRFATAL.
Also worth recording: kick 1 was never sent either. That is correct and not a gap - activation goes
through the X5ACT := 0 write (ACT51), and the kick is the PREEMPT path only.
G10 - _accpIdle sticks after the 244B TERMINATE [P1, FIXED AND VERIFIED 2026-07-30]¶
The original title and premise of this gap were WRONG, and both are corrected here rather than overwritten, because the wrong version is what a reader would otherwise act on.
Originally filed as "something makes SINTRAN TERMINATE the ACCP mid-run", reasoning that 244B is what the ND-500 monitor sends on ACCP timeout (manual chapter 5.3.9), so some exchange of ours must have gone unanswered. That inference was wrong. Measured, not inferred: the 244B arrives after exactly 3 ACCP commands with all 3 answered, and a full run answers 149 of 149. It is present in a fixed run and a broken run alike, at the same place in the ladder, with the same three commands behind it.
244B TERMINATE is an unconditional, NORMAL bring-up step. Receiving one is not a fault signal and is not evidence of a timeout. SINTRAN sends it, then restarts the microprogram.
The actual defect was the flag, not the terminate: _accpIdle was cleared only by ContinueAccp
and ResetStation, and never by STAMIC0 / CONTMIC / RESTMIC. So the microprogram restarted
but the flag stayed set, and the guard in OctobusND5000Station.HandleFrame dropped every kick
for the rest of the session. That is why stop-system reached ERRFATAL no matter how correct
the kick-3 handler was.
The standing warning still holds and is now better founded: do NOT "fix" this by letting kicks
through the _accpIdle guard. The guard is authentic - a stopped microprogram genuinely cannot
execute a kick. The fix is to clear the flag on the restart paths that really do restart it.
Verified: k3=1, X5CLR=0000, X5CCL=0001, accpIdle=False, full ladder green.
How we got it wrong: the first clean capture predated the footer field that records the 244B snapshot, so the line was simply not written. We read a missing FIELD as a missing EVENT, and built a timeout theory on top of it.
1. GAP REGISTER¶
Severity: P1 = a SINTRAN path provably breaks (an ERRFATAL/timeout we can name).
P2 = incorrect but no known SINTRAN caller. P3 = robustness / diagnostics.
The kick dispatch table¶
OCB_DECODE -> OCB_MES_K -> VECT := word & 0o77 -> OCB_DEC_K (016430), 64 entries. In
RetroCore, OctobusND5000Station.cs:1330-1367 loads every kick into the AOB and raises
KickReceived, but only kickNumber == 1 drives any behaviour (WalkQueue()).
| Kick | SINTRAN sender | Microcode handler | Our station | Gap |
|---|---|---|---|---|
| 0 | - | NOTREC |
logged | G6 FIXED |
1 N100KICK |
ACT52 @145520 |
ACTIVATE |
WalkQueue() |
none |
| 2 | none found in NPL | ACTIVATE (shares 1) |
WalkQueue() |
G4 FIXED |
3 CLRKICK |
ST0PSYS @147467, LMPCLR @136732 |
OCB_KICK03 025522 |
ExecuteClearFunctions() |
G1 FIXED |
| 4, 5 | none found in NPL | OCB_KICK05 025553 |
logged, not implemented | G5 (P2) |
6 IDLEKICK |
TER51 @145230 |
OCB_KICK06 025561 |
logged, not implemented | G2 (P1) |
| 7-63 | n/a | OCB_KICK64 -> UNLOCK_QUE + NOTREC 204 |
logged | G6 FIXED (unlock still owed) |
Implemented 2026-07-30 in OctobusND5000Station.cs: the kick handler is now an explicit
switch over the table instead of an if (kickNumber == 1), so kicks 2 and 3 do their real work
and every unhandled number logs UNCONDITIONALLY naming the microcode routine it should have run.
Tests: OctobusMailboxO1Tests.KickPath_Kick3_* (station level, 14/14 green) and
MailboxClrKickTests (real-microcode oracle, 3/3 green).
G1 - kick 3 (CLRKICK) is a no-op [P1, CONFIRMED LIVE - NOW FIXED]¶
Requirement: ST0PSYS writes X5CLR := 0o77, sends kick 3, then polls X5CLR for zero up to
1000 times; CALL ERRFATAL if it never clears (MP-P2-N500.NPL:3759).
Evidence: measured on the live octobus harness - X5CLR = 0x0000 before stop-system,
0x003F after. Nothing consumed it. X5CLR appears in RetroCore only in doc comments
(IServicerHost.cs:116, OctobusND5000Station.cs:628).
FIXED 2026-07-30: OctobusND5000Station.ExecuteClearFunctions() reads the mask from
X5CLR (never assumes 0o77), then writes X5CCL := 1, X5PRO := -1, and X5CLR := 0 last
(the release signal must come after the other two, or SINTRAN can proceed while they are stale) -
all three under _mpm.SyncRoot, since the ND-100 polls X5CLR from another thread and must never
see a half-applied acknowledge.
Deliberately not modelled: the mask's cache / data-TSB / dump bits have no physical effect because we model no ND-5000 cache or TSB - there is nothing to invalidate, so acknowledging IS the complete correct behaviour here.
Note: the ST0PSYS poll is bounded, so this can never HANG stop-system - it degrades to
ERRFATAL. Do not reach for this gap to explain a hang.
G2 - kick 6 (IDLEKICK) is a no-op [P1]¶
Requirement: TER51 (MP-P2-N500.NPL:145230) is the ND-500 TERMINATE path:
TER51: FOR LOOPCOUNTER DO
IDLEKICK; CALL XKICK500
CALL GETC5PROC
IF A=-1 GO OKRET % X5PRO idle -> terminated cleanly
OD
TER52: ESPTIMOUT % never went idle -> timeout error
So SINTRAN sends kick 6 in a loop and leaves only when X5PRO reads -1. With kick 6 ignored,
X5PRO never becomes -1 and every terminate falls into ESPTIMOUT.
This is the same shape as G1: a mailbox cell SINTRAN polls that our station never writes. It was invisible for the same reason - the failure is a timeout on a path nothing in the harness exercises yet.
Microcode (OCB_KICK06 025561, per the carve): CNTXTSAVE if a process is loaded, SET_IDLE,
OCB_CLNUP, UNLOCK_QUE, PRNOWR(SC14), IDLE. The observable TER51 depends on is
X5PRO := -1. A test asserting exactly that against the real microcode is in
MailboxClrKickTests.OcbKick06_IdleKick_SetsX5ProIdle_WhichTer51PollsFor.
Fix: implement kick 6 to set X5PRO := -1 and park the CPU idle. CNTXTSAVE and OCB_CLNUP
are larger pieces - see G3.
G3 - OCB_CLNUP requeue semantics not modelled [P2]¶
OCB_CLNUP (025570) is reached from kicks 4/5/6. Per the carve it does NOT walk/discard the
message region: it un-claims the in-progress message - DPA := ADR_MESS, check 5CPUN@-6
against this CPU, clear MSGME (srf 2021), MSG_CCMOVE, then write N5STA := 1 (back to
MSGN500), returning the message to the queue unanswered.
So kicks 4/5/6 REQUEUE in-flight work, they do not drop it. Any implementation of G2/G5 that discards the current message is wrong in a way that will look like lost messages much later.
Not yet modelled at all in our station.
G3 probe 2026-07-30 - still open, and NOT reproducible the easy way. Drove the real microcode
through OCB_KICK06 (which calls OCB_CLNUP at 0o25564) with a message laid down, chained from
X5BEX, N5STA = 2. Result: N5STA came back unchanged at 0002 - the carve's "writes
N5STA := 1 (MSGN500)" did not happen.
Reading: OCB_CLNUP un-claims the CPU's CURRENT in-progress message, which it finds via
ADR_MESS (0o17334) and the MSGME cell (srf 0o2021), plus a 5CPUN@-6 check that the message
belongs to this CPU. Simply pointing X5BEX at a message does not make it "in progress", so the
routine has nothing to un-claim and no-ops. Pinning the contract needs that srf state set up
first - that is the next step, not more guessing.
Useful side result: our kick-6 implementation does NOT touch N5STA, so it is not consuming or
corrupting queued work - it is incomplete, not wrong. The test
MailboxClrKickTests.OcbKick06_WithMessage_ShowsWhatOcbClnupDoesToIt guards that.
G3 probe 3, 2026-07-31 - the carve's N5STA := 1 claim is NOT REPRODUCIBLE¶
Probe 2 supplied the state probe 1 was missing - MSGME (srf 0o2021) pointing at the message, and
the 5CPUN ownership word at message-6 - and swept the CPU number 0..15 rather than guessing
one, because a wrong guess and a genuine no-op look identical.
SUMMARY: 16/16 runs REACHED OCB_CLNUP, 0 changed N5STA, 0 requeued to MSGN500(1), owner=NONE FOUND
All 16 runs REACHED OCB_CLNUP and none of them wrote N5STA. The reachability watch is
proven non-vacuous by a control in the same test that asserts the watch reports FALSE for an address
the path never executes - so "reached" is a measurement, not an assumption.
This is evidence of absence, not absence of evidence, and it is the first result here that actually contradicts the carve rather than merely failing to confirm it. Two readings remain open and this probe does NOT choose between them:
- The carve's "write
N5STA := 1" is wrong - possibly read off a neighbouring routine or a mis-rendered listing, the same class of error already found in correction 3 of the catalog. - "In progress" needs more than
MSGME+5CPUN, and the routine is still declining early.
Do NOT implement N5STA := 1 in the station on the strength of the carve. It is now a claim
with a failed reproduction against the real B30 microcode, and the project rule is that executing
microcode outranks a carve summary.
Test: MailboxClrKickTests.OcbClnup_SweepOwningCpu_ReportsWhichOneRequeuesTheMessage.
G3 probes 4 and 5, 2026-07-31 - reading 2 is CONFIRMED, and reading 1 is not needed¶
Probe 4 traced the microprogram counter step by step from the moment OCB_CLNUP is entered, and
diffed a run with MSGME set against one without.
OCB_CLNUP occupies 0o25570..0o25604, but only FOUR microwords execute:
step CS
0 25570 <- OCB_CLNUP entry
1 17334 <- ADR_MESS
2 104
3 105
4 25571
5 25572
6 25573
7 104
8 105
9 25565 <- back in the CALLER (OCB_KICK06)
The tail never runs. MSGME is cleared at 0o25601, MON_ERR? is at 0o25605 and MSG_CCMOVE
at 0o25611 - none are reached. That is the whole explanation for "no N5STA write was ever
observed": the code that would do it is in a tail this path does not enter.
The two traces NEVER diverge, so MSGME does not steer this path at all - which retires the
assumption behind probes 2 and 3.
Probe 5 then logged every memory READ while the routine ran. OCB_CLNUP's executed microwords
perform ZERO memory reads (the one logged read, CS 25511 [0x2000] r2 = 0, happens after control
has already left the routine). So:
- The early exit is decided by register / srf state, NOT by anything in memory.
- That is why supplying
MSGMEand the5CPUNword in memory changed nothing - the routine never looks at memory before deciding. - The
5CPUN@-6ownership check the carve describes cannot be happening on this path; it would require a memory read, and there is none.
Status: reading 2 from probe 3 is CONFIRMED - "in progress" needs state we are not setting, and
it lives in the srf/registers. Reading 1 (the carve is simply wrong) is neither needed nor
supported: the N5STA := 1 code may well be correct and simply unreached.
Tests: OcbClnup_TracePath_ShowsWhereItLeavesEarly,
OcbClnup_LogReads_NamesTheCellThatDecidesTheEarlyExit.
G3 probe 6, 2026-07-31 - THE BRANCH IS NAMED. Hand-decoded from the microword bits¶
Raw microwords from MICRO-5800-B30.DATA (16 bytes/word, word N at file offset N*16):
CS 25570 : 0000000000017006000000001EDC0000
CS 25571 : 50080000000180180000000000150000
CS 25572 : 40000001180E8000000000002B7B0000
CS 25573 : 400000006C0150218120000000440000
Decoded with tools/microcode-5000-def.json (SEQUENCER: COND_SEQ 69, SEQ_TRUE 68-65,
SEQ_FALSE 64-61, INVSEQ 60, TESTOBJ 58-53; ADDRESS: ABS_ADDR 31-16):
| CS | COND_SEQ | TESTOBJ | ABS_ADDR | note |
|---|---|---|---|---|
| 25570 | 0 | - | 17334 |
unconditional -> ADR_MESS |
| 25571 | 0 | - | 25 |
|
| 25572 | 0 | - | 25573 |
|
| 25573 | 1 | 0o11 = COND,MZRO (Z from ALU operation) |
104 |
THE BRANCH |
ABS_ADDR is independently corroborated, not assumed. Three of the four words carry a value
that matches the OBSERVED execution exactly: 17334 = ADR_MESS (trace step 1), 25573 (step 6),
104 (step 7). The field definition and the live trace agree.
The answer to G3's open question: OCB_CLNUP calls ADR_MESS, then at CS 0o25573 branches on
the ALU zero flag to 0o104 - the return path. ADR_MESS returned ZERO, meaning "this CPU has
no current message", so the routine correctly declines and returns after four microwords. The
body at 0o25574..0o25604 - the MSGME clear, and whatever writes N5STA - is only entered when
ADR_MESS returns NON-ZERO.
Consequence for the carve: it is NOT disproven. The N5STA := 1 claim describes the body, and
this path never enters the body. Probe 3's "evidence of absence" is now correctly scoped: it is
evidence that the body did not run, NOT evidence that the body does something different. The
earlier framing here overstated it and is corrected by this entry.
Remaining G3 question, now the only one: what makes ADR_MESS (0o17334) return non-zero. It
reads no memory on this path, so the answer is in srf/register state. microcode-5000-def.json has
no SRF field group, so naming the cell needs either that field added or a register-level trace
through ADR_MESS.
Still do not implement N5STA := 1 from the carve - it remains unobserved. But it is now
unobserved-because-unreached, which is a much weaker objection than "reproduction failed".
G3 probe 7, 2026-07-31 - operand decode. The carve's DPA := ADR_MESS is CONFIRMED¶
Same four words, now decoded through the OPERANDS group (A_OP 6 bits, B_OP, DEST) and the
IAC/MEMORY groups:
| CS | A_OP | DEST | meaning |
|---|---|---|---|
| 25570 | A,BM00 |
D,SC14 |
set up, then jump to ADR_MESS |
| 17334 (ADR_MESS) | A,BM12 |
D,RFA1 |
computes a register-file address; one microword, returns via 0o104 |
| 25571 | A,BM00 |
D,NONE |
no destination - a condition-setting step |
| 25572 | A,RF1 |
D,DAC,DPA |
DPA := RF1 |
| 25573 | A,SC13 |
D,SC12 |
the zero test is on SC13 |
Two things settle here.
DPA := current message (ADR_MESS)is CONFIRMED at CS 0o25572 - the carve's first step is right, byte-decoded.ADR_MESSwritesRFA1(a register-file ADDRESS), and 0o25572 then loadsDPAfromRF1(the register-file DATA at that address). That is a clean two-step address-then-fetch, and it means the carve's description of this routine is not fabricated - the parts we can reach check out.- The exit test at 0o25573 is on
SC13, NOT onDPA. So the routine does not decline because the message pointer is null; it declines on a separate scratch value.SC13is not written anywhere in these four words, so it arrives from the CALLER (OCB_KICK06).
Corrects probe 6's wording. Probe 6 said "ADR_MESS returned ZERO, meaning no current message".
That inference does not survive the operand decode - the tested register is SC13, and nothing
observed says ADR_MESS returned zero. The honest statement is: the routine exits on SC13 being
zero, and what SC13 means is not yet known.
Next: find where OCB_KICK06 (0o25561..0o25567) sets SC13. The trace already has the caller's
path - 0o25565, 0o25505-0o25512, 0o25566, 0o25416, 0o25567, 0o24670 - so this is another
bounded hand-decode, not a search.
G3 probe 8, 2026-07-31 - SC13 is general-purpose, and forcing it DOES NOTHING. Contradiction.¶
Three facts, each independently checked:
SC13is not a dedicated flag. Scanning all 16384 words of the B30 image forDEST = 0o26finds 600 writers. It is a general-purpose scratch register.OCB_KICK06never writes it. Decoding 0o25561..0o25567: DESTs areSC12,SC14,SC14,NONE,NONE,SC12,NONE. NoSC13. Nor doesOCB_CLNUPitself. Only four words execute beforeOCB_CLNUPis entered (0o25561-0o25564), and none of them touch it.- The ALU op at 0o25573 is
ALU,A- "A OPERAND DIRECT THROUGH THE ALU".B_OPisX1but is ignored by this op. So the zero flag should be exactlySC13 == 0.
That predicts a clean experiment: preset SC13 (WRF index 22) non-zero and the body should run.
SC13 reachedBody N5STA writes
00000000 False 0002 24
00000001 False 0002 24
00002800 False 0002 24
FFFFFFFF False 0002 24
It changes NOTHING - not even the write count. All four runs are byte-identical in effect.
This is a contradiction, and it is the finding. The decode says the branch tests SC13 through
a pass-through ALU op, and nothing between the preset and the test writes SC13. Forcing it should
change the outcome. It does not. So at least one of these is false:
- the
A_OPdecode (0o66 =A,SC13), - the WRF index mapping (
Registers.cssays 20-23 = SC11-SC14, so SC13 = 22), - the assumption that
ALU_TRUEis the field in force - 0o25573 also hasCOND_ALUin play, and if the ALU takesALU_FALSEinstead, the operation is a different one entirely, - or our emulator's handling of
COND_ALU/ALU_FALSE/ the Z flag on this path.
The last two are the most likely and the most interesting, because a COND_ALU mishandling
would be an EMULATOR DEFECT, not a carve question - and it would silently affect every conditional
microword, not just this one. That is worth more than G3 itself.
Do not conclude "the harness enters cold" either - that was probe 8's hypothesis and this experiment REFUTED it. Recorded so it is not re-adopted.
Test: MailboxClrKickTests.OcbClnup_NonZeroSc13_ShowsTheEarlyExitWasAHarnessArtefact (the name
records the hypothesis; the FINDING line records that it failed).
G3 probe 9, 2026-07-31 - CONTRADICTION RESOLVED, and it points at an EMULATOR DEFECT¶
The COND_ALU suspicion from probe 8 is WITHDRAWN. Checked directly: COND_ALU = 0 at
0o25573, so ALU_TRUE (ALU,A) really is in force, and the emulator maps A_OP 0o66 to
Wrf[22] = SC13 correctly (OperandRouter.ReadA: group 1, reg = sel - 32 = 22). Both suspects
cleared - I flagged them and they were wrong.
Why forcing SC13 did nothing: it was being overwritten before the branch read it. Sampling
SC13 at the moment 0o25573 executes shows 00000000 in every run including the FFFFFFFF
preset. Logging only the ticks where SC13 CHANGES:
preset SC13 = FFFFFFFF (immediately after write)
CS 14672 WROTE SC13: FFFFFFFF -> 00000800
CS 14722 WROTE SC13: 00000800 -> 00000000
CS 14737 WROTE SC13: 00000000 -> 80000000
CS 25571 WROTE SC13: 80000000 -> 00000000 <-- immediately before the branch
reached CS 25573 (the branch)
Two structural facts fall out, both correcting earlier entries here:
- 0o25562 is a CALL, not a fall-through. Execution goes 0o25561 -> 0o25562 -> 0o14666 and
only later returns to 0o25563/0o25564/0o25570. So
SC13is not caller state inherited from before the kick - it is COMPUTED by the subroutine at 0o14666. Probe 8's "nothing writes SC13 between entry and the test" was wrong because it only looked at the straight-line words. - The word at 0o25571 zeroes
SC13, one word before 0o25573 tests it. That makes the early exit UNCONDITIONAL in effect:OCB_CLNUPcan never enter its body, for ANY mailbox state.
And 0o25571 should not be writing anything. Decoded from its microword
50080000000180180000000000150000: DEST = 0o30 (= 24 = D,NONE), OR_ENABLE = 0, ORCON = 0.
The emulator's own WriteDest treats destination 24 as a no-op and returns. Emulator and
microcode-5000-def.json agree that DEST is bits 83-76, so this is not a field-position
disagreement.
So something OTHER than the DEST field is writing SC13 during that word. [OPEN]
Two readings, and this probe does NOT choose:
- (a) EMULATOR DEFECT - an unintended write to
Wrf[22]somewhere inTick(). If so it is not a G3 issue at all: it would silently corrupt a scratch register on any word of this shape, and it happens to land one word before a conditional that reads it. This is the higher-value possibility and should be chased first. - (b) Tick attribution is off by one - the change is committed during 0o25571's tick but
originates in a deferred write from an earlier word (the codebase already models exactly this for
LC, seeRegisters.LcLoadPipeand the deferral note inRegisters.cs). Then the real writer is 0o25570 or earlier and the DEST decode of THAT word is what to check.
Next: read CpuND5000.Tick() for writes to Wrf outside WriteDest, and for any deferred
write pipeline that could commit a cycle late. That distinguishes (a) from (b) directly.
G3 probe 10 - RETRACTED IN FULL by probe 11. There is NO emulator anomaly.¶
DO NOT ACT ON THE SECTION BELOW. Its conclusion - "a register no code path should write is being written" - was my own measurement error, not a defect.
State.Mpcis updated BEFORE a word executes, so the address my probe logged was the NEXT word, not the writer. The real writer is CS 0o16554, a word carrying a perfectly legitimateDEST = SC13. Kept visible with this banner because "suspected emulator defect" is exactly the kind of claim that gets re-adopted if it is quietly deleted. Full correction in probe 11 below.
G3 probe 10, 2026-07-31 - reading (b) is RULED OUT. This is an emulator anomaly. [OPEN]¶
The decode is not in dispute. The emulator's own Microword agrees with the hand-decode of the
raw image, field for field:
emulator decode CS 25570: AOp=0 Dest=27
emulator decode CS 25571: AOp=0 Dest=30 <- 0o30 = 24 = D,NONE
emulator decode CS 25572: AOp=214 Dest=350
emulator decode CS 25573: AOp=66 Dest=25
So probe 9's reading (b) - "my hand-decode is wrong / field positions disagree" - is ruled out.
Both decoders say the word that writes SC13 has no destination.
Static inspection cannot explain the write:
OperandRouter.WriteDestis called from exactly ONE place (CpuND5000.cs:1890), and forsel = 24it returns immediately without writing.- The only other
Wrfwrites in the CPU are the AAP register-pair path (Wrf[4+rin]andWrf[12+rin],rin0-3), which can only reach indices 4-7 and 12-15.SC13is index 22 and is unreachable from there. - The one cross-word mechanism that could make a word write somewhere unexpected is
_pendingOrconD/ResolveOrDestination, and it is gated onword.Dest == 31. 0o25571 hasDest = 24.
So: a register that no code path should write is being written, deterministically, one word before a conditional that reads it. That is an emulator anomaly, not a carve question. Status [OPEN] - named, reproducible, and NOT explained.
Deliberately not guessed at. Three hypotheses have already died this session (harness-enters-cold,
COND_ALU, decode-disagreement); a fourth invented from static reading would be worth nothing.
What would settle it in one run: a conditional breakpoint or a temporary write-barrier on
Regs.Wrf[22] inside CpuND5000.Tick(), reporting the call stack on change. That requires editing
src/CpuND5000.cs, which is currently carrying someone else's uncommitted work - so it was NOT
touched. This is the first thing to do once that file is free.
Impact if it is a defect: OCB_CLNUP can never execute its body under our emulator, so kicks
4/5/6 can never requeue in-flight work, and G3 cannot be closed by observation at all until this is
fixed. It would also affect any microword that reads a scratch register written this way - which is
far wider than this routine.
G3 probe 11, 2026-07-31 - the "anomaly" was MY BUG. Retracting probe 10.¶
Put a write barrier inside the CPU (temporary, since reverted - src is untouched) reporting the CS
address whenever a word writes Wrf[22]:
[SC13-BARRIER] CS 16554 writes SC13 := 00000000
The writer is CS 0o16554, and its DEST field really is SC13 (0o26). Nothing improper
happened. There is no unexplained write, no defect, and nothing to fix in the emulator.
The error was mine, and it is worth naming precisely. My probe logged State.Mpc before calling
Tick() and labelled it "the word that wrote". But State.Mpc is updated BEFORE the word
executes, so that address is the NEXT word, not the executing one. Every "CS X WROTE SC13" line in
probes 9 and 10 is off by one word - which is how a routine word (0o25571, D,NONE) got blamed for
a write made elsewhere, and how that turned into a reported "emulator anomaly".
Three consequences:
- Probe 10 is retracted in full (banner added above; text kept so the wrong claim is not
re-adopted). The
COND_ALUsuspicion of probe 8 was already withdrawn in probe 9. Two suspected emulator defects were raised this session and BOTH were my own measurement errors. - The G3 mechanism is now plain and involves no defect:
SC13is computed by the subroutine chain called from 0o25562, last written at 0o16554 with the value 0, and 0o25573 then branches on that zero straight to the return.OCB_CLNUPdeclines because the state it is asked about genuinely says "nothing to clean up". - The test's log wording is fixed so it can never mislabel again - it now prints "Mpc now X = NEXT word, NOT the writer" and carries a comment explaining what the false report cost.
Method lesson, recorded because it burned three probes: in this emulator, do not read
State.Mpc as "the instruction that just ran". Attribute writes with an in-CPU barrier, not by
sampling the program counter around Tick().
STRENGTHENED BY PROBE 18 - and this entry's own conclusion is partly retracted there.
State.Mpcis unreliable INSIDE the barrier too, not merely aroundTick(): probe 18 shows a write whose executing word hasDEST = SC13while the word stored at the reportedMpchasDEST = NONE. So "the real writer is CS 0o16554" below is NOT established - it rested on a coincidence. The safe signal isword.Deston the executing word; the ADDRESS needs a facility the CPU does not currently expose.CAUSE FOUND IN PROBE 19 - and probe 18's explanation was wrong.
State.MpcDOES identify the fetched word. The reason a write can report a mismatchedMpcis the SNEAK CYCLE:ExecuteBodyruns twice per tick, once for the fetched word and once for the word at itsABS_ADDRwhenEXUCis set, and BOTH report the sameMpc. No CPU facility is needed - the rule is simply that a write reported atMpcmay belong to the sneak word atCache.Words[word.AbsAddr]. CheckEXUCon the fetched word before attributing anything.
G3 status: the exit is fully explained and correct. What remains is unchanged and unblocked -
to see the body run, SC13 must be non-zero at 0o25573, which means setting up whatever 0o16554's
subroutine reads. That is real mailbox state, not a harness trick.
G3 probe 12, 2026-07-31 - 0o16554 is SCAN_ACCP. Delivering an AOB word does NOT open the gate.¶
The writer is identified by an exact label match, not by proximity: MICRO-5800-B30.LABE lists
SCAN_ACCP 016554*. So the value 0o25573 branches on is the ACCP scan result, and the
neighbouring labels (SCAN_ACCP1 0o16560, SCAN_ACCP2 0o16562, SCAN_ACCP3 0o16564) confirm the
routine starts exactly there.
That gives a clean hypothesis: our StubAccpController presents an idle ACCP, SCAN_ACCP writes
SC13 := 0, and OCB_CLNUP returns because there is nothing to clean up.
Tested by delivering an asynchronous word into AOB with ATRAP asserted - the shape of a forwarded octobus kick - and re-running:
accpDelivers reachedBody SC13@branch N5STA
False False 00000000 0002
True False 00000000 0002
Negative. SC13 is still zero at the branch and the body is still not reached. So delivering
into AOB is not what SCAN_ACCP inspects. The hypothesis is NOT confirmed, and is recorded as such
rather than being quietly upgraded because the label match looked convincing.
What this does and does not establish:
- Established: the gate value is produced by
SCAN_ACCP, byte-verified by label. - NOT established: that ACCP activity of any kind flips it. One delivery shape was tried and it did nothing.
Next: SCAN_ACCP most likely tests AFLAG bits rather than AOB occupancy (AFLAG is the ACCP
status register, and this file already carries "AFLAG bits 7/8 unknown" as an open item). Decode
0o16554-0o16564 for which flag it reads, then drive that bit rather than guessing at delivery
shapes. Test: MailboxClrKickTests.OcbClnup_WithBusyAccp_ChecksWhetherScanAccpIsTheGate.
G3 probe 13, 2026-07-31 - SC13 := AFLAG, and our AFLAG CANNOT satisfy the scan. [DEFECT]¶
SCAN_ACCP decoded (0o16554 onward):
| CS | A operand | DEST | test | branch |
|---|---|---|---|---|
| 16554 | A,SPEC,AFLAG |
D,SC13 |
- | -> 16555 |
| 16555 | A,BM13 (mask bit 11) |
NONE | - | -> 16556 |
| 16556 | A,BM14 (mask bit 12) |
NONE | COND,MZRO |
-> 16560 |
| 16560 | A,BM05 (mask bit 5) |
NONE | COND,MZRO |
-> 16562 |
| 16562 | A,BM06 (mask bit 6) |
NONE | COND,MZRO |
-> 16564 |
| 16564 | A,BM00 |
NONE | COND,MZRO |
-> 104 (return) |
So SC13 IS the AFLAG word, byte-verified: 0o16554 reads A,SPEC,AFLAG straight into SC13.
That fully explains OCB_CLNUP: 0o25573 tests SC13 for zero, so an ACCP with AFLAG == 0 makes
the routine return immediately. The whole chain from G3's symptom to its cause is now closed.
And here is a REAL emulator gap - visible in the source, not inferred from a measurement.
AccessModule.ReadAflag() is:
return ((uint)AobFull << AobfBit) | ((uint)AibFull << AibfBit); // AOBF bit 9, AIBF bit 10
It can only ever produce bits 9 and 10. SCAN_ACCP tests bits 5, 6, 11 and 12. Those four
bit tests can NEVER fire in our emulator, whatever the ACCP stub does. That is why probe 12's
delivered AOB word changed nothing: it sets bit 9, which SCAN_ACCP does not look at.
This is a genuine defect and this time it is not a measurement artefact - it is a two-line
method that structurally cannot produce the bits the microcode reads. Contrast the two false alarms
earlier in this file (COND_ALU, the "unexplained SC13 write"), both of which were my own errors:
here the evidence is the source text itself plus a byte-verified decode of the reader.
Scope beyond G3: SCAN_ACCP is called from the OCB_WAITSEX spin as well (catalog correction
3 - "SCAN_ACCP each pass"), so every path that polls the ACCP is scanning a status word that can
never report four of its conditions.
This also extends the existing "AFLAG bits 7/8 unknown" open item - the unmodelled set is wider
than bits 7/8. Known real bits now: 9 AOBF, 10 AIBF (modelled); 5, 6, 11, 12 read by SCAN_ACCP
(unmodelled); 7, 8 previously flagged unknown.
Next: find what the real ACCP asserts in AFLAG bits 5, 6, 11, 12 before implementing anything -
ND-05.020.01 section 5.1.3 is the AFLAG reference already cited in AccessModule, and the ACCP
firmware carve (ACCP-COMPLETE-REFERENCE.md) may name them from the other side. Do not invent
bit meanings to make the scan pass.
G3 probe 14, 2026-07-31 - AFLAG dispatch bits IMPLEMENTED from the verified map. Gate still shut.¶
The bit meanings did not need inventing - they were already carved.
ACCP-COMPLETE-REFERENCE.md "AFLAG bit map [V, but see the warning]" has all of them, and it
independently confirms this session's decode (it states the same octal BM naming: BM05 = bit 5,
BM13 = bit 11, BM14 = bit 12):
| Bit | Meaning | Confidence |
|---|---|---|
| 5 | async-trap word pending (TRAP_OCBA / TRAP_ATRP) |
[V] |
| 6 | other trap (TRAP_OTRP, NOTREC 210) |
[V] |
| 7 | data-fault indication | [OPEN] |
| 8 | instruction-fault indication | [OPEN] |
| 9 | AOB has data | [V] |
| 10 | AIB busy | [V] |
| 11 | power-fail warning (TRAP_PWF) |
[V] |
| 12 | OCB kick / message pending (TRAP_OCBAK / TRAP_OMESS) |
[V] |
Implemented in AccessModule: bits 5, 6, 11, 12 are now composed by ReadAflag alongside the
existing 9 and 10, with the provenance and the OCB_CLNUP consequence in the XML docs. Bits 7 and
8 were deliberately left out - the source marks them [OPEN], never re-verified after an
off-by-one correction shifted every dispatch bit. Adding them to make a poll succeed is exactly the
failure mode this interface already suffered.
This is a real fix regardless of G3: SCAN_ACCP runs on every pass of the OCB_WAITSEX spin
too, so until now every ACCP poll in the emulator read a status word that could not report four of
its conditions.
But the gate is still shut, and this is NOT explained:
accpDelivers reachedBody SC13@branch N5STA
False False 00000000 0002
AFLAG after setting OcbPending = 00001000 (expect bit 12 = 0x1000)
True False 00000000 0002
ReadAflag() demonstrably returns 0x1000 (checked in-run, so the change is live and not a stale
build), yet SC13 is still 0 when 0o25573 executes. Given that an in-CPU barrier showed
SCAN_ACCP is the ONLY writer of SC13 in this run, SC13 should hold 0x1000.
Not guessed at. Plausible directions, none tested: the busy run may take a different route
(dispatching TRAP_OCBAK) and arrive at 0o25573 having re-scanned an AFLAG that was cleared by the
dispatch; or SCAN_ACCP may run before the flag is visible on that path. The next step is to log
SC13 changes and the CS path for the busy run specifically - attributing writes with an in-CPU
barrier, per the method lesson in probe 11, not by sampling State.Mpc.
Regression check after the src change: mailbox + ACCP + octobus fixtures 50/50 green.
G3 probe 15, 2026-07-31 - ANSWERED. We enter PAST the ACCP scan, so OCB_CLNUP runs before it.¶
Correlating AFLAG against SC13 every tick of the busy run (test-side only - CpuND5000.cs is
carrying someone else's work and was not touched):
tick 0: AFLAG=00001000 SC13=00000000
tick 7: AFLAG=00001000 SC13=00000800
tick 33: AFLAG=00001000 SC13=00000000
tick 57: AFLAG=00001000 SC13=80000000
tick 76: AFLAG=00001000 SC13=00000000
tick 83: AFLAG=00001000 SC13=00002000
tick 88: AFLAG=00001000 SC13=00000000
tick 137: AFLAG=00001000 SC13=00002000
tick 142: AFLAG=00001000 SC13=00000000
tick 159: AFLAG=00001000 SC13=00001000 <-- SCAN_ACCP finally reads AFLAG
tick 178: AFLAG=00001000 SC13=00000200 <-- ACCP_READ's SC13 := BM11 (bit 9)
Three things this settles:
AFLAGis never consumed - it holds0x1000for the entire run. The flag is not being cleared behind our back, which was one of the two guesses in probe 14. That guess is dead.SCAN_ACCPDOES work now - at tick 159 it readsAFLAGand writesSC13 = 0x1000, exactly as the probe-13 decode predicted. The AFLAG fix is confirmed working end to end.- But it runs FAR TOO LATE.
OCB_CLNUPexecutes and exits in the first ~33 ticks. The branch sample is the FIRST visit to 0o25573, which happens long before any ACCP scan.
Root cause, and it is a HARNESS issue, not a microcode or emulator one: these probes set
Mpc = OCB_KICK06 and jump straight in. On real hardware a kick arrives through the trap dispatch
(TRAP_OCBA / OCB_DECODE), which runs SCAN_ACCP FIRST and only then dispatches to the kick
handler. By entering at the handler we skip the scan, so SC13 still holds whatever earlier code
left - zero - when OCB_CLNUP tests it.
Corroboration that the later values are ordinary scratch traffic and not noise: SC13 = 0x200 at
tick 178 is BM11 = bit 9, which the catalog records as ACCP_READ's opening SC13 := BM11.
Note on the superficially similar dead hypothesis. Probe 8 guessed "the harness enters cold with
zeroed scratch" and was REFUTED - presetting SC13 changed nothing. This is a different and
specific claim, and it is evidenced rather than assumed: the scan that computes SC13 is bypassed
by our entry point, and we can see it running correctly 126 ticks later.
Next: drive the kick through the trap dispatch instead of jumping to OCB_KICK06 - deliver the
kick with AFLAG bit 12 set and start from the ACCP trap entry, so SCAN_ACCP runs before
OCB_CLNUP in the natural order. That is the setup in which the carve's N5STA := 1 can finally be
observed or refuted.
G3 probe 16, 2026-07-31 - the trap entry does NOT reach OCB_CLNUP. Recommend PARKING G3.¶
Entered at both trap addresses that precede SCAN_ACCP, with AFLAG bit 12 set and a message laid
down:
entry reachedClnup reachedBody SC13@clnup N5STA
16550 False False 00000000 0002
16552 False False 00000000 0002
OCB_CLNUP is never reached from either entry. So the trap path with only bit 12 asserted does
not dispatch to kick 6 - it goes elsewhere, almost certainly TRAP_OMESS / OCB_DECODE, which need
an actual kick WORD in AOB to decode, not merely a pending flag. Both entries were tried precisely
so that a wrong single choice could not masquerade as a null result.
Honest assessment of where G3 stands.
Fully settled, and none of it needs revisiting:
- OCB_CLNUP runs 0o25570-0o25573 and returns; the body 0o25574-0o25604 is never entered.
- It exits at 0o25573 on COND,MZRO testing SC13.
- SC13 is the AFLAG word, loaded by SCAN_ACCP at 0o16554 (A,SPEC,AFLAG -> D,SC13).
- DPA := ADR_MESS at 0o25572 is confirmed, so the carve's description is sound where reachable.
- A real emulator gap was found and fixed on the way: AFLAG now composes bits 5, 6, 11, 12.
Not settled: whether the body writes N5STA := 1. Observing it needs a COMPLETE kick driven
through the ACCP - a decodable kick word in AOB, the right trap bits, and the dispatch reaching
kick 4/5/6 - which is a substantially bigger harness than any probe here.
RECOMMENDATION: park G3 as "mechanism understood, body-observation deferred". Six probes have now gone at reaching the body from different angles and each produced a real narrowing but no observation. The remaining question is worth answering with a full kick-injection harness, not more entry-point guessing - and that harness is the same one G5 (kicks 4/5) will need, so it should be built once, deliberately, rather than approximated again.
Do not, in the meantime, implement N5STA := 1 in the station. It stays unobserved. The
existing guard test (OcbKick06_WithMessage_ShowsWhatOcbClnupDoesToIt) already prevents the
opposite error of consuming the message.
G3 probe 17, 2026-07-31 - kick-injection harness BUILT and kicks 4/5/6 reach OCB_CLNUP¶
The harness works and is validated against an oracle. Delivering a framed kick word into
AOB with ATRAP and AFLAG bit 12, entering at TRAP_OMESS (0o16412), dispatches correctly:
entry reachedKick03 X5CLR X5CCL X5PRO
TRAP_OMESS 016412 True 0000 0001 FFFF
OCB_DECODE 016417 True 0000 0001 FFFF
OCB_MES_K 016424 True 0000 0001 FFFF
TRAP_OCBA 016550 False 003F 0000 0000
That is the full documented CLRKICK acknowledge, reproduced end to end - so the injection path is proven, not assumed.
Kick words are FRAMED, not bare - the first attempt used 0x0003 and dispatched nowhere.
OCB_MES_K fast-paths an exact match against 0o100501 (0x8141) and OCB_DEC_K indexes on
word AND 0o77, so a kick word is 0o1005nn. Independently corroborated by a real captured frame:
this register already recorded lastKickFrame=0xB843 delivered as 0x8143 with info = 3.
Building the harness around a KNOWN kick is what surfaced this immediately instead of yielding a
plausible null result for 4/5/6.
Driving kicks 4, 5 and 6 through it:
kick word reachedClnup reachedBody N5STA state
4 8144 True False 0002 SC13@branch=00000000 AFLAG=1000
5 8145 True False 0002 SC13@branch=00000000 AFLAG=1000
6 8146 True False 0002 SC13@branch=00000000 AFLAG=1000
All three now REACH OCB_CLNUP through a real dispatch - the first time that has happened.
The body is still not entered and N5STA is untouched (never 3, so nothing consumes the message).
CORRECTS PROBE 13's FRAMING. AFLAG is 0x1000 at the branch - the flag IS available - and
SC13 is still 0. So while SCAN_ACCP at 0o16554 genuinely does SC13 := AFLAG, the value
OCB_CLNUP tests is NOT a fresh AFLAG read. SC13 is a general-purpose scratch (600 writers)
and by the time 0o25573 executes it holds 0 written by something in between. "SC13 is the AFLAG
word" is right about SCAN_ACCP's write and wrong as an explanation of the branch value.
So the real gate is: whatever leaves SC13 non-zero immediately before 0o25570. That is now a
sharply defined question - and answering it means finding the last SC13 writer on the kick path,
using an in-CPU barrier (per the method lesson in probe 11), not the counter.
Everything below the harness is now unblocked, including G5: kicks 4/5 can be driven and observed for the first time.
G3 probe 18, 2026-07-31 - State.Mpc DOES NOT IDENTIFY THE EXECUTING WORD. Attribution retracted.¶
Barriered the kick path on word.Dest == 22 (SC13) and printed, for each write, the Mpc, the
executing word's own DEST, and the DEST of the word stored at that Mpc:
[SC13] Mpc=25571 := 00000000 execDest=26 destOfWordAtMpc=30
[SC13] Mpc=16554 := 00001000 execDest=26 destOfWordAtMpc=26
Read the first line carefully. The executing word has DEST = 0o26 (SC13) - it genuinely is an
SC13 write - but the word STORED at 0o25571 has DEST = 0o30 (NONE). They are different words.
So State.Mpc, sampled inside the execute path, is not the address of the word being executed.
The second line coincides (26 and 26), which is exactly what makes this trap dangerous: the
attribution is right often enough to look reliable.
RETRACTION. Probe 11 concluded "the real writer is CS 0o16554, a word with a legitimate
DEST = SC13". That conclusion rested on this same coincidence and is not safe. What is
actually established is weaker and is all that should be carried forward:
- The last write to
SC13before 0o25573 sets it to 0, and the executing word legitimately hasDEST = SC13. No defect - that part of probe 11 stands. - Which ADDRESS that word occupies is UNKNOWN. It is not 0o25571 (wrong DEST), and 0o16554 cannot be trusted either since the same method produced both.
Method rule for this emulator, now demonstrated rather than assumed:
never derive a microword address from State.Mpc - not before Tick(), not inside the execute
path. It has now produced three separate wrong attributions in this investigation (probes 9, 10,
and 11's replacement claim). Distinguish a real SC13 write from a mis-attributed one by reading
word.Dest on the executing word, which IS reliable.
To name the address properly the CPU must expose the executing word's own address (a small
read-only addition next to the existing State.Mpc). That is a change to CpuND5000.cs, which is
carrying unrelated in-progress work, so it was not made. The diagnostic barrier used here was
temporary and src is reverted clean.
G3's remaining question is unchanged and still sharp: find what leaves SC13 non-zero
immediately before 0o25570. It now needs the executing-address facility to answer cleanly.
G3 probe 19, 2026-08-01 - ANSWERED. A SNEAK CYCLE zeroes SC13, so the exit is UNCONDITIONAL.¶
And probe 18's stated cause was wrong - State.Mpc DOES identify the fetched word (it is read at
CpuND5000.cs:311 and only updated at the end, line 832). The real mechanism is better:
ExecuteBody is called twice per tick. Once for the fetched word, and again at line 808 for a
sneak word - ExecuteBody(in sneak, ...) - when EXUC is set. The sneak word is the one at the
fetched word's ABS_ADDR, and its own DEST deposits its value. Both executions report the same
State.Mpc, which is exactly why the attribution looked broken.
Decoded, and it fits perfectly:
CS 0o25571 EXUC=1 DEST=0o30 (NONE) ABS_ADDR=0o25
word at 0o25 EXUC=0 DEST=0o26 (SC13) A_OP=0o357 (A,LARG) LARG = 0x0
So 0o25571 carries no destination of its own, but its EXUC sneak-executes the constant word at
0o25, which writes the literal 0 into SC13. One word later, 0o25573 tests SC13 for zero
with COND,MZRO and branches to 0o104 - the return.
SC13 is zero there BY CONSTRUCTION, not by circumstance. No mailbox state, no ACCP flag and no
kick can change it. OCB_CLNUP's body at 0o25574..0o25604 is unreachable through the
OCB_KICK05 / OCB_KICK06 call path. That is why nineteen probes could not enter it.
Two readings, and this DOES have to be flagged:
- The body is genuinely dead on this path, and the carve's
N5STA := 1belongs to some other entry into 0o25574 (a direct jump), or the carve mis-described the routine. - Our sneak-cycle model runs the sneak when real hardware would not. The model is
EXUC != 0plus a sequencer-type rule, calibrated against ONE site (SHIFT_ROT@017070, per the comment atCpuND5000.cs:788-796). If that rule over-fires here, real hardware leavesSC13alone and the body IS reachable.
Reading 2 cannot be dismissed and is now the higher-value question - it is a CPU-model question
affecting every EXUC word in the image, not a mailbox question. G3 has effectively handed off to
it.
Practical consequence, unchanged and now firmly grounded: do NOT implement N5STA := 1 in the
station. Under our current CPU model the microcode provably never performs it via kicks 4/5/6.
G3 probe 20, 2026-08-01 - the constant-word sneak is a PERVASIVE IDIOM, not a one-off¶
Reading 2 above (our sneak model over-fires) is now much weaker. Sweeping the whole B30 image:
| Measure | Count |
|---|---|
Words with EXUC set |
345 |
...whose sneak target is a LARG-to-WRF constant word |
51 |
| ...of those, the deposited constant is zero | 21 |
| Distinct constant words used as sneak targets | 9 |
Uses of the constant word at 0o25 (the one in OCB_CLNUP) |
18 |
Low CS addresses hold a shared CONSTANT POOL, and EXUC words across the image sneak-execute
them to deposit values into registers. That is precisely the "CPU_READ constant-word trick" the
source comment at CpuND5000.cs:780-786 describes, cited to ND-05.022.1 section 7.2. Nine constants
serving 51 sites is a compiler-style shared pool, not an accident of decoding.
So 0o25571 clearing SC13 via the sneak at 0o25 is ORDINARY - it is the same
"clear this scratch" idiom 17 other sites use. Our emulator is reproducing a real, documented and
heavily-exercised mechanism, not misfiring on one word.
The sequencer condition also checks out: 0o25571 is unconditional (COND_SEQ = 0) with
SEQ_TRUE = 12 -> type 3, which is not the one-cycle JMP that the manual says has no sneak. So
the sneak SHOULD fire here by the documented rule.
Conclusion: reading 1 is now much the stronger. OCB_CLNUP's body is genuinely unreachable on
the kick path in B30 - the routine clears SC13 and immediately tests it, so the early exit is
structural. The carve's N5STA := 1 must belong to a different entry into 0o25574, or the routine
is effectively disabled in this microcode revision.
Not claimed: that reading 2 is impossible. Confirming the sneak against real hardware is not something this project can do, and the sneak rule's exact firing condition was calibrated against one site. But the burden has clearly shifted - our model now matches a pattern with 51 instances, and a single-site calibration explaining all 51 by accident is not credible.
Note on the harness: this iteration was blocked for a while by an unrelated uncommitted edit to
src/CpuND5000.cs (a _aapProductForQ field never assigned, CS0649-as-error). Not ours; left
alone; it compiles again now.
G4 - kick 2 not mapped to ACTIVATE [P2]¶
OCB_DEC_K sends both 1 and 2 to ACTIVATE. We handle only 1. No NPL sender for kick 2 was
found, so nothing known breaks - but the mapping is one line and its absence is a silent
divergence from the table.
G5 - kicks 4/5 (OCB_KICK05) not implemented [P2]¶
OCB_KICK05 (025553, shared by kicks 4 and 5; there is no separate OCB_KICK04): SET_IDLE,
LOCK_QUE, OCB_CLNUP, UNLOCK_QUE, PRNOWR(0), NOTREC 204 - a stop-and-clean-queue. No NPL
sender found, so this is unproven-need; do not build it ahead of a caller. Listed for completeness
of the table. Depends on G3.
MEASURED 2026-07-31 - the contract is no longer inferred. STILL NOT IMPLEMENTED. Driving real kicks through the injection harness, with kick 3 as a control whose outcome is independently known:
kick X5ACT X5PRO X5CLR X5CCL hdrLock
3 FFFF FFFF 0000 0001 00000000 CONTROL - passes (X5CLR=0, X5CCL=1, X5PRO=-1)
4 FFFF FFFF 003F 0000 00000000 OCB_KICK05
5 FFFF FFFF 003F 0000 00000000 OCB_KICK05
6 FFFF FFFF 003F 0000 00000000 OCB_KICK06
X5PRO was preset to 5 before each run. What the microcode actually does:
- Kicks 4, 5 and 6 all set
X5PRO := -1(ND-500 IDLE). That isSET_IDLE, now observed rather than read off a listing. - They do NOT touch
X5CLRorX5CCL-X5CLRkeeps the0o77mask it was given andX5CCLstays 0. The cache-clear cells belong to kick 3 alone. A future kick-4/5 implementation that writes them would be wrong. - The queue-lock word ends 0 in every case, including kick 3. Consistent with the G6 finding that our station has no lock to release.
Independently corroborates the G2 fix. G2 was implemented on the reasoning that kick 6 writes
X5PRO := -1 so TER51 completes instead of falling into ESPTIMOUT. The real microcode confirms
it - a fix that was justified by inference now has an execution behind it.
Caveat, stated rather than glossed: these runs enter through the trap dispatch, so X5PRO := -1
is attributable to the kick path as a whole. Nothing here isolates it to OCB_KICK05 itself as
opposed to SET_IDLE earlier in that path. The observable contract is what matters for the station
and that is what is recorded.
Still do NOT implement kicks 4/5. The "no known NPL sender" objection is untouched by this - knowing what a kick does is not evidence that anything sends it. This entry now says what to build IF a caller appears.
G6 - unrecognised kicks are silently swallowed [P3]¶
Kick 0 and kicks 7-63 must reach NOTREC (OCB_KICK64 = UNLOCK_QUE + NOTREC 204), i.e. the
real machine reports an unrecognised kick and releases the queue lock. Ours drops them with no
diagnostic (a DEBUG_DETAIL-only log for disabled kicks, nothing for unhandled numbers).
Two consequences: a wrong kick number is undiagnosable, and the UNLOCK_QUE is skipped, so a
queue lock taken before a bad kick would leak.
Fix: default branch that logs the kick number unconditionally, plus the queue unlock.
STATUS 2026-07-31 - diagnostic half CLOSED, unlock half NOT APPLICABLE. Do not "fix" it.
- Logging: DONE.
OctobusND5000Stationhas adefault:branch that logs every unhandled kick unconditionally, naming what the microcode would have run (OCB_KICK05,OCB_KICK06, orNOTREC 204). The "undiagnosable wrong kick number" consequence is gone. -
UNLOCK_QUE: NOT APPLICABLE, and implementing it now would be a DEFECT. Checked the whole station:_mailboxHeaderBaseis only ever compared against 0 and handed to the servicer - the station never reads or writes the queue-lock word, so it never TAKES the lock. There is therefore nothing to leak and nothing to release.Writing an unconditional unlock would be actively harmful: the lock cell is shared with SINTRAN, so releasing a lock we never acquired could clear a lock SINTRAN currently holds. The original entry's "the UNLOCK_QUE is skipped, so a queue lock taken before a bad kick would leak" assumed our station takes the lock. It does not.
Correct scoping: the unlock is owed BY whoever implements queue locking (G3's kick-4/5/6 work), as part of that change - not as a standalone fix here.
G7 - NOT A GAP. Withdrawn 2026-07-30, my claim was wrong¶
X5ACT IS re-armed: OctobusND5000Station.cs:1163,
WriteNd100Word(_extBlockBase + 5 * 2, 1); // re-arm (IDLE_2 writes 1 [V]), covered by the
passing test OctobusMailboxO1Tests.IdlePath_ReArmsX5ActToOne_BeforeConsuming. The real
microcode agrees - running the B30 IDLE loop leaves X5ACT=0001.
How I got it wrong: I searched for the byte offset 0x0A and concluded "nothing writes it".
The code writes 5 * 2. A grep for one spelling of an address is not evidence that nothing
touches it - check the symbolic form too, or assert on the VALUE instead of searching for the
write.
Original (incorrect) text follows for the record.
G7 - X5ACT is never re-armed [WITHDRAWN - see above]¶
The microcode's IDLE loop, on finding work, re-arms X5ACT to 1 and then walks the X5BEX
chain. I found no write to _extBlockBase + 0x0A anywhere in OctobusND5000Station.cs - the cell
is read, hooked and logged only.
Why it may work anyway today: on the CS-derived path OnMpmActivationWrite triggers on a write to
the cell, not on a 0xFFFF -> 0 transition, so a repeated 0 write still wakes us. The
wasMinusOne fallback path, though, requires a real 0xFFFF -> 0, and that path cannot fire twice
if nothing ever restores the cell.
Graded [P2, verify] rather than asserted: I have not proven a SINTRAN path that breaks. The
check is cheap - assert X5ACT after a walk in the existing mailbox tests.
G8 - X5CCL (cache-clear counter) never written [P2]¶
X5CCL (word 0o11, byte +0x12) is documented as "cache-clear counter (read/compared)" and the
executed microcode sets it to 1 during OCB_KICK03. We never write it. Subsumed by the G1 fix
(section 0.1), listed separately because any other cache-clear path owes the same write.
STATUS 2026-07-31 - CLOSED for every path that exists today. Audited the station: X5CCL
(X5CclByteOffset = 0x12) is written in exactly one place, ExecuteClearFunctions, the kick-3
handler - and kick 3 is the ONLY cache-clear path the station implements. So the "any other
cache-clear path" caveat has no current subject.
It becomes live again the moment kicks 4/5 land (G5): OCB_KICK05 runs the same clear
sequence, so that implementation owes the X5CCL write too. Recorded here rather than closed
silently, so G5's author inherits the requirement instead of rediscovering it from a SINTRAN poll
that never terminates.
G9 - OCB_WAITSEX trigger condition ~~unknown~~ CONFIRMED 2026-08-01. Bit 15 arms it.¶
~~OCB_WAITSEX (025543) spins on global header word 0o24 (byte 0x28) until zero. The carve says
it is armed when the original X5CLR had bit 15 set, but running the real microcode with
X5CLR = 0x803F produced the same 26 writes and did not enter the spin, so the stated trigger is
not confirmed.~~
The carved trigger is RIGHT, and the earlier "not confirmed" was a DETECTION FAILURE.
OCB_WAITSEX spins on global header word 0o24 (byte 0x28) until zero - and every setup in
this fixture pre-zeroes that word. A spin whose exit condition is already true is entered and left
in the same breath, which is indistinguishable from never entering it. The earlier probe could not
have seen the spin whatever the mask was.
Varying the mask bit and the spin cell independently, driving a real CLRKICK through the injection harness:
mask spinCell reachedWaitsex finalX5CLR spinCellAfter
003F zero False 0000 00000000
803F zero True 0000 00000000
003F nonzero False 0000 00000001
803F nonzero True 0000 00000001
X5CLR bit 15 arms OCB_WAITSEX, cleanly and in both cell states. Bit 15 clear never reaches
it. That is the carve's stated trigger, now executed rather than read.
Two further observations from the same table:
- The acknowledge still completes:
X5CLRends0000in all four rows, so arming the spin does not cost theST0PSYSack. - The spin does not clear its own cell -
spinCellAfterstays00000001. Whatever zeroes header word0o24is external to this path, which is consistent with it being a wait-for-someone-else.
Still NOT on the critical path and still unimplemented: ST0PSYS's 0o77 never sets bit 15, so
nothing in the stop-system ladder arms this. It could matter for LMPCLR, whose mask is runtime
data. The trigger is no longer a guess, so an implementation would now be justified when a caller
appears - but no caller has been found, so none is written.
Lesson worth carrying: this is the second time in this investigation that a "did not happen"
was really "could not have been observed" (the first was OCB_CLNUP's body). Before recording a
negative, check the setup does not make the positive invisible.
1.1 Harness reproducibility bug - FIXED, and it invalidated one run¶
Nd100SintranNd5000OctobusBootHarnessTests.EnsureWorkingCopy refreshed the working pack only when
it was MISSING or a DIFFERENT SIZE. The pack is a fixed-size disk image, so after the first run the
copy never happened again - while SINTRAN WRITES to the pack during boot and login. State
accumulated run over run, and any damage was permanent.
Not theoretical: two runs of identical code gave a fully green ladder and then
nd-500=STALL status=STALL start-swapper=STALL with a garbage mailbox base of 0x7FFFF6 (the
ND-500 monitor never came up at all). Forcing an unconditional copy restored the green ladder
immediately, which confirms the diagnosis.
Fixed: the working copy is now always overwritten from source in SetUp. Costs a second or two
against a multi-minute boot. A harness whose starting state depends on how many times it has been
run cannot be used as evidence about the machine, which is the entire purpose of the fixture.
2. Not gaps - recorded so they are not re-opened¶
ST0PSYS's wait loop cannot hang.Lruns-1000 -> 0; worst case isERRFATAL.stop-systemitself works. The historicalSTALLwas an unmatchable harness marker, not the machine. Full ladder green - see the companion doc.- Kick 1 is never sent during a normal boot, and that is correct. Activation is the
X5ACT := 0write (ACT51); the kick is the PREEMPT path only.TxKickCountwas 0 for the whole run untilstop-system. - The
_accpIdlekick drop is correct behaviour. A terminated ACCP (244B) has a stopped microprogram, and kicks are delivered to the microprogram. Do not "fix" it to let kicks through. LMPCLRdoes not pollX5CLR. It writes, kicks, and answers OK (OKMONICO/XACTRDY). That is why G1 is silent on the swapper path and only surfaces at shutdown.- The ND-500 (3022) path does not share these gaps. It runs different microcode from the ND-5000, and we have no ND-500 microcode yet (expected within weeks as of 2026-07-30). Octobus is the focus; do not start ND-500-side microcode work on the strength of this document.
3. Order of work¶
Done 2026-07-30: G1, G4, G6 (diagnostic half), G10, G2, plus the harness-reproducibility fix below. G7 withdrawn - it was never a gap.
- G10 (top item):
_accpIdlewas cleared only byContinueAccpandResetStation, never bySTAMIC0/CONTMIC/RESTMIC. SINTRAN sends 244B TERMINATE as a NORMAL bring-up step (measured: after 3 commands, all answered; 0 of 149 unanswered in a whole run), then restarts the microprogram - and the flag stuck, so every kick was dropped for the rest of the session. Verified:k3=1,X5CLR=0000,X5CCL=0001,accpIdle=False, full ladder green. - G2: kick 6 writes
X5PRO := -1, soTER51completes instead ofESPTIMOUT.
0. G10 - DONE. Was listed here as "first, until understood". It is understood and fixed: the
244B TERMINATE is a normal bring-up step, and the defect was _accpIdle never being cleared on the
microprogram-restart paths. See the corrected G10 entry above.
Remaining, in order:
- G7 (verify
X5ACTre-arm) - one assertion in the existing mailbox tests; either closes the gap or promotes it to a defect. - G2 (kick 6) -
X5PRO := -1at minimum, which is whatTER51actually polls. Full fidelity needs G3. This is the last known P1. - G3 (
OCB_CLNUPrequeue) - prerequisite for an honest G2/G5. Getting this wrong shows up much later as lost messages, so do not fake it. - ~~G6 remainder - the
UNLOCK_QUEthatOCB_KICK64performs~~ - CLOSED 2026-07-31. Logging was already done; the unlock is NOT owed here because the station never takes the lock, and releasing one we do not hold could clear SINTRAN's. It belongs to whoever implements queue locking. See the G6 entry. - ~~G8~~ - CLOSED 2026-07-31 for every path that exists; kick 3 is the only cache-clear
path and it writes
X5CCL. Re-opens as a requirement on G5 when kicks 4/5 land. - G5 - do NOT build ahead of a proven caller. Its CONTRACT is now measured (see the G5
entry): kicks 4/5/6 set
X5PRO := -1and leaveX5CLR/X5CCLalone. Build only if a sender turns up. G9 - trigger RESOLVED 2026-08-01:X5CLRbit 15 armsOCB_WAITSEX, executed not inferred. Still unimplemented because no caller sets bit 15 on any known path, but the "do not guess" caveat no longer applies - the guess has been replaced by a measurement.
4. Sources¶
SINTRAN/NPL-SOURCE/NPL/MP-P2-N500.NPL-ST0PSYS@3759,LMPCLR@1222,TER51@2950 (145230),ACT51/ACT52@3012/3032,XKICK500@3278SINTRAN/NPL-SOURCE/SYMBOLS/L07/N500-SYMBOLS.SYMB.TXT-X5CPU=4 X5ACT=5 X5PRO=6 X5CLR=0o10 X5CCL=0o11,MPACT=1,5ALIV=0o15SINTRAN/ND5000/ND5800-MICROCODE-ACCP-OCTOBUS-CATALOG.md-OCB_DEC_Ktable, kick handlers,OCB_CLNUPMICRO-5800-B30.DATA+MICRO-5800-B30.LABE(inRetroCore\Nuget\HackerCorpLabs.Emulation.CPU.ND5000\tests\MC\) - executed oracle + label xrefE:\Dev\Repos\Ronny\RetroCore\Nuget\HackerCorpLabs.Emulation.CPU.ND5000\tests\MailboxClrKickTests.cs- the executed kick-3 / kick-6 testsE:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\NDBUS\OctobusND5000Station.cs:1330-1367- the kick handler as it stands