phase10: *** MILESTONE MET — 480 distinct bodies / 489 regions (target 475) ***

+6 bodies (worker A claims 23-26, worker B2 claims 8-9). Candidate gate MATCH
before every promotion.

FULL CLEAN AUDIT GREEN at the milestone:
  make clean && make all  exit 0
  cmp                     exit 0
  SHA-1 both files        e173426c157384ebf1b6caf8c6fea18a85a14af9
  registry                489 rows, 0 overlap, 0 unsorted, 0 bad extents,
                          0 missing sources, 480 distinct sources
  firewall                0 tracked paths under any prohibited root (583 files)
  suite                   237 tests OK

The phase goal (475 from the 400 baseline) is met with 5 bodies to spare.
Per the plan, phase close requires the developer's explicit confirmation of the
milestone; this commit records the state, not the close.

SIZE-BAND FINDING now confirmed a third time, within-worker: worker B2's nine
matches cost 1, 4, 6, 4, 1, 2, 2, 1, 1 attempts -- the three <=120 B frameless leaves
all cost exactly ONE attempt, while the two >200 B P1 rows it opened with cost 6 and
4 attempts and produced ZERO matches.

Two more levers recorded from the closing rows:
  - RECORD IDENTITY FROM WIDTHS (0x80041610): the two arms read three shorts at
    +264/+266/+268 versus three ints at +20/+24/+28 through one extra indirection,
    so they are TWO record types; declaring one shared type would have been wrong.
  - a1[1] = -a1[1] RELOADS FROM MEMORY and its source order matters: the negation
    reloads 4(a1) because the intervening store to 8(a1) may alias it.
  - A 2-D ARRAY'S ROW STRIDE IS BYTE-LOAD-BEARING (0x800AC7A0): D[a3][a4] emits
    sll a3,4 + sll a4,2 + add (correct length); D[a3*4 + a4] folds the outer *4 into
    a second sll and comes out 4 bytes SHORT. When a scaled index is one sll short,
    the source is a 2-D array, not a flattened index -- a LENGTH-class lever.
This commit is contained in:
Christopher Williams
2026-09-24 08:15:39 -04:00
parent 7971c91abc
commit c1dbf9d64e
8 changed files with 1657 additions and 1199 deletions
+1193 -1199
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -21,6 +21,7 @@
0x80012F24 0x80012F80 src/func_80012F24.c
0x80013AF0 0x80013B80 src/func_80013AF0.c
0x80013C88 0x80013C90 src/func_80013C88.c
0x80013C90 0x80013D04 src/func_80013C90.c
0x80013D04 0x80013D44 src/func_80013D04.c
0x8001587C 0x8001590C src/func_8001587C.c
0x8001590C 0x80015950 src/func_8001590C.c
@@ -85,6 +86,7 @@
0x80026C2C 0x80026C7C src/func_80026C2C.c
0x80026F14 0x80026F3C src/func_80026F14.c
0x800276B0 0x800276D4 src/func_800276B0.c
0x80027C44 0x80027CA0 src/func_80027C44.c
0x80027E1C 0x80027ECC src/func_80027E1C.c
0x80028150 0x800281A4 src/func_80028150.c
0x800281A4 0x800281E4 src/func_800281A4.c
@@ -147,6 +149,7 @@
0x8003B320 0x8003B34C src/func_8003B320.c
0x8003CB8C 0x8003CBE4 src/func_8003CB8C.c
0x8003D310 0x8003D35C src/func_8003D310.c
0x80041610 0x80041688 src/func_80041610.c
0x800419D0 0x80041A24 src/func_800419D0.c
0x80041A24 0x80041A58 src/func_80041A24.c
0x80041FE4 0x80042088 src/func_80041FE4.c
@@ -222,6 +225,7 @@
0x8006ADB0 0x8006AE04 src/func_8006ADB0.c
0x8006B184 0x8006B1CC src/func_8006B184.c
0x8006B1CC 0x8006B214 src/func_8006B1CC.c
0x8006B270 0x8006B2D4 src/func_8006B270.c
0x8006B66C 0x8006B6BC src/func_8006B66C.c
0x8006B778 0x8006B7C0 src/func_8006B778.c
0x8006B9E0 0x8006BA3C src/func_8006B9E0.c
@@ -240,6 +244,7 @@
0x800734A4 0x800734DC src/func_800734A4.c
0x80073F78 0x8007405C src/func_80073F78.c
0x80074C00 0x80074C74 src/func_80074C00.c
0x8007A2AC 0x8007A324 src/func_8007A2AC.c
0x8007A404 0x8007A428 src/func_8007A404.c
0x8007A428 0x8007A4FC src/func_8007A428.c
0x8007BC64 0x8007BD0C src/func_8007BC64.c
@@ -376,6 +381,7 @@
0x800B7230 0x800B7264 src/func_800B7230.c
0x800B74D0 0x800B7524 src/func_800B74D0.c
0x800BBDEC 0x800BBDF8 src/func_800BBDEC.c
0x800BC960 0x800BC9C4 src/func_800BC960.c
0x800BFE80 0x800BFEA0 src/func_800BFE80.c
0x800BFEA0 0x800BFEC0 src/func_800BFEA0.c
0x800BFEC0 0x800BFEE0 src/func_800BFEC0.c
1 # Code-region registry: one C region per matched function.
21 0x80012F24
22 0x80013AF0
23 0x80013C88
24 0x80013C90
25 0x80013D04
26 0x8001587C
27 0x8001590C
86 0x80026C2C
87 0x80026F14
88 0x800276B0
89 0x80027C44
90 0x80027E1C
91 0x80028150
92 0x800281A4
149 0x8003B320
150 0x8003CB8C
151 0x8003D310
152 0x80041610
153 0x800419D0
154 0x80041A24
155 0x80041FE4
225 0x8006ADB0
226 0x8006B184
227 0x8006B1CC
228 0x8006B270
229 0x8006B66C
230 0x8006B778
231 0x8006B9E0
244 0x800734A4
245 0x80073F78
246 0x80074C00
247 0x8007A2AC
248 0x8007A404
249 0x8007A428
250 0x8007BC64
381 0x800B7230
382 0x800B74D0
383 0x800BBDEC
384 0x800BC960
385 0x800BFE80
386 0x800BFEA0
387 0x800BFEC0
+74
View File
@@ -0,0 +1,74 @@
/*
* func_80013C90 — 104 bytes at 0x80013C90..0x80013CF8
*
* Flag-guarded notify: when a byte flag on the object is set, clears a four-byte
* scratch record, fills three of its bytes from the flag, submits it, and clears the
* flag.
*
* Original words:
* 27BDFFD8 addiu sp,sp,-40
* AFB20020 sw s2,32(sp)
* 00809021 addu s2,a0,zero ; s2 = a0
* AFBF0024 sw ra,36(sp)
* AFB1001C sw s1,28(sp)
* AFB00018 sw s0,24(sp)
* 9250013C lbu s0,316(s2) ; s0 = a0->byte_13c
* 00000000 nop
* 1200000D beq s0,zero,0x80013CE8 ; clear -> done
* 00A08821 addu s1,a1,zero ; (delay) s1 = a1
* 27A40010 addiu a0,sp,16 ; a0 = &buf
* 00002821 addu a1,zero,zero ; a1 = 0
* 0C03DBC0 jal 0x800F6F00
* 24060004 addiu a2,zero,4 ; (delay) func_800F6F00(buf, 0, 4)
* 27A40010 addiu a0,sp,16 ; a0 = &buf
* 24050001 addiu a1,zero,1 ; a1 = 1
* 02203021 addu a2,s1,zero ; a2 = a1
* A3B00010 sb s0,16(sp) ; buf[0] = flag
* A3B00011 sb s0,17(sp) ; buf[1] = flag
* 0C004EE0 jal 0x80013B80
* A3B00012 sb s0,18(sp) ; (delay) buf[2] = flag
* A240013C sb zero,316(s2) ; a0->byte_13c = 0
* 8FBF0024 lw ra,36(sp) ; (END)
* 8FB20020 lw s2,32(sp)
* 8FB1001C lw s1,28(sp)
* 8FB00018 lw s0,24(sp)
* 27BD0028 addiu sp,sp,40
* 03E00008 jr ra
* 00000000 nop
*
* The first call CLEARS the record (it is passed the buffer, zero and a length of 4),
* and the three byte stores come AFTER it — so the source is a clear call followed by
* three assignments, not an initialised local. The third store is scheduled into the
* second call's delay slot, and the second call is `f(buf, 1, a1)` with the incoming
* second argument forwarded.
*
* Frame arithmetic: `s0` is at 0x18, so the local between the outgoing argument area
* and the saves is 0x10..0x18 — 8 bytes. Only three bytes are written, so the
* declared size is not recoverable from the stores alone.
*
* LIMITS: the function name, both callees, the flag offset 316, the record length 4
* and the meaning of the flag are hypotheses read from the instruction shape; only the
* bytes are evidence. The flag is loaded with `lbu`, so it is an UNSIGNED byte.
* func_800F6F00 is this row's unregistered family callee (13 worklist rows) and
* func_80013B80 is unregistered too; both are referenced by address-named spellings.
* The function sets no result, so it is `void`.
*/
extern void func_800F6F00(char *buf, int a1, int a2);
extern void func_80013B80(char *buf, int a1, int a2);
void func_80013C90(int a0, int a1)
{
char buf[8];
unsigned char flag = *(unsigned char *)((char *)a0 + 316);
if (flag == 0)
return;
func_800F6F00(buf, 0, 4);
buf[0] = flag;
buf[1] = flag;
buf[2] = flag;
func_80013B80(buf, 1, a1);
*(unsigned char *)((char *)a0 + 316) = 0;
}
+65
View File
@@ -0,0 +1,65 @@
/*
* func_80027C44 — 92 bytes at 0x80027C44..0x80027CA0
*
* Component-wise difference of two 3-word vectors into a stack temporary, passed on
* with a third argument; always returns 0.
*
* Original words:
* 27BDFFD8 addiu sp,sp,-40
* AFBF0020 sw ra,32(sp)
* 8C820000 lw v0,0(a0) ; v0 = a0[0]
* 8CA30000 lw v1,0(a1) ; v1 = a1[0]
* 00000000 nop
* 00431023 subu v0,v0,v1 ; v0 -= v1
* AFA20010 sw v0,16(sp) ; d[0] = v0
* 8C820004 lw v0,4(a0)
* 8CA30004 lw v1,4(a1)
* 00000000 nop
* 00431023 subu v0,v0,v1
* AFA20014 sw v0,20(sp) ; d[1] = v0
* 8C820008 lw v0,8(a0)
* 27A40010 addiu a0,sp,16 ; a0 = &d
* 8CA30008 lw v1,8(a1)
* 00C02821 addu a1,a2,zero ; a1 = a2
* 00431023 subu v0,v0,v1
* 0C008C6F jal 0x800231BC
* AFA20018 sw v0,24(sp) ; (delay) d[2] = v0
* 8FBF0020 lw ra,32(sp)
* 00001021 addu v0,zero,zero ; return 0
* 03E00008 jr ra
* 27BD0028 addiu sp,sp,40 ; (delay) frame release
*
* The three pairs are the same expression with offsets 0, 4 and 8, so this is three
* statements and not a loop. The third store is scheduled into the `jal` delay slot
* and the third subtraction's operands are loaded before the two argument moves, so
* the source order is three difference statements followed by the call.
*
* Frame arithmetic fixes the temporary at 12 bytes: `ra` is at 0x20, so the local
* between the outgoing argument area and the save is 0x10..0x1C — three words.
*
* THE EPILOGUE IS THE `rare-real` SHAPE (cookbook findings 11/35): `lw ra,32(sp)` /
* `addu v0,zero,zero` / `jr ra` / `addiu sp,sp,40`. The instruction between the `ra`
* load and the jump is real and does not read `ra`, so GNU `as` reproduces it with the
* default toolchain and no override.
*
* LIMITS: the function name, the callee, the vector element count (3) and the return
* constant are hypotheses read from the instruction shape; only the bytes are
* evidence. The two vector arguments are `int *` (32-bit loads at offsets 0, 4, 8) and
* the difference is signed `subu`. func_800231BC is not registered and is referenced
* by its address-named spelling; it takes the temporary's ADDRESS as its first
* argument.
*/
extern void func_800231BC(int *d, int a1);
int func_80027C44(int *a0, int *a1, int a2)
{
int d[3];
d[0] = a0[0] - a1[0];
d[1] = a0[1] - a1[1];
d[2] = a0[2] - a1[2];
func_800231BC(d, a2);
return 0;
}
+80
View File
@@ -0,0 +1,80 @@
/* func_80041610 — 0x80041610..0x80041688 (120 bytes).
*
* Original words (objdump of the validated payload, little-endian):
* lw v1,12(a0) p = *(char **)(a0 + 12)
* nop
* beqz v1,0x80041644 if (p == 0) take the second arm
* _nop
* lh v0,264(v1) a1[0] = *(short *)(p + 264)
* nop
* sw v0,0(a1)
* lh v0,266(v1) a1[1] = *(short *)(p + 266)
* nop
* sw v0,4(a1)
* lh v0,268(v1) a1[2] = *(short *)(p + 268)
* j 0x80041680
* _sw v0,8(a1) (delay slot)
* 0x80041644:
* lw v0,8(a0) \ q = *(char **)(*(char **)(a0 + 8) + 12)
* nop | two levels of indirection, see point 2
* lw v1,12(v0) |
* nop /
* lw v0,20(v1) a1[0] = *(int *)(q + 20)
* nop
* sw v0,0(a1)
* lw v0,24(v1) a1[1] = *(int *)(q + 24)
* nop
* sw v0,4(a1)
* lw v0,4(a1) \
* lw v1,28(v1) | a1[2] = *(int *)(q + 28);
* negu v0,v0 | a1[1] = -a1[1];
* sw v1,8(a1) |
* sw v0,4(a1) /
* 0x80041680: jr ra; _nop
*
* Fills a 3-word output array from one of two sources depending on whether the
* object's +12 pointer is null. The two arms read DIFFERENT field widths from
* DIFFERENT offsets, so they are two genuinely different records.
*
* WHAT THE BYTES PIN DOWN:
*
* 1. THE TWO ARMS USE DIFFERENT WIDTHS AND OFFSETS, SO THEY ARE TWO RECORD
* TYPES. The first arm reads three `short`s at +264/+266/+268 (a contiguous
* 6-byte triple, i.e. an x/y/z of 16-bit values) and widens each to a word
* with `lh` + `sw`. The second arm reads three `int`s at +20/+24/+28 from a
* DIFFERENT object reached through one extra indirection. Declaring one shared
* record type for both would be wrong; the widths are the evidence.
* 2. THE SECOND ARM'S OBJECT IS REACHED THROUGH TWO LOADS: `*(char **)(a0 + 8)`
* then `+12`. So `a0` has a pointer at +8 to an intermediate object whose +12
* pointer is the record. Getting the level of indirection wrong changes the
* instruction count (worker C's lever 4).
* 3. THE LAST STORE IS A NEGATED RELOAD: `a1[1] = -a1[1]` is written AFTER the
* `a1[2]` store in the source, and cc1 reloads `4(a1)` rather than keeping the
* value, because the intervening store to `8(a1)` may alias it (a3 is an
* `int *`, so cc1 cannot prove the offsets disjoint). The order
* `a1[2] = ...; a1[1] = -a1[1];` is what produces the reload.
* 4. NO FRAME AT ALL: the row makes no calls and uses only t/v registers, so
* there is no prologue and no epilogue beyond `jr ra`.
*
* LIMITS: the record layouts (only the offsets above are observed), the meaning
* of the three components and of the negation are hypotheses read from the
* instruction shapes. Only the compiled bytes are evidence.
*/
void func_80041610(int a0, int *a1)
{
char *p = *(char **)(a0 + 12);
if (p != 0) {
a1[0] = *(short *)(p + 264);
a1[1] = *(short *)(p + 266);
a1[2] = *(short *)(p + 268);
} else {
char *q = *(char **)(*(char **)(a0 + 8) + 12);
a1[0] = *(int *)(q + 20);
a1[1] = *(int *)(q + 24);
a1[2] = *(int *)(q + 28);
a1[1] = -a1[1];
}
}
+75
View File
@@ -0,0 +1,75 @@
/*
* func_8006B270 — 100 bytes at 0x8006B270..0x8006B2D4
*
* Walks a singly-linked list from a global head, calling one routine per node and
* clearing one bit of a field of the intermediate record.
*
* Original words:
* 8F840550 lw a0,1360(gp) ; a0 = D_80121E88 (hoisted above the frame)
* 27BDFFE0 addiu sp,sp,-32
* AFBF001C sw ra,28(sp)
* AFB20018 sw s2,24(sp)
* AFB10014 sw s1,20(sp)
* 1080000C beq a0,zero,0x8006B2B8 ; empty -> done
* AFB00010 sw s0,16(sp) ; (delay) save s0
* 2412FBFF addiu s2,zero,-1025 ; s2 = ~0x400 (hoisted loop invariant)
* 8C90000C lw s0,12(a0) ; (L) s0 = node->field_0c
* 00000000 nop
* 8E11018C lw s1,396(s0) ; s1 = s0->field_18c (next)
* 0C01A4E6 jal 0x80069398
* 00000000 nop ; (delay) func_80069398(node)
* 8E020104 lw v0,260(s0) ; v0 = s0->field_104
* 02202021 addu a0,s1,zero ; node = next
* 00521024 and v0,v0,s2 ; v0 &= ~0x400
* 1480FFF7 bne a0,zero,0x8006B290 ; more -> loop
* AE020104 sw v0,260(s0) ; (delay) s0->field_104 = v0
* 8FBF001C lw ra,28(sp) ; (END)
* 8FB20018 lw s2,24(sp)
* 8FB10014 lw s1,20(sp)
* 8FB00010 lw s0,16(sp)
* 27BD0020 addiu sp,sp,32
* 03E00008 jr ra
* 00000000 nop
*
* Same list shape as src/func_8006B184.c and src/func_8006ADB0.c — the same head global
* and the same two link offsets (12 for the hop, 396 for the next) — with a bit clear
* on a third field (260) of the intermediate record. The head load is hoisted above
* the frame setup.
*
* `s2` holds `~0x400` for the whole loop and is saved purely for that: cc1 parks the
* loop-invariant mask in a callee-saved register instead of rematerialising it after
* the call, which is why this 25-instruction body saves three registers in a 32-byte
* frame. The same hoisting shows up in src/func_80026A04.c and src/func_800A6B38.c.
*
* The entry test is a guard and the back edge branches to the BODY start with the
* store in its DELAY SLOT, so the store runs on every iteration including the last —
* a `do`/`while` under an explicit empty-list guard, not a `while` whose test is
* duplicated. The `jal` delay slot is a `nop`, so the callee takes the node already in
* `a0`.
*
* LIMITS: the function name, the callee, the head global, the three field offsets
* (12, 260, 396) and the cleared bit 0x400 are hypotheses read from the instruction
* shape; only the bytes are evidence. D_80121E88 is gp-relative and carries a registry
* `gp` marker; func_80069398 is not registered and is referenced by its address-named
* spelling. The function sets no result, so it is `void`.
*/
extern int D_80121E88;
extern void func_80069398(int *node);
void func_8006B270(void)
{
int *node = (int *)D_80121E88;
if (node == 0)
return;
do {
int *mid = *(int **)((char *)node + 12);
int *next = *(int **)((char *)mid + 396);
func_80069398(node);
*(int *)((char *)mid + 260) &= ~0x400;
node = next;
} while (node != 0);
}
+90
View File
@@ -0,0 +1,90 @@
/* func_8007A2AC — 0x8007A2AC..0x8007A324 (120 bytes).
*
* Original words (objdump of the validated payload, little-endian):
* bltz a0,0x8007A2B8 if (a0 >= 0) D_80122590 = a0
* _li a2,-1 (delay slot) v = -1
* sw a0,3160(gp)
* 0x8007A2B8:
* bgez a1,0x8007A2D8 if (a1 < 0) return D_80122590
* _nop
* lw v0,3160(gp)
* j 0x8007A31C
* _nop
* 0x8007A2CC: <- found: commit the cursor and return the value
* sw v0,3160(gp) D_80122590 = i + 1
* j 0x8007A31C
* _move v0,a2 (delay slot)
* 0x8007A2D8:
* lw a0,3160(gp) i = D_80122590
* nop
* slt v0,a0,a1 v0 = (i < a1)
* beqz v0,0x8007A318 if (i >= a1) return v
* _sll v0,a0,0x2 (delay slot) i * 4
* lui v1,0x8014 \ table = D_80138460
* addiu v1,v1,-31648 /
* addu v1,v0,v1 &table[i]
* 0x8007A2F8:
* lw a2,0(v1) v = table[i]
* nop
* bgez a2,0x8007A2CC if (v >= 0) -> commit
* _addiu v0,a0,1 (delay slot) v0 = i + 1
* addiu a0,a0,1 i++
* slt v0,a0,a1 i < a1
* bnez v0,0x8007A2F8 loop
* _addiu v1,v1,4 (delay slot) &table[i]++
* 0x8007A318: move v0,a2 return v
* 0x8007A31C: jr ra; _nop
*
* A cursor-plus-scan: an optional cursor set, then a forward scan for the first
* non-negative table entry, leaving the cursor just past the found entry.
*
* WHAT THE BYTES PIN DOWN:
*
* 1. THE TWO EARLY GUARDS ARE PLAIN `if`s, AND THE FIRST ONE WRITES THE GLOBAL
* ONLY ON THE NON-NEGATIVE PATH. `bltz a0` skips the `sw`, so the store is
* inside `if (a0 >= 0)`; the negative case is the fall-through.
* 2. THE `a1 < 0` CASE RETURNS THE GLOBAL ITSELF, not a constant: the path is
* `lw v0,3160(gp)` then a jump to the shared epilogue. So the function's
* return value on that path is the cursor, which is why `v` cannot be folded
* into a single result register.
* 3. THE FOUND PATH STORES `i + 1`, NOT `i`. `addiu v0,a0,1` sits in the
* `bgez` delay slot (so `v0` is `i + 1` on both paths out of the test) and the
* found block stores THAT to the global. So the cursor is advanced past the
* found entry.
* 4. THE FALL-THROUGH RESULT IS THE LAST TABLE VALUE, NOT ZERO. The `-1` is
* materialised in the FIRST branch's delay slot (`li a2,-1`), i.e. `v` is
* initialised once before the loop; if the loop never runs, or runs to
* exhaustion without a non-negative entry, `move v0,a2` returns whatever `v`
* holds. That is why the initialisation is a `li` in a delay slot rather than
* a `move v0,zero` at the exit.
* 5. THE TABLE IS `int` AND SCALED BY 4 (`sll v0,a0,0x2`), and the base is the
* two-piece `la` form of 0x80138460 (`lui 0x8014` + `addiu -31648`), so the
* address-named symbol `D_80138460` is what reproduces the encoding.
* 6. NO FRAME: the row makes no calls, so there is no prologue.
*
* LIMITS: the meaning of the table, of the negative-entry sentinel and of the
* cursor global are hypotheses read from the instruction shapes. Only the
* compiled bytes are evidence.
*/
extern int D_80122590;
extern int D_80138460[];
int func_8007A2AC(int a0, int a1)
{
int i;
int v = -1;
if (a0 >= 0)
D_80122590 = a0;
if (a1 < 0)
return D_80122590;
for (i = D_80122590; i < a1; i++) {
v = D_80138460[i];
if (v >= 0) {
D_80122590 = i + 1;
return v;
}
}
return v;
}
+74
View File
@@ -0,0 +1,74 @@
/*
* func_800BC960 — 100 bytes at 0x800BC960..0x800BC9C4
*
* Countdown tick: decrements a global counter, and when it reaches zero runs one
* teardown on a global handle and, if a flag is set, a second one.
*
* Original words:
* 8F820768 lw v0,1896(gp) ; v0 = D_801220A0 (hoisted above the frame)
* 27BDFFE8 addiu sp,sp,-24
* 10400012 beq v0,zero,0x800BC9B4 ; already zero -> return
* AFBF0010 sw ra,16(sp) ; (delay)
* 2442FFFF addiu v0,v0,-1 ; v0 -= 1
* AF820768 sw v0,1896(gp) ; D_801220A0 = v0
* 1C40000E bgtz v0,0x800BC9B4 ; still positive -> return
* 00000000 nop
* 3C048013 lui a0,0x8013
* 8C84D79C lw a0,-10340(a0) ; a0 = D_8012D79C
* 0C02F1F6 jal 0x800BC7D8
* 00000000 nop ; (delay) func_800BC7D8(handle)
* 3C028012 lui v0,0x8012
* 90422374 lbu v0,9076(v0) ; v0 = D_80122374 UNSIGNED byte
* 00000000 nop
* 10400005 beq v0,zero,0x800BC9B4 ; flag clear -> return
* 00000000 nop
* 3C048013 lui a0,0x8013
* 8C84DD30 lw a0,-8912(a0) ; a0 = D_8012DD30
* 0C02F1F6 jal 0x800BC7D8
* 00000000 nop ; (delay) func_800BC7D8(handle)
* 8FBF0010 lw ra,16(sp) ; (END)
* 27BD0018 addiu sp,sp,24
* 03E00008 jr ra
* 00000000 nop
*
* The counter load is hoisted above the frame setup (the func_80042964 shape) and is
* tested BEFORE the decrement, while the sign test uses the DECREMENTED value — so the
* source decrements into the global and then tests the new value, and the zero test is
* a separate guard on the old one. Both guards branch to the same shared epilogue, so
* they are `return;` statements.
*
* Both handles are loaded from globals and passed in `a0`; the second call's handle is
* a DIFFERENT global from the first (0x8012D79C vs 0x8012DD30), and the flag
* D_80122374 is the same byte that src/func_800453C0.c tests.
*
* LIMITS: the function name, the callee, the four globals and the flag's meaning are
* hypotheses read from the instruction shape; only the bytes are evidence. D_801220A0
* is gp-relative and carries a registry `gp` marker; the other three are loaded
* ABSOLUTELY (`lui` + displacement) and are not in the registry, so their address-named
* spellings stay absolute as the original does. The flag is read with `lbu`, so it is
* an UNSIGNED byte. The function sets no result, so it is `void`.
*/
extern int D_801220A0;
extern int D_8012D79C;
extern unsigned char D_80122374;
extern int D_8012DD30;
extern void func_800BC7D8(int a0);
void func_800BC960(void)
{
int v = D_801220A0;
if (v == 0)
return;
D_801220A0 = --v;
if (v > 0)
return;
func_800BC7D8(D_8012D79C);
if (D_80122374 != 0)
func_800BC7D8(D_8012DD30);
}