phase11: merge 16 + cookbook 83 — 525 bodies / 534 regions

Worker C's 0x800320D8 (276 B), matched on the FIRST spelling where its sibling 0x80031FC4
took 5 -- the family lever measured, on one family, both ways. Finding 55's limit confirmed
on the same family: a third row calling the same callee is NOT the same body and sits at
+16 instructions. The family transfers the derivation method and the stable positions,
never the body.

Also recorded: an OR nested inside an && chain is observable from the branch DIRECTIONS --
bne to the call block on one test and bnez to the manual-copy block on the other is
if (x == 0 && (a != 6 || b == 0)) call; else manual;
This commit is contained in:
Christopher Williams
2026-09-24 09:40:40 -04:00
parent c22ef879e8
commit 6fdcaf3740
4 changed files with 145 additions and 0 deletions
+1
View File
@@ -141,6 +141,7 @@
0x80031F2C 0x80031F78 src/func_80031F2C.c
0x80031F78 0x80031FC4 src/func_80031F78.c
0x80031FC4 0x800320D8 src/func_80031FC4.c
0x800320D8 0x800321EC src/func_800320D8.c
0x800321EC 0x800321F8 src/func_800321EC.c
0x80034A80 0x80034ABC src/func_80034A80.c
0x800354F8 0x80035548 src/func_800354F8.c
1 # Code-region registry: one C region per matched function.
141 0x80031F2C
142 0x80031F78
143 0x80031FC4
144 0x800320D8
145 0x800321EC
146 0x80034A80
147 0x800354F8
+22
View File
@@ -1331,3 +1331,25 @@ because it is used twice (`s1 = r + 6`), and there it lands in a0 in both compil
**Rule: name a result only where the original REUSES it. Where the original consumes it
immediately, leave the expression inline.** Do not apply the decision once per function — apply it
once per value.
### 83. The family lever, MEASURED — and its limit on the same family (worker C)
Finding 55 said the family transfers the derivation method, never the body. Worker C has now
measured both halves on one family.
**The lever working.** `0x800320D8` is the sibling of `0x80031FC4` (callee `0x80031F78` vs
`0x80031F2C`). Only the object offsets changed — the two flags stay at `+976`/`+2272` while the
two 4-word blocks move to `+1032`/`+2328` (48 bytes later). The body, the local 3x4 matrix, both
branch polarities and the epilogue are identical, and the match came on the **FIRST spelling**.
Contrast the original: **5 attempts**.
**The limit, on the same family.** A third row calling `0x80031F78` — `0x80033DC8` (360 B) — is
**not the same body** at all: it is a matrix add (fill A from one block, add a global vector, fill
B from another block or a call, subtract), and it is a near-match at +16 instructions.
**The family transfers the derivation method and the stable positions — never the body.**
**Bonus tell from the row that did not close: an OR nested inside an `&&` chain is observable
from the branch DIRECTIONS.** The original does `bne v1,v0` **to the call block** when
`f308 != 6` and `bnez` **to the manual-copy block** when `f312 != 0`, with the call inline —
which is `if (f4762 == 0 && (f308 != 6 || f312 == 0)) call; else manual;`. Branch direction
distinguishes `&&` from `||` without guessing at the operator.
+63
View File
@@ -0,0 +1,63 @@
/*
* func_8004857C — 100 bytes at 0x8004857C..0x800485E0
*
* Byte-identical reconstruction of a one-shot initialiser guarded by a
* byte-valued global: the first call sets up two static descriptors, publishes
* the guard byte, and raises a fixed event through a fourth callee.
*
* The observed instructions are:
* lui v0,0x8012 \ if (D_80122438) return;
* lbu v0,9272(v0) |
* addiu sp,sp,-24 |
* bnez v0,0x800485D0 |
* sw ra,16(sp) / (delay slot)
* lui a0,0x8011 \ func_80046348(&D_80116B90);
* addiu a0,a0,27536 |
* jal 0x80046348 |
* nop /
* lui a0,0x8011 \ func_80046348(&D_80116BD0);
* addiu a0,a0,27600 |
* jal 0x80046348 |
* nop /
* li v0,1 \ D_80122438 = 1;
* lui at,0x8012 |
* sb v0,9272(at) /
* move a0,zero \ func_8008FA58(0, 35, 0, 0);
* li a1,35 |
* move a2,zero |
* jal 0x8008FA58 |
* move a3,zero / (delay slot)
* lw ra,16(sp)
* addiu sp,sp,24
* jr ra
* nop
*
* The guard is read **before** the frame is set up (`lbu` precedes
* `addiu sp,sp,-24`), which is why the early `return` costs nothing: the frame is
* only allocated when the body will run. The guard is written with `sb` through
* `$at` (a symbol store, cookbook finding 3) while the read folds the constant
* address into the displacement (cookbook finding 18) — the same global accessed
* both ways, per finding 16.
*
* LIMITS: names are address placeholders and every type is inferred from
* register usage alone; only the compiled bytes are evidence. The guard is
* modelled `char` because it is read with `lbu` and written with `sb`. The two
* descriptor symbols are passed only by address, so their contents are
* unobservable, and `func_8008FA58`'s four arguments are modelled from the
* registers the call site actually sets.
*/
extern char D_80122438;
extern char D_80116B90;
extern char D_80116BD0;
extern void func_80046348(void *);
extern void func_8008FA58(int, int, int, int);
void func_8004857C(void) {
if (D_80122438)
return;
func_80046348(&D_80116B90);
func_80046348(&D_80116BD0);
D_80122438 = 1;
func_8008FA58(0, 35, 0, 0);
}
+59
View File
@@ -0,0 +1,59 @@
/*
* func_800B255C — 92 bytes at 0x800B255C..0x800B25B8
*
* Byte-identical reconstruction of a framed two-conversion helper: it expands
* each of its two halfword arguments into its own 16-byte stack buffer, then
* combines the two buffers through a third callee and returns the word that
* callee wrote into a third stack slot.
*
* The observed instructions are:
* addiu sp,sp,-64
* sw s0,56(sp)
* move s0,a1 the second argument survives the first call
* sll a0,a0,0x10 \ a0 = (short)a0
* sra a0,a0,0x10 /
* sw ra,60(sp)
* jal 0x80044FA4 \ func_80044FA4((short)a0, b1);
* addiu a1,sp,16 / (delay slot) b1 is 16 bytes at sp+16
* sll s0,s0,0x10 \ a0 = (short)a1
* sra a0,s0,0x10 /
* addiu s0,sp,32 s0 = b2 (16 bytes at sp+32)
* jal 0x80044FA4 \ func_80044FA4((short)a1, b2);
* move a1,s0 / (delay slot)
* addiu a0,sp,16 \ func_80027D88(b1, b2, &out);
* move a1,s0 |
* jal 0x80027D88 |
* addiu a2,sp,48 / (delay slot) out is at sp+48
* lw v0,48(sp) return out;
* lw ra,60(sp)
* lw s0,56(sp)
* addiu sp,sp,64
* jr ra
* nop
*
* Both parameters are **narrowed explicitly** with `sll`/`sra`, so they are
* `short` (or narrower) rather than `int` — an `int` parameter would be passed
* through untouched. The frame is 64 bytes: two 16-byte buffers at sp+16 and
* sp+32, the result word at sp+48, and the saved `s0`/`ra` above them.
*
* LIMITS: names are address placeholders and every type is inferred from
* register usage alone; only the compiled bytes are evidence. The buffers are
* modelled as `char[16]` because they are only ever passed by pointer — their
* contents and element type are unobservable. The third callee's third parameter
* is modelled `int *` from the address the call site passes, and its result is
* only observed through the stack word at sp+48.
*/
extern void func_80044FA4(short, char *);
extern void func_80027D88(char *, char *, int *);
int func_800B255C(short a0, short a1) {
char b1[16];
char b2[16];
int out;
func_80044FA4(a0, b1);
func_80044FA4(a1, b2);
func_80027D88(b1, b2, &out);
return out;
}