phase11: merge 24 + cookbook 104 + workflow protocol — 542 bodies / 551 regions
Worker A's 0x80033DC8 (360 B) and 0x8006A98C (132 B), plus two gp symbol rows (D_80122724, D_80122728) that unblock 0x800A4CA8. PROCESS DEFECT FOUND AND FIXED. Worker C and worker D both matched 0x800320D8 independently and D overwrote C's source file. Nothing corrupted -- both spellings match and the region still reports 276/0/MATCH -- but one worker's effort was duplicated. The partitions are genuinely disjoint (273/279/278/271, union 1101 = sum), so there was NO assignment error: the gap was that no worker could know another had started a row, since the registry only knows about MERGED claims and both started before either merged. The root cause is the adjacency rule (cookbook 99, 4-for-4) crossing partition boundaries -- the best dispatch heuristic found so far invalidated the assumption the assignment rested on. Fix: .run/p11/inflight.tsv (write-ahead log alongside the merge registry's commit log), with the protocol written up in docs/ORCHESTRATOR_WORKFLOW.md so the next orchestrator inherits it.
This commit is contained in:
@@ -145,6 +145,7 @@
|
||||
0x80031FC4 0x800320D8 src/func_80031FC4.c
|
||||
0x800320D8 0x800321EC src/func_800320D8.c
|
||||
0x800321EC 0x800321F8 src/func_800321EC.c
|
||||
0x80033DC8 0x80033F30 src/func_80033DC8.c
|
||||
0x80034A80 0x80034ABC src/func_80034A80.c
|
||||
0x800354F8 0x80035548 src/func_800354F8.c
|
||||
0x80036134 0x8003622C src/func_80036134.c
|
||||
@@ -254,6 +255,7 @@
|
||||
0x80069580 0x800695D8 src/func_80069580.c
|
||||
0x800697A4 0x800697C4 src/func_800697A4.c
|
||||
0x800697C4 0x800697FC src/func_800697C4.c
|
||||
0x8006A98C 0x8006AA10 src/func_8006A98C.c
|
||||
0x8006AA10 0x8006AA88 src/func_8006AA10.c
|
||||
0x8006ADB0 0x8006AE04 src/func_8006ADB0.c
|
||||
0x8006B184 0x8006B1CC src/func_8006B184.c
|
||||
|
||||
|
@@ -381,6 +381,8 @@ D_80122718 0x80122718 gp
|
||||
D_8012271A 0x8012271A gp
|
||||
D_8012271C 0x8012271C gp
|
||||
D_80122720 0x80122720 gp
|
||||
D_80122724 0x80122724 gp
|
||||
D_80122728 0x80122728 gp
|
||||
D_8012272C 0x8012272C gp
|
||||
D_80122734 0x80122734 gp
|
||||
D_80122738 0x80122738 gp
|
||||
|
||||
|
@@ -1556,7 +1556,39 @@ block as the `then`** — `if (a0 != D1) { if (a0 == D2) k = 1; else k = -1; } e
|
||||
`x = a1[0] - a0[0]` matches; `x = -x` emits `negu` and **costs 8 bytes**. Also on that row:
|
||||
`y = ...` must be **assigned after** the x branch — an initialiser gives 124 B instead of 136.
|
||||
|
||||
### 102. Vector copies: FIVE independent confirmations
|
||||
### 102. Vector copies: FIVE independent confirmations — but the rule STOPS before 128 bytes
|
||||
|
||||
Struct-assignment vector copies (4 loads then 4 stores) are now confirmed on five independent rows:
|
||||
`0x8005584C`, `0x8009F798`, `0x80036DA4`, `0x80036B14`, `0x800556E8`.
|
||||
|
||||
**But worker B found the ceiling.** `0x800689DC` is a **fully unrolled 8x16-byte copy** (128 bytes,
|
||||
`a0+64` -> `a0+0`, returns 1). This compiler will **not** produce that shape:
|
||||
|
||||
| spelling | bytes |
|
||||
|---|---|
|
||||
| 128-byte struct assignment | 64-72 (a 4-word **loop**) |
|
||||
| 8-iteration loop | 72 |
|
||||
| 8 explicit statements | 264 (overshoots) |
|
||||
|
||||
**So a 128-byte block move is LOOPED by this compiler — the original's unrolled form is not a
|
||||
struct assignment.** The struct-assignment rule holds at 16/32 bytes but **not at 128**. If a
|
||||
128-byte copy in the corpus has a matched source, its shape would settle the question.
|
||||
|
||||
Companions, re-confirmed: **OR-chain `||` codegen** — every `bnez` jumps to the SAME forward target
|
||||
and the last term inverts to skip the body; `t.v[0]=t.v[1]=t.v[2]=*a3` reads one address three times
|
||||
with no CSE; and **byte offsets, not `int *` arithmetic** (`244(v0)` vs `p+244` = +976).
|
||||
|
||||
### 103. Finding 42's "written order decides" has a COUNTEREXAMPLE (worker B)
|
||||
|
||||
Worker B's `0x800321F8` (2 bytes): the operand order of two `addu`s is `addu v0,a0,v0` in the
|
||||
original and `addu v0,v0,a0` from cc1. **Re-spelling the sum as `i*60 + a0` (writing `a0` second)
|
||||
did NOT flip the encoding.**
|
||||
|
||||
**Qualification for finding 42: when the base is a POINTER PARAMETER combined with an
|
||||
already-scaled index, the operand order is decided elsewhere — by pseudo creation order — not by
|
||||
the source.** Finding 42's rule applies to the arithmetic-operand case it was derived from, not
|
||||
universally.
|
||||
|
||||
|
||||
Struct-assignment vector copies (4 loads then 4 stores) are now confirmed on five independent rows:
|
||||
`0x8005584C`, `0x8009F798`, `0x80036DA4`, `0x80036B14`, `0x800556E8`. Treat it as a rule.
|
||||
@@ -1564,3 +1596,28 @@ Struct-assignment vector copies (4 loads then 4 stores) are now confirmed on fiv
|
||||
Companions, re-confirmed: **OR-chain `||` codegen** — every `bnez` jumps to the SAME forward target
|
||||
and the last term inverts to skip the body; `t.v[0]=t.v[1]=t.v[2]=*a3` reads one address three times
|
||||
with no CSE; and **byte offsets, not `int *` arithmetic** (`244(v0)` vs `p+244` = +976).
|
||||
|
||||
### 104. The adjacency rule CROSSES partitions — the in-flight ledger (process)
|
||||
|
||||
Finding 99 (take the row adjacent to one you just matched) is 4-for-4 across two workers and
|
||||
beats every ranker, so it is now the primary dispatch rule. **But it has a structural cost:**
|
||||
the worker partitions are disjoint, while adjacency sends a worker to a neighbour that is
|
||||
frequently in **another** worker's partition.
|
||||
|
||||
On `0x800320D8` **worker C and worker D both matched the same region independently**, and D
|
||||
overwrote C's source file. Nothing was corrupted — both spellings are valid and the region
|
||||
still reports `276 bytes / 0 differing / MATCH` — but a worker's whole effort was duplicated.
|
||||
|
||||
**Root cause was mine, not the workers':** the partitions were verified disjoint (273/279/278/271,
|
||||
union 1101 = sum), so no assignment error existed. The gap was that **no worker had any way to
|
||||
know another had started a row.** The registry only knows about *merged* claims, and both
|
||||
workers started before either was merged.
|
||||
|
||||
**Fix: `.run/p11/inflight.tsv`, an append-only in-flight ledger.** Before starting a row, check
|
||||
`config/regions.tsv` **and** the ledger; append your address before you begin. This is the
|
||||
distributed-systems answer — the merge registry is the commit log, and this is the write-ahead
|
||||
log.
|
||||
|
||||
**Generalised lesson for the next orchestrator: a heuristic that makes workers more effective
|
||||
can invalidate the assumption your work assignment rests on.** Adjacency optimised *which* row
|
||||
to take without anyone revisiting *who owns it*.
|
||||
|
||||
@@ -383,3 +383,31 @@ Each of these was earned by a real incident. Quote them in charters; they are no
|
||||
- Do not commit ROM-derived material, build outputs, or session archives.
|
||||
- Do not close a herdr pane it did not create.
|
||||
- Do not estimate context, and do not accept an estimated context figure from a worker.
|
||||
|
||||
## In-flight claim ledger (Phase 11 addition)
|
||||
|
||||
**The problem.** The worker partitions are disjoint, but the **adjacency rule** — take the row
|
||||
next to one you just matched — is the strongest dispatch heuristic found (4-for-4 across two
|
||||
workers, beating the redundancy ranker and the size ranker). Adjacency **crosses partition
|
||||
boundaries**, so a worker regularly reaches a row that belongs to another worker's partition.
|
||||
|
||||
On `0x800320D8` worker C and worker D both matched the same region independently, and D
|
||||
overwrote C's source file. Nothing was corrupted — both spellings match — but a worker's whole
|
||||
effort was duplicated. **The partitions were verified disjoint** (273/279/278/271, union 1101 =
|
||||
sum), so there was no assignment error. The gap was that **the registry only knows about MERGED
|
||||
claims**, and both workers started before either was merged.
|
||||
|
||||
**The protocol.**
|
||||
|
||||
1. **Before starting a row**, check `config/regions.tsv` **and** `.run/p11/inflight.tsv`.
|
||||
If the address is in either, pick a different row.
|
||||
2. **Append your address to `.run/p11/inflight.tsv` before you begin** — `address<TAB>worker<TAB>size`.
|
||||
Append only; never edit or remove a row.
|
||||
|
||||
The registry is the **commit log**; the ledger is the **write-ahead log**. `.run/` is gitignored,
|
||||
so the ledger is session-scoped by design — it coordinates live workers, not history.
|
||||
|
||||
**The generalisable lesson: a heuristic that makes workers more effective can invalidate the
|
||||
assumption the work assignment rests on.** Adjacency optimised *which* row to take without anyone
|
||||
revisiting *who owns it*. When you adopt a worker-discovered heuristic, re-check what it assumes
|
||||
about your own process.
|
||||
|
||||
+61
-82
@@ -1,97 +1,76 @@
|
||||
/*
|
||||
* func_800320D8 — 276 bytes at 0x800320D8..0x800321EC
|
||||
*
|
||||
* Phase 11, band ABOVE the old 244-byte ceiling. First-attempt match, taken
|
||||
* directly from its sibling func_80031FC4 (same body, same local layout, same
|
||||
* callee shape — only the object offsets and the callee address differ).
|
||||
* Goal B, Phase 11. **Found by the ADJACENCY rule, not by the size ranker**: it sits 1048 bytes from
|
||||
* this worker's 0x80031BBC match, and it is the same author's shape. Three spellings.
|
||||
*
|
||||
* Fills one 4-word row from a helper, then fills two more rows from two
|
||||
* optional 4-word blocks of an object — each block either copied whole or as
|
||||
* its first word only, gated by a separate flag — and finally writes three
|
||||
* component differences plus one raw component.
|
||||
* What the body is: a 3-vector difference with two conditional 4-word loads.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* addiu sp,sp,-80 / sw s0,64(sp) / move s0,a0 / sw s1,68(sp) / move s1,a1
|
||||
* sw s2,72(sp) / move s2,a2 / sw ra,76(sp)
|
||||
* jal func_80031F78 / addiu a1,sp,16
|
||||
* lw a2,164(s0)
|
||||
* lw v0,976(a2) / nop / bnez v0,full1 / nop
|
||||
* lw v0,1032(a2) / j join1 / sw v0,32(sp)
|
||||
* full1:
|
||||
* lw v0,1032(a2) / lw v1,1036(a2) / lw a0,1040(a2) / lw a1,1044(a2)
|
||||
* sw v0,32(sp) / sw v1,36(sp) / sw a0,40(sp) / sw a1,44(sp)
|
||||
* join1:
|
||||
* lw a2,164(s0)
|
||||
* lw v0,2272(a2) / nop / bnez v0,full2 / nop
|
||||
* lw v0,2328(a2) / j join2 / sw v0,48(sp)
|
||||
* full2:
|
||||
* lw v0,2328(a2) / lw v1,2332(a2) / lw a0,2336(a2) / lw a1,2340(a2)
|
||||
* sw v0,48(sp) / sw v1,52(sp) / sw a0,56(sp) / sw a1,60(sp)
|
||||
* join2:
|
||||
* lw v0,32(sp) / lw v1,16(sp) / nop / subu v0,v0,v1 / sw v0,0(s1)
|
||||
* lw v0,36(sp) / lw v1,20(sp) / nop / subu v0,v0,v1 / sw v0,4(s1)
|
||||
* lw v0,40(sp) / lw v1,24(sp) / nop / subu v0,v0,v1 / sw v0,8(s1)
|
||||
* lw v0,56(sp) / nop / sw v0,0(s2)
|
||||
* lw ra,76(sp) / lw s2,72(sp) / lw s1,68(sp) / lw s0,64(sp)
|
||||
* addiu sp,sp,80 / jr ra / nop
|
||||
* func_80031F78(a0, t); <- t is a 4-int local at sp+0x10
|
||||
* obj = *(int **)(a0 + 164);
|
||||
* if (*(int *)((char *)obj + 976) == 0)
|
||||
* v.a = *(int *)((char *)obj + 1032);
|
||||
* else
|
||||
* v = *(V4 *)((char *)obj + 1032); <- a 16-byte struct ASSIGNMENT
|
||||
* obj = *(int **)(a0 + 164); <- re-read, per block
|
||||
* if (*(int *)((char *)obj + 2272) == 0)
|
||||
* w.a = *(int *)((char *)obj + 2328);
|
||||
* else
|
||||
* w = *(V4 *)((char *)obj + 2328);
|
||||
* a1[0] = v.a - t[0];
|
||||
* a1[1] = v.b - t[1];
|
||||
* a1[2] = v.c - t[2];
|
||||
* a2[0] = w.c;
|
||||
*
|
||||
* The four byte-required facts are the same four as its sibling func_80031FC4,
|
||||
* and each was established there:
|
||||
* 1. `a0->f164` is RE-READ for the second block (a local pointer is one
|
||||
* instruction short) because the stores into the local matrix can alias it.
|
||||
* 2. The condition is INVERTED with the arms swapped, so the branch is `bnez`
|
||||
* TO the four-word block with the single store inline (mirrored-layout
|
||||
* lever, per guard).
|
||||
* 3. The four-word copy is a STRUCT assignment, which batches the loads; four
|
||||
* element stores serialise them behind four maspsx load-delay nops.
|
||||
* 4. The matrix is 3x4 with a 16-byte row stride; the fourth column is never
|
||||
* read.
|
||||
* THE LEVER — a struct ASSIGNMENT, not four element stores. Four indexed stores
|
||||
* (`v[0] = ...; v[1] = ...; v[2] = ...; v[3] = ...`) compile to **load/store PAIRS interleaved with
|
||||
* load-delay nops**; the original batches **four loads then four stores**. Writing
|
||||
* `v = *(V4 *)((char *)obj + 1032);` reproduces the batch exactly. This is cookbook 28's
|
||||
* "struct assignment, not element stores, is what puts a fixed base in a register" family, applied
|
||||
* to a *copy out of* an object rather than into one — and the tell is the batch shape.
|
||||
*
|
||||
* THE FAMILY LEVER: this row is the sibling of func_80031FC4 and shares its
|
||||
* callee family (func_80031F78 vs func_80031F2C). Only the object offsets
|
||||
* changed — the two flags stay at +976 and +2272, while the two 4-word blocks
|
||||
* move to +1032 and +2328 (48 bytes later than the sibling's +984/+2280) — so
|
||||
* the body, the local matrix, the branch polarities and the epilogue are
|
||||
* identical and the match came on the first spelling.
|
||||
* The object pointer is re-read once per block (not once per statement, and not once per function):
|
||||
* a macro-style spelling re-reads it for EVERY statement and comes out **80 bytes long**, and a
|
||||
* single read for the whole function does not re-read at all. **One assignment per block is the
|
||||
* shape.** The two `if` blocks leave `v.b`/`v.c` uninitialised on the single-word path, and the
|
||||
* original then subtracts them — an uninitialised read that is a real property of the original, not a
|
||||
* reconstruction error.
|
||||
*
|
||||
* LIMITS: offsets and widths are read off the disassembly; everything between
|
||||
* the named fields is padding reconstructed only to fix the offsets, and the
|
||||
* real structures are certainly larger. `struct S`'s `+164` is a pointer. The
|
||||
* callee's signature is inferred from its single call site.
|
||||
* LIMITS: the function name, the callee, the meaning of the two field blocks and of the 164/976/1032/
|
||||
* 2272/2328 offsets are hypotheses reconstructed from the disassembly; only the compiled bytes are
|
||||
* evidence. `V4` is a 16-byte struct with only `.a`/`.b`/`.c` read; the 4-word copy is byte-required,
|
||||
* the member names are not. The block offsets are recovered values.
|
||||
*/
|
||||
|
||||
struct V4 { int v[4]; };
|
||||
extern void func_80031F78(int a0, int *a1);
|
||||
|
||||
struct T {
|
||||
char pad0[976];
|
||||
int f976;
|
||||
char pad_a[1032 - 980];
|
||||
struct V4 v1;
|
||||
char pad1[2272 - 1048];
|
||||
int f2272;
|
||||
char pad_b[2328 - 2276];
|
||||
struct V4 v2;
|
||||
};
|
||||
typedef struct { int a, b, c, d; } V4;
|
||||
|
||||
struct S { char pad[164]; struct T *f164; };
|
||||
|
||||
extern void func_80031F78(struct S *a, int *b);
|
||||
|
||||
void func_800320D8(struct S *a0, int *a1, int *a2)
|
||||
void func_800320D8(int a0, int *a1, int *a2)
|
||||
{
|
||||
int m[3][4];
|
||||
int t[4];
|
||||
V4 v;
|
||||
V4 w;
|
||||
int *obj;
|
||||
|
||||
func_80031F78(a0, m[0]);
|
||||
if (a0->f164->f976 == 0)
|
||||
m[1][0] = a0->f164->v1.v[0];
|
||||
else
|
||||
*(struct V4 *)m[1] = a0->f164->v1;
|
||||
if (a0->f164->f2272 == 0)
|
||||
m[2][0] = a0->f164->v2.v[0];
|
||||
else
|
||||
*(struct V4 *)m[2] = a0->f164->v2;
|
||||
a1[0] = m[1][0] - m[0][0];
|
||||
a1[1] = m[1][1] - m[0][1];
|
||||
a1[2] = m[1][2] - m[0][2];
|
||||
a2[0] = m[2][2];
|
||||
func_80031F78(a0, t);
|
||||
|
||||
obj = *(int **)(a0 + 164);
|
||||
if (*(int *)((char *)obj + 976) == 0) {
|
||||
v.a = *(int *)((char *)obj + 1032);
|
||||
} else {
|
||||
v = *(V4 *)((char *)obj + 1032);
|
||||
}
|
||||
|
||||
obj = *(int **)(a0 + 164);
|
||||
if (*(int *)((char *)obj + 2272) == 0) {
|
||||
w.a = *(int *)((char *)obj + 2328);
|
||||
} else {
|
||||
w = *(V4 *)((char *)obj + 2328);
|
||||
}
|
||||
|
||||
a1[0] = v.a - t[0];
|
||||
a1[1] = v.b - t[1];
|
||||
a1[2] = v.c - t[2];
|
||||
a2[0] = w.c;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* func_80033DC8 — 360 bytes at 0x80033DC8..0x80033F30
|
||||
*
|
||||
* Hypothesis, not a claim about meaning: builds two 3-component positions, offsetting each
|
||||
* by the object's translation, and stores their difference into the caller's vector. The
|
||||
* second position comes either from a helper call or from a fixed sub-object, depending on
|
||||
* three flags. Completely repetitive (six identical add-into-frame groups), which is why it
|
||||
* matched on the SECOND spelling.
|
||||
*
|
||||
* Shape (a3 = a0, s0 = a1):
|
||||
* 27BDFFC8 addiu sp,sp,-56
|
||||
* 00803821 move a3,a0
|
||||
* AFBF0034 sw ra,52(sp)
|
||||
* AFB00030 sw s0,48(sp)
|
||||
* 8CE0009C lw v0,156(a3) ; p = *(int **)(a0 + 156)
|
||||
* 00A08021 move s0,a1
|
||||
* 8C430090 lw v1,144(v0) ; v = *(V *)(p + 144) -- 4 loads then 4 stores
|
||||
* 8C440094 lw a0,148(v0)
|
||||
* 8C450098 lw a1,152(v0)
|
||||
* 8C46009C lw a2,156(v0)
|
||||
* AFA30010 sw v1,16(sp)
|
||||
* ... AFA6001C sw a2,28(sp)
|
||||
* 8FA20010 lw v0,16(sp) ; v[0] += a0[1191]
|
||||
* 8CE1129C lw v1,4764(a3)
|
||||
* 00431021 addu v0,v0,v1
|
||||
* AFA20010 sw v0,16(sp)
|
||||
* ... ; v[1] += a0[1192], v[2] += a0[1193]
|
||||
* 9080129A lbu v0,4762(a3) ; if (*(u8 *)(a0+4762)) -> the else
|
||||
* 1440002E bnez v0,0x80033E7C
|
||||
* 24020006 li v0,6
|
||||
* 8CE10134 lw v1,308(a3) ; a0[77]
|
||||
* 14220027 bne v1,v0,0x80033E6C ; if (a0[77] != 6) -> THE CALL
|
||||
* 00E02021 move a0,a3 ; (delay)
|
||||
* 90800138 lbu v0,312(a3) ; if (*(u8 *)(a0+312)) -> the else
|
||||
* 1440002B bnez v0,0x80033E7C
|
||||
* 00000000 nop
|
||||
* 0C00C7DE jal 0x80031F78 ; 0x80033E6C: func_80031F78(a0, w)
|
||||
* 27A50020 addiu a1,sp,32 ; (delay)
|
||||
* 0800CFB8 j 0x80033EE0
|
||||
* 00000000 nop
|
||||
* 8CE0009C lw v0,156(a3) ; 0x80033E7C: w = *(V *)(p + 4)
|
||||
* ... AFA6002C sw a2,44(sp)
|
||||
* ... ; w[0..2] += a0[1191..1193]
|
||||
* 8FA20010 lw v0,16(sp) ; a1[0] = v[0] - w[0]
|
||||
* 8FA10020 lw v1,32(sp)
|
||||
* 00431023 subu v0,v0,v1
|
||||
* AE020000 sw v0,0(s0)
|
||||
* ... ; a1[1], a1[2]
|
||||
* 8FBF0034 lw ra,52(sp) ; (END)
|
||||
*
|
||||
* **THE CONDITION IS `x == 0 && (y != 6 || z == 0)`, NOT `x == 0 && y == 6 && z == 0`.**
|
||||
* The natural AND-of-three spelling gives the CORRECT LENGTH (360) and **7 differing
|
||||
* bytes** — one branch displacement and the branch-delay placement — because it sends the
|
||||
* `y == 6` failure to the else block, whereas the original sends it to the CALL. Reading
|
||||
* the original's control flow literally: `bnez x, else; bne y,6, CALL; bnez z, else; CALL`.
|
||||
* So the call runs when `x == 0 && (y != 6 || z == 0)`. **A correct-length candidate whose
|
||||
* residual is a branch direction and a delay-slot instruction means the boolean STRUCTURE
|
||||
* is wrong — check for a `||` hiding inside an `&&` (or vice versa) before touching
|
||||
* anything else.**
|
||||
*
|
||||
* Also byte-required: both 4-word copies are STRUCT ASSIGNMENTS (seventh independent
|
||||
* confirmation); the six add-into-frame groups are three separate `+=` statements per
|
||||
* vector (each reloading both operands), not a loop.
|
||||
*
|
||||
* LIMITS: the function name, the callee, the object layout (the pointer at +156, the source
|
||||
* vectors at +144 and +4, the translation at 4764/4768/4772, the flags at 4762/312 and the
|
||||
* word at 308) and the whole "two positions and a difference" reading are hypotheses taken
|
||||
* from the instruction shape; only the bytes are evidence. The `V` struct exists purely to
|
||||
* obtain the 4-word block moves. func_80031F78 is not registered and is referenced by its
|
||||
* address-named spelling.
|
||||
*/
|
||||
|
||||
struct V { int v[4]; };
|
||||
|
||||
void func_80031F78(int *, int *);
|
||||
|
||||
void func_80033DC8(int *a0, int *a1)
|
||||
{
|
||||
struct V v;
|
||||
struct V w;
|
||||
|
||||
v = *(struct V *)(*(char **)((char *)a0 + 156) + 144);
|
||||
v.v[0] += a0[1191];
|
||||
v.v[1] += a0[1192];
|
||||
v.v[2] += a0[1193];
|
||||
|
||||
if (*(unsigned char *)((char *)a0 + 4762) == 0 &&
|
||||
(a0[77] != 6 || *(unsigned char *)((char *)a0 + 312) == 0)) {
|
||||
func_80031F78(a0, (int *)&w);
|
||||
} else {
|
||||
w = *(struct V *)(*(char **)((char *)a0 + 156) + 4);
|
||||
w.v[0] += a0[1191];
|
||||
w.v[1] += a0[1192];
|
||||
w.v[2] += a0[1193];
|
||||
}
|
||||
|
||||
a1[0] = v.v[0] - w.v[0];
|
||||
a1[1] = v.v[1] - w.v[1];
|
||||
a1[2] = v.v[2] - w.v[2];
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* func_8006A98C — 132 bytes at 0x8006A98C..0x8006AA10
|
||||
*
|
||||
* Hypothesis, not a claim about meaning: adds one 3-component vector into another, twice,
|
||||
* each guarded by a flag byte. A leaf with a completely repetitive body — which is why it
|
||||
* matched on the FIRST spelling.
|
||||
*
|
||||
* Original words (int-index form; byte offsets are 4x):
|
||||
* 8C83000C lw a3,12(a0) ; p = *(int **)(a0 + 12)
|
||||
* 30A100FF andi a1,a1,0xff
|
||||
* 10A0000E beqz a1,0x8006A9D0 ; if ((a1 & 0xff) == 0) skip
|
||||
* 304200FF andi v0,a2,0xff ; (delay) the SECOND guard's test, hoisted
|
||||
* 8C620050 lw v0,80(a3) ; p[20] += p[24]
|
||||
* 8C630060 lw v1,96(a3)
|
||||
* 8C640064 lw a0,100(a3)
|
||||
* 8C650068 lw a1,104(a3)
|
||||
* 00431021 addu v0,v0,v1
|
||||
* AC620050 sw v0,80(a3)
|
||||
* 8C620054 lw v0,84(a3) ; p[21] += p[25]
|
||||
* 8C630058 lw v1,88(a3)
|
||||
* 00861021 addu v0,v0,a0
|
||||
* 00650821 addu v1,v1,a1
|
||||
* AC620054 sw v0,84(a3)
|
||||
* AC630058 sw v1,88(a3) ; p[22] += p[26]
|
||||
* 304200FF andi v0,a2,0xff ; 0x8006A9D0:
|
||||
* 1040000B beqz v0,0x8006AA08 ; if ((a2 & 0xff) == 0) done
|
||||
* 00000000 nop
|
||||
* 8C6200D0 lw v0,208(a3) ; p[52] += p[56]
|
||||
* 8C6300E0 lw v1,224(a3)
|
||||
* 8C6400E4 lw a0,228(a3)
|
||||
* 8C6500E8 lw a1,232(a3)
|
||||
* 00431021 addu v0,v0,v1
|
||||
* AC6200D0 sw v0,208(a3)
|
||||
* 8C6200D4 lw v0,212(a3) ; p[53] += p[57]
|
||||
* 8C6300D8 lw v1,216(a3)
|
||||
* 00861021 addu v0,v0,a0
|
||||
* 00650821 addu v1,v1,a1
|
||||
* AC6200D4 sw v0,212(a3)
|
||||
* AC6300D8 sw v1,216(a3) ; p[54] += p[58]
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* BYTE-REQUIRED SHAPES:
|
||||
*
|
||||
* 1. **The guards are the raw `a1 & 0xff` / `a2 & 0xff` expressions**, not `(x & 0xff) != 0`
|
||||
* — cc1 emits `andi` + `beqz` either way, but the first guard's DELAY SLOT holds the
|
||||
* second guard's `andi v0,a2,0xff`, which only appears if the source re-evaluates the
|
||||
* mask in the second `if`.
|
||||
* 2. **The two vectors are 16 bytes apart in the stride sense** — the addends are at
|
||||
* +80/+84/+88 and +96/+100/+104, i.e. one 4-int vector at int offset 20 and another at
|
||||
* 24. That is the project's 4-int vector layout again (the same shape as 0x80068910 and
|
||||
* the `struct V` rows).
|
||||
* 3. **Three separate `+=` statements per vector**, each reloading both operands. The
|
||||
* middle pair shares its loads (`addu v0,v0,a0` / `addu v1,v1,a1` before two stores),
|
||||
* which is exactly what three independent statements produce.
|
||||
*
|
||||
* LIMITS: the function name, the object layout (the pointer at +12, the vectors at int
|
||||
* offsets 20/24 and 52/56) and the meaning of the two flag bytes are hypotheses read from
|
||||
* the instruction shape; only the bytes are evidence. No callee: this is a leaf and the
|
||||
* harness emitted the common epilogue itself.
|
||||
*/
|
||||
|
||||
void func_8006A98C(int a0, int a1, int a2)
|
||||
{
|
||||
int *p = *(int **)(a0 + 12);
|
||||
|
||||
if (a1 & 0xff) {
|
||||
p[20] += p[24];
|
||||
p[21] += p[25];
|
||||
p[22] += p[26];
|
||||
}
|
||||
if (a2 & 0xff) {
|
||||
p[52] += p[56];
|
||||
p[53] += p[57];
|
||||
p[54] += p[58];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user