phase12: merge 6 — worker A's 572 B body -> 621 bodies / 630 regions; exclude the 0x800C3490 fragment
Worker A's third claim, verified from a fresh --work dir against the exact md5 in the claim,
merged to a candidate, gated whole-binary, promoted only on result=MATCH. It is the largest body
any worker has taken this phase (572 B).
sf3_match gate c_regions=630 differing_bytes=0 result=MATCH
sha1 e173426c157384ebf1b6caf8c6fea18a85a14af9 (unchanged)
make check exit 0 make extents-verify regions=630 disagreements=0 AGREE
make worklist listed=989, excluded_already_registered=630
registry audit 630 rows, 621 distinct sources, 0 missing, 3 carry cc1bin
**ADJACENCY IS NOW 3-FOR-3 FOR WORKER A, ACROSS THREE BANDS.** 0x800297F4 (204 B, band 0) ->
0x800298C0 (392 B, band 1) -> 0x80029A48 (572 B, band 2): three CONSECUTIVE bodies, 1168 bytes,
four spellings total, with the density ranker not involved in any of the three picks. The chain
ends where no row starts at the next boundary. That is the strongest single piece of dispatch
evidence in the phase, and it is why the fresh-band files were re-cut to put adjacency above
density inside a band.
Worker A's lever, recorded because it is an operator RECOVERY rule rather than a spelling tip: the
original is `if ((x1 < 0 && x2 > 0) || (x1 > 0 && x2 < 0))`, and the truth-table-equivalent
if/else ladder is EXACTLY ONE INSTRUCTION SHORT. The emitted stream is `bgez x1` / `bgtz x2` /
`blez x1` / `bgez x2` -- each term's first test branches over its own second test -- and the ladder
has no `bgez x1` to emit. So the four BRANCH SENSES let you write the operator down without
guessing; cookbook 83's "branch direction distinguishes && from ||" turned into a recovery rule.
And `(x1 ^ x2) < 0` is the same predicate with the wrong codegen: the original compares.
**0x800C3490 IS EXCLUDED FROM THE WORKLIST.** Cookbook 114 (worker A, Phase 10) records it as a
FRAGMENT: it starts mid-expression, its body is a SHARED TAIL (`addiu sp,sp,48; jr ra`) that also
appears at 0x800C3470-0x800C348C, and it cannot be matched standalone. The extents table still
grades it `exact` with `term=jr_ra` because the boundary walk sees a well-formed terminal, so the
tool cannot catch it -- which is exactly why it needs a recorded exclusion rather than a tool rule.
Worker A recognised it for the SECOND time in Phase 12 and skipped it instead of spending reading
budget, which is the signal that the exclusion belongs in the Makefile: a row that has to be
re-identified by hand every phase is a row the dispatch should not be offering.
Added with its provenance in the Makefile comment, the same mechanism Phase 10 used for
0x8010080C's false extent start. excluded_named_exclusion 8 -> 9.
Ledger updated with merges 2-5, the cc1bin lever and its gate, the mechanism correction, the gp
pair rewrite and both of my failures in it, the dispatch measurement (369 of 993 rows abut SOME
region = noise; only 5 abut a PHASE-12 match = the signal), and the partition-membership fix.
This commit is contained in:
@@ -123,6 +123,16 @@ dupes: validate
|
||||
# boundary. It is now caught earlier by sf3_triage's general `restores_unsaved`
|
||||
# rule (worker A's independent scan re-derived the same row from a different
|
||||
# signal), so this row is retained only as the provenance record for that defect.
|
||||
#
|
||||
# 0x800C3490 was added in Phase 12 as a FRAGMENT, not a function (cookbook 114,
|
||||
# found by worker A and re-confirmed by the coordinator against the registry). It
|
||||
# starts mid-expression, its body is a SHARED TAIL (`addiu sp,sp,48; jr ra`) that
|
||||
# also appears at 0x800C3470-0x800C348C, and it cannot be matched standalone. The
|
||||
# extents table still grades it `exact` with `term=jr_ra`, because the boundary walk
|
||||
# sees a well-formed terminal -- so the tool cannot catch this one. Excluded rather
|
||||
# than left in the dispatch files, where it costs every fresh-band worker a reading
|
||||
# budget before it is recognised again. Worker A identified it for the second time in
|
||||
# Phase 12 and skipped it rather than spending that budget.
|
||||
worklist: validate
|
||||
@test -f "$(TRIAGE_TOOL)"
|
||||
@test -f "$(EXTENTS)"
|
||||
@@ -133,7 +143,8 @@ worklist: validate
|
||||
--exclude 0x80108034 --exclude 0x8010804C \
|
||||
--exclude 0x80012A10 --exclude 0x80012AE0 \
|
||||
--exclude 0x80012CFC --exclude 0x80012B20 \
|
||||
--exclude 0x8008A198 --exclude 0x8010080C --out "$(WORKLIST)" --force
|
||||
--exclude 0x8008A198 --exclude 0x8010080C \
|
||||
--exclude 0x800C3490 --out "$(WORKLIST)" --force
|
||||
|
||||
# Verification gates. `test` is synthetic-only and needs no game input;
|
||||
# `check` adds the extents check and the full-binary byte gate (which do).
|
||||
|
||||
+776
-778
File diff suppressed because it is too large
Load Diff
@@ -119,6 +119,7 @@
|
||||
0x800290F0 0x80029118 src/func_800290F0.c
|
||||
0x800297F4 0x800298C0 src/func_800297F4.c
|
||||
0x800298C0 0x80029A48 src/func_800298C0.c
|
||||
0x80029A48 0x80029C84 src/func_80029A48.c
|
||||
0x8002A9D4 0x8002AA18 src/func_8002A9D4.c
|
||||
0x8002AC84 0x8002ACBC src/func_8002AC84.c
|
||||
0x8002C6EC 0x8002C728 src/func_8002C6EC.c
|
||||
|
||||
|
@@ -371,3 +371,112 @@ And I then **repeated my own pipeline mistake on a fresh tool**: `./tools/sf3_me
|
||||
tail -4` reported `exit=0` for cases that actually exit 1. Twice now. The ledger's T0 note says *capture
|
||||
the exit code directly, never through a pipeline* — and it is a habit, not a slip, so it is now recorded
|
||||
as one.
|
||||
|
||||
### Merges 2-5 — **620 bodies / 629 regions** (from 602 / 611 at the Phase 11 close)
|
||||
|
||||
| merge | source | bodies | registry |
|
||||
|---|---|---|---|
|
||||
| 1 | C — 2 twins of registered bodies | 604 | 613 |
|
||||
| 2 | B — 3 rows, one translation unit | 607 | 616 |
|
||||
| 3 | D — 5 rows incl. **2 Goal-B twins** | 612 | 621 |
|
||||
| 4 | A — 2 rows incl. **an OVERTURN** | 614 | 623 |
|
||||
| 5 | B, D, C — 5 rows incl. **the first 3 `cc1bin` regions** | **620** | **629** |
|
||||
|
||||
Every row re-verified by me from a fresh `--work` directory against the exact md5 in the claim,
|
||||
merged to a CANDIDATE, gated whole-binary, promoted only on `result=MATCH`. Merge 3 carried the
|
||||
every-3rd-merge full audit: `make clean && make all` exit 0, `cmp` exit 0, SHA-1 unchanged,
|
||||
extents AGREE, `git status --short src/` reviewed, firewall clean.
|
||||
|
||||
**`sf3_match gate` at 629: `differing_bytes=0 result=MATCH`,
|
||||
sha1 `e173426c157384ebf1b6caf8c6fea18a85a14af9`, unchanged since Phase 9.** `make worklist`
|
||||
gives `listed=991, excluded_already_registered=629`; registry audit 629 rows ordered,
|
||||
non-overlapping, **620 distinct sources, 0 missing**; `make test` 318.
|
||||
|
||||
### The alternative-cc1 lever (`cc1bin=`) — approved, built, and RESTRICTED BY THE GATE
|
||||
|
||||
**The census that justifies it, verified three times independently (worker C, worker D, me):**
|
||||
|
||||
> **623/623 REGISTERED regions contain EXACTLY ONE `jr $31`. ZERO contain two or more.
|
||||
> 12 of the 193 NEGATIVES rows contain two or more.** That is the whole blocked class.
|
||||
> Counting only `0x03E00008` — `j $31` is not a real encoding, so the census is pure `jr $31`.
|
||||
|
||||
**Not a flag:** 14 flag sets on the default cc1 all yield one exit. It is the **compiler binary**.
|
||||
|
||||
**And it produces bodies** — the three rows now IN the registry, all with `cc1bin=gcc-2.8.1-psx`:
|
||||
|
||||
| region | size | default cc1 | 2.8.1 |
|
||||
|---|---|---|---|
|
||||
| `0x800FF43C` | 64 B | 56 LENGTH-MISMATCH | **MATCH** |
|
||||
| `0x800FF47C` | 64 B | 60 LENGTH-MISMATCH | **MATCH** |
|
||||
| `0x80108578` | 56 B | 52 LENGTH-MISMATCH | **MATCH** |
|
||||
|
||||
`0x800FF43C`/`0x800FF47C` are a twin pair (d=2/16) **and** two of the rows worker F left open at
|
||||
the Phase 11 close. `0x80108578` needed a SECOND lever: the original's loop back edge targets the
|
||||
function's FIRST instruction, so the guard must be re-tested each iteration
|
||||
(`for(;;){ if (a0==0) return 0; ... }`); a `while` spelling rotates the loop and leaves exactly
|
||||
the 1-byte residual an earlier attempt had recorded.
|
||||
|
||||
**The restriction is mechanical, not documentary.** A region naming `cc1bin` is refused unless its
|
||||
original body has ≥2 exit jumps, so the lever can only touch a row with a **named mechanism in the
|
||||
bytes**. Reason: the gate cannot catch a wrong compiler choice — byte-exact is byte-exact — so the
|
||||
only defence that works is requiring the mechanism. Verified both ways: a 2-exit row builds and
|
||||
gates MATCH; a 1-exit row exits 2 with the count and the requirement named. `cc1bin` takes a bare
|
||||
vendored directory name under `tools/old-gcc/` (no slashes), so the nameable set is the pinned set.
|
||||
|
||||
### ⚠ A MECHANISM I RECORDED AS A FINDING, AND HAD TO CORRECT
|
||||
|
||||
I wrote into `tools/sf3_match` that "2.7.2 emits one shared return epilogue while 2.8.x emits one
|
||||
per return". **Worker C's 4-return probe reproduces that and I reproduced C's probe exactly — but
|
||||
worker D could not reproduce it at all**: on a 3-return framed probe AND a 3-return frameless
|
||||
probe, **all ten builds emitted one shared exit**. Both probes are real, so the compiler-side
|
||||
result is **shape-dependent** and the general claim is false. Corrected in the file, marked as
|
||||
corrected rather than deleted, because the next reader will otherwise re-derive it and believe it.
|
||||
|
||||
**What survives:** the census, and the three byte-exact bodies. **What does not:** the mechanism
|
||||
sentence. This is the second time this phase that a *probe* was mistaken for a *proof* — the first
|
||||
was my own density statistic, computed on the wrong bytes.
|
||||
|
||||
### The 2.8.x gp-access pair — and my first two attempts at it were both wrong
|
||||
|
||||
2.8.x addresses a global as `lui $R,%hi(SYM)` + `<op> $r,%lo(SYM)($R)` where 2.7.2 emits a bare
|
||||
`sw $2,SYM`; both lines were invisible to the existing regexes, so a gp-touching region built with
|
||||
an alt cc1 came out **4 bytes long**. Now a pair rewrite: the access becomes `%gp_rel(SYM)($gp)`
|
||||
and the `%hi` is dropped. My two failures, recorded in the code:
|
||||
|
||||
1. it required the two lines to be **adjacent**, and maspsx emits a **BLANK LINE** between them —
|
||||
so it silently did nothing;
|
||||
2. it deleted the `%hi` **unconditionally**, which leaves a later access to the same global
|
||||
addressing a register nothing wrote — right length, plausible, wrong.
|
||||
|
||||
The deletion is now gated on every use of the register before it is redefined being rewritten, with
|
||||
redefinition tracked explicitly and `jalr` deliberately non-defining so the rule can only keep a
|
||||
`%hi` alive, never delete a live one. No regression: the 616-region gate was byte-exact before and
|
||||
after.
|
||||
|
||||
### The dispatch result — measured, and it re-ranks the fresh band
|
||||
|
||||
**Every body Phase 12 has produced came from adjacency, a family/twin finder, or a named structural
|
||||
class. None came from the top of a density-ranked file** — and in exactly that position worker B hit
|
||||
the leading indicator (four consecutive small-residual near-misses, no new mechanism) and correctly
|
||||
stopped to report rather than grind.
|
||||
|
||||
So the fresh-band files are now ordered **band-first, then ADJACENCY TO A PHASE-12 MATCH, then
|
||||
density**. The measurement that forced the refinement: **369 of 993 rows (37%) abut SOME registered
|
||||
region, in every band (153/82/86/48)** — so plain adjacency is close to noise. But only **FIVE rows
|
||||
abut a region matched THIS PHASE**, and the mechanical key independently flagged `0x8006B398`,
|
||||
which worker D had already chosen from B's handoff. **The key agrees with the human pick**, which is
|
||||
the only validation that matters for a dispatch heuristic.
|
||||
|
||||
### Partition membership — wrong twice, now a hash
|
||||
|
||||
I told the workers membership was stable and it was not, twice:
|
||||
|
||||
1. partitioned by **density-sorted position** → correcting the payload bug re-sorted the list and
|
||||
moved rows (band-0 top-20 overlap **1/20**);
|
||||
2. partitioned by **(band, address) POSITION** → stable only while the row SET is unchanged, and the
|
||||
set shrinks by design as rows register (1001 → 993), shifting every assignment after a removal.
|
||||
|
||||
Now `crc32("band:address") % 3` — membership depends only on the row's own identity. **Verified:
|
||||
removing 10 rows moves 0 assignments.** Buckets came out 310/330/353, which is a 12% spread; the
|
||||
partition is a **default, not a wall**, and abutting neighbours are explicitly claimable outside it.
|
||||
`crc32`, not Python's `hash()`, which is salted per process and would re-partition every run.
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* func_80029A48 — 572 bytes at 0x80029A48..0x80029C84
|
||||
*
|
||||
* A fixed-point (Q12, 4096 == 1.0) vector-pair preparation step. It copies both
|
||||
* input vectors into the output record, computes their dot product / 4096, and —
|
||||
* only when bit 1 of the record's flag word is set — also projects each vector
|
||||
* onto the record's four-word `m` row. A sign-disagreement test on those two
|
||||
* projections decides whether the dot product and the second vector are negated
|
||||
* (the "turn the pair around" case); then, if the dot product is below 0xFD8
|
||||
* (4056, i.e. an angle large enough to need it), a two-call helper chain computes
|
||||
* an interpolation factor into +0x20/+0x24 and the record's low flag bit is set
|
||||
* or cleared accordingly.
|
||||
*
|
||||
* THE RECORD LAYOUT, read off the displacements (all words, no padding):
|
||||
* +0x00 a.v[4] the first vector, copied from a1 (4 words)
|
||||
* +0x10 b.v[4] the second vector, copied from a2 (4 words)
|
||||
* +0x20 f8 helper result
|
||||
* +0x24 f9 helper result
|
||||
* +0x28 flags bit 0 = "has interpolation factor", bit 1 = "project"
|
||||
* +0x2c m[4] the projection row
|
||||
*
|
||||
* The observed instructions are:
|
||||
* addiu sp,sp,-24 / sw s0,16(sp) / move s0,a0 / sw ra,20(sp)
|
||||
* lw v0,0(a1) / lw v1,4(a1) / lw a0,8(a1) / lw a3,12(a1)
|
||||
* sw v0,0(s0) / sw v1,4(s0) / sw a0,8(s0) / sw a3,12(s0) p->a = *a
|
||||
* ...the same four-and-four for a2 at 16..28(s0) p->b = *b
|
||||
* (a's components are then loaded once into t1/t0/a3/t3 and b's into
|
||||
* t7/t6/t5/t4 and reused for every product below)
|
||||
* dot = (a0*b0 + a1*b1 + a2*b2 + a3*b3), then `bgez`+`addiu 0xfff`+`sra 12`
|
||||
* if (p->flags & 2) {
|
||||
* x1 = (a . m) / 4096 (same idiom, dividend is a0 so the `sra`
|
||||
* x2 = (b . m) / 4096 lands in the `bgez` delay slot)
|
||||
* if ((x1 < 0 && x2 > 0) || (x1 > 0 && x2 < 0)) f = 1
|
||||
* } else if (dot < 0) f = 1
|
||||
* if (f) { dot = -dot; p->b.v[0..3] = -p->b.v[0..3]; }
|
||||
* if (dot < 4056) { q = 0x80029E88(dot); p->f8 = q; r = 0x800F9344(q);
|
||||
* p->f9 = r; if (r) { p->flags |= 1; return; } }
|
||||
* p->flags &= ~1
|
||||
*
|
||||
* THE LEVER THAT CLOSED IT (the first spelling was 4 bytes SHORT). The two
|
||||
* projections must be tested as ONE sign-disagreement condition written as a
|
||||
* two-term `||` CHAIN:
|
||||
*
|
||||
* if ((x1 < 0 && x2 > 0) || (x1 > 0 && x2 < 0)) f = 1;
|
||||
*
|
||||
* The emitted branch DIRECTIONS are the evidence, and they distinguish `||` from
|
||||
* an if/else ladder without guessing at the operator (cookbook 83): four
|
||||
* comparisons in the order `bgez x1` (skip term 1), `bgtz x2` (arm 1 taken),
|
||||
* `blez x1` (skip term 2), `bgez x2` (arm 2 not taken) — each term's first test
|
||||
* branching over its own second test. Writing it as `if (x2 > 0) f = 1; else if
|
||||
* (x1 > 0 && x2 < 0) f = 1;` — the same truth table — loses the `bgez x1` test
|
||||
* and the row comes out exactly ONE instruction short. `(x1 ^ x2) < 0` is the
|
||||
* same predicate but must NOT be used: the original compares, it does not xor.
|
||||
*
|
||||
* THREE MORE BYTE-BEARING DETAILS:
|
||||
* - `p->a = *a; p->b = *b;` are STRUCT ASSIGNMENTS, not element stores. The
|
||||
* emitted four-loads-then-four-stores per vector is the signal (cookbook 76 /
|
||||
* 102); element stores serialise behind maspsx load-delay nops instead.
|
||||
* - Every `/ 4096` is a DIVISION (cookbook 97): the `bgez` + `addiu 0xfff` sign
|
||||
* bias is present on all three of them. `>> 12` would emit a bare `sra`.
|
||||
* - The `f = 0` initialisation is scheduled into the delay slot of the dot
|
||||
* product's division fixup, so it uses no instruction of its own; the `f`
|
||||
* test is `f & 0xff`, i.e. the source narrows it (a `char`/`short` local).
|
||||
*
|
||||
* The two helper results are stored through the calls' own delay slots
|
||||
* (`sw a0,0x20(s0)` after the first, `sw v0,0x24(s0)` after the second), and the
|
||||
* `p->flags &= ~1` block is SHARED between the "dot too small" exit and the
|
||||
* "helper returned zero" exit — cc1 cross-jumped them, so both spellings of that
|
||||
* tail are equivalent to the bytes.
|
||||
*
|
||||
* LIMITS: the struct/field names, the vector and record meanings (the quaternion
|
||||
* or rotation reading of the two vectors and of `m`), the semantics of
|
||||
* 0x80029E88 and 0x800F9344, the meaning of the flag bits, and the 4056
|
||||
* threshold are hypotheses reconstructed from the disassembly. Only the compiled
|
||||
* bytes are evidence.
|
||||
*/
|
||||
|
||||
struct V4 {
|
||||
int v[4];
|
||||
};
|
||||
|
||||
struct REC {
|
||||
struct V4 a; /* 0x00 */
|
||||
struct V4 b; /* 0x10 */
|
||||
int f8; /* 0x20 */
|
||||
int f9; /* 0x24 */
|
||||
int flags; /* 0x28 */
|
||||
int m[4]; /* 0x2c */
|
||||
};
|
||||
|
||||
extern int func_80029E88(int a0);
|
||||
extern int func_800F9344(int a0);
|
||||
|
||||
void func_80029A48(struct REC *p, struct V4 *a, struct V4 *b)
|
||||
{
|
||||
int dot;
|
||||
int x1;
|
||||
int x2;
|
||||
int f = 0;
|
||||
|
||||
p->a = *a;
|
||||
p->b = *b;
|
||||
|
||||
dot = (a->v[0] * b->v[0] + a->v[1] * b->v[1] + a->v[2] * b->v[2]
|
||||
+ a->v[3] * b->v[3]) / 4096;
|
||||
|
||||
if (p->flags & 2) {
|
||||
x1 = (a->v[0] * p->m[0] + a->v[1] * p->m[1] + a->v[2] * p->m[2]
|
||||
+ a->v[3] * p->m[3]) / 4096;
|
||||
x2 = (b->v[0] * p->m[0] + b->v[1] * p->m[1] + b->v[2] * p->m[2]
|
||||
+ b->v[3] * p->m[3]) / 4096;
|
||||
if ((x1 < 0 && x2 > 0) || (x1 > 0 && x2 < 0))
|
||||
f = 1;
|
||||
} else {
|
||||
if (dot < 0)
|
||||
f = 1;
|
||||
}
|
||||
|
||||
if (f & 0xff) {
|
||||
dot = -dot;
|
||||
p->b.v[0] = -p->b.v[0];
|
||||
p->b.v[1] = -p->b.v[1];
|
||||
p->b.v[2] = -p->b.v[2];
|
||||
p->b.v[3] = -p->b.v[3];
|
||||
}
|
||||
|
||||
if (dot < 4056) {
|
||||
int q;
|
||||
int r;
|
||||
|
||||
q = func_80029E88(dot);
|
||||
p->f8 = q;
|
||||
r = func_800F9344(q);
|
||||
p->f9 = r;
|
||||
if (r != 0) {
|
||||
p->flags |= 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
p->flags &= ~1;
|
||||
}
|
||||
Reference in New Issue
Block a user