# Ch303 closeout — caller-loop autopsy; verdict `channel_loop_returns_ignored` **Status:** Closed. Observation-only chapter per Codex's framing. No RTL, no new HLE cases. **Named verdict:** `channel_loop_returns_ignored` for the syscall 0x6B path. The disassembly also revealed the complete bounded set of remaining syscall-wrapper functions, which lets Ch304 batch with confidence. ## Key structural finding: these are wrapper TABLES, not loops The regions Codex pointed at are **tables of 4-instruction syscall-wrapper leaf functions**, each of the form: ``` addiu $v1, $zero, syscall jr $ra nop ``` ### Table 1 — `0x00111D40..0x00111D9C` | Wrapper PC | $v1 set | syscall | status | |------------|---------|---------|--------| | 0x00111D40 | -67 (0xFFFFFFBD) | i-variant of 67 (0x43) | **unhandled** | | 0x00111D50 | 68 (0x44) | 0x44 | **unhandled** | | 0x00111D60 | 107 (0x6B) | 0x6B | **current blocker** | | 0x00111D70 | 118 (0x76) | 0x76 | **unhandled** | | 0x00111D80 | 119 (0x77) | 0x77 | done Ch297 | | 0x00111D90 | 121 (0x79) | 0x79 | done Ch296 | ### Table 2 — `0x00112A50..0x00112A8C` | Wrapper PC | $v1 set | syscall | status | |------------|---------|---------|--------| | 0x00112A50 | 18 (0x12) | 0x12 | done Ch290 | | 0x00112A60 | 19 (0x13) | 0x13 | done Ch302 | | 0x00112A70 | 22 (0x16) | 0x16 | done Ch291 | | 0x00112A80 | 23 (0x17) | 0x17 | done Ch301 | **Table 2 is fully handled.** Table 1 has **4 remaining**: the i-variant -67, 0x44, 0x6B, 0x76. ## The 0x6B caller IGNORES the return value The immediate caller of the 0x6B wrapper is a function at `0x00111B00`: ``` 0x111b00: daddu $s1, $a0, $zero ; save $a0 0x111b08: daddu $s0, $a1, $zero ; save $a1 0x111b0c: lw $v0, -16392($v1) ; load a counter 0x111b10: addiu $v0, $v0, 1 ; ++counter 0x111b14: jal 0x00111330 ; helper 0x111b18: sw $v0, -16392($v1) ; store counter (delay slot) 0x111b1c: jal 0x00111d60 ; ← call syscall_0x6B wrapper 0x111b20: nop ; (delay slot) 0x111b24: daddu $a1, $zero, $zero ; $a1 = 0 ← does NOT read $v0 0x111b28: addiu $a2, $zero, 112 ; $a2 = 112 0x111b2c: jal 0x00110b88 ; next call (args set, $v0 ignored) 0x111b30: daddu $a0, $sp, $zero ; (delay slot) ``` **After `jal 0x00111d60` returns, the very next real instruction (0x111b24) overwrites $a1 with 0 and sets up args for a different call — `$v0` from the 0x6B syscall is never tested or consumed.** → For the 0x6B path: `channel_loop_returns_ignored`. ## The 0x112A00 driver IS a loop, and it DOES capture $v0 For completeness (Codex asked about loop shape generally), the function at `0x00112A00` is a genuine loop: ``` 0x112a00: jal 0x00112a80 ; call syscall_0x17 wrapper 0x112a04: daddu $a0, $s1, $zero ; (delay) $a0 = $s1 0x112a08: daddu $s1, $v0, $zero ; $s1 = $v0 ← CAPTURES return 0x112a0c: sync 0x112a10: bne $s0, $zero, 0x112a30 ; loop-control on $s0 (NOT $v0) 0x112a18: daddu $v0, $s1, $zero ; return $s1 0x112a28: jr $ra ... 0x112a30: jal 0x00111c60 0x112a38: beq $zero, $zero, 0x112a1c 0x112a40: jal 0x00111c10 0x112a48: beq $zero, $zero, 0x112a00 ; ← loop back to top ``` So this loop **captures $v0 into $s1** and threads it forward (as $a0 for the next iteration, or as the function's return value). However: - It drives **syscall 0x17** (already HLE'd to return 0). - qbert **progressed +87 retires** with that 0 return — so a 0 return is tolerated here. - The loop EXIT is gated by `$s0` (0x112a10 `bne $s0,$0`), not by the syscall return value. So even the loop that *captures* $v0 doesn't *gate* on it — it just propagates it. Returning 0 is consistent with observed forward progress. ## $a0=5 is constant — NOT a per-channel iteration Across every observed call (0x17, 0x13, 0x6B), `$a0 = 5` is constant. If this were a `for (ch=0..N)` loop, $a0 would vary. It doesn't. **This is channel-5-specific initialization, not a loop over all channels.** The `count=2` for 0x17/0x13 comes from the 0x112A00 driver looping twice (gated by $s0), not from iterating channel ids. ## Verdict, per Codex's enum | Verdict | Fit? | |---------|------| | `channel_loop_returns_ignored` | **YES (best)** — the 0x6B caller at 0x111B24 discards $v0; the 0x112A00 loop captures but tolerates 0. | | `channel_loop_checks_v0` | Partial — the 0x112A00 loop *captures* $v0, but doesn't *gate* on it (loop exit is on $s0), and 0 has been tolerated. | | `channel_loop_waits_on_event` | No — no spin; qbert progresses each chapter. | | `channel_loop_unbounded` | No — the wrapper tables are finite; remaining set is exactly {-67, 0x44, 0x6B, 0x76}. | | `channel_loop_shape_unknown` | No — fully decoded. | **Pick: `channel_loop_returns_ignored`.** The current blocker (0x6B) discards its return; the one loop that captures a syscall return ($v0→$s1) tolerates 0 and gates its exit on a different register. ## Ch304 framing — batch the bounded remaining set The autopsy proves the remaining unhandled syscalls in these init tables form a **finite, enumerable set of 4**: 1. **0x6B** (107) — current blocker, return ignored. 2. **0x76** (118) — same wrapper table, almost certainly same "ignored return" treatment. 3. **0x44** (68) — same table. 4. **-67 / 0xFFFFFFBD** — i-variant (interrupt-context) of syscall 67 (0x43). Negative-$v1 convention. Needs a dispatcher case matching the 32-bit value `0xFFFFFFBD` (or a "negative $v1 → treat as i-variant" decode if more i-variants surface). **Recommendation:** Ch304 adds `$v1 == 0x6B` → $v0=0 (the proven blocker). Then — given the autopsy shows the bounded set — **Ch305 could batch 0x76, 0x44, and the -67 i-variant** in one chapter, since they're all in the same wrapper table and the 0x6B caller pattern (ignored return) is representative. Per Codex's "prefer the closeout propose Ch304 rather than combine," I'm NOT adding any HLE case in Ch303. Ch304 = add 0x6B alone, confirm qbert reaches 0x76 (or 0x44 or -67) next, then Ch305 batches the rest if the pattern holds. One caution for the -67 i-variant: our dispatcher currently matches exact unsigned $v1 values. -67 arrives as $v1 = 0xFFFFFFBD. A naive `32'h0000_0043` case would NOT match it. The i-variant needs either its own `32'hFFFF_FFBD` case or a sign-aware decode. Flag for whoever frames the -67 chapter. ## Files - `/tmp/ch294_disasm.py` — disassembler retargeted across 0x00111D40, 0x00112A00, 0x00111B00, 0x00111300 windows. Same one-shot diagnostic. - This closeout. ## Pattern note — autopsy prevented trap-by-trap guessing This is the value Codex predicted: instead of discovering 0x6B, 0x76, 0x44, -67 one trap at a time across four chapters, the single caller-loop autopsy enumerated the complete remaining set AND established that returns are ignored. Ch304+Ch305 can now clear the whole init-table sequence in two chapters with confidence rather than four blind ones. ## Regression Unchanged at **177/177** — no RTL or TB changes in Ch303.