Skip to content

Correct the ov006 near-miss records: a defective draft faked a zero-word floor - #2701

Open
andrewboudreau wants to merge 6 commits into
mainfrom
fix/ov006-nearmiss-record-corrections-0913
Open

andrewboudreau wants to merge 6 commits into
mainfrom
fix/ov006-nearmiss-record-corrections-0913

Conversation

@andrewboudreau

Copy link
Copy Markdown
Collaborator

Four record corrections around func_ov006_020d27dc and func_ov006_020e5450. No byte changes anywhere: rombuild --no-rom reads 11,205/11,205 reproducing, 0 mismatching, 106/106 exact, PASS — identical to the pre-change baseline, so every retype here is byte-inert.

1. The func_ov006_020d27dc floor was resting on a defective draft

The banked source spelled one site *(s32 *)((int)p + 0x5374). Disassembly shows that cast is the first mismatch:

ROM   add r0, sb, #0x5000    /  ldr r1, [r0, #0x374]
CAND  ldr r0, [pc, #0x8c4]   /  ldr r0, [sb, r0]

That one spurious literal-pool word is the entire 3656-vs-3652 delta — the row looks size-exact only because of the defect. The real budget is +1 word, not zero.

The corrected source could not be banked as a replacement: at 3652 bytes evaluate_full sentinels the size mismatch at 999, and the strictly-improving upsert correctly refuses that against 17. So the row keeps its source and gains a floor mark carrying the mechanism, the md5 pair, the +1-word budget and the refutations. The size-exact 14-divergence variant stays unbanked — it stores to p+0x46d4, which the ROM's low arm does not do.

A correction to the standing note: I predicted all five (int)p sites were load-bearing. Three of the four removable ones are byte-inert — only the site feeding the if-converted compare pays. The old bound ("byte-strided base ⇒ inert") is wrong; the rule is probe the site that feeds the divergence, not a convenient one.

2. The bogus "29" was in five places, not two

func_ov006_020e5450 had been recorded at 29 from an attempt whose own note says size 0x52c vs 0x560. Beyond the TU banner and config/match_attempts.jsonl, it was also in config/tu_manifest.d/ov006/dScMgCurling2_c.json and two sites in notes/data/class-facts/dScMgCurling2_c.json — the latter also carrying a stale "banked draft is at 324" that was actively steering future work off the row. Re-measured: 191/344 at exactly 0x560.

The attempt row's divergences is cleared to null rather than overwritten with 191. That attempt's source is gone and cannot be rescored, and writing my measurement into another author's run would misattribute it. null is the schema's own shape — absent on 889 of 2,073 rows — and the honest 191/344 is logged as its own row via log_attempt.py.

3. func_02012718 — the decl-agreement gate had 14 baselined disagreements

The definition was the wrong side, but fixing it alone would have flipped 12 agreeing declarations into new red. Fixed the whole set instead: definition, 13 pointer mirrors, 16 call sites and one u32. Now 24 declarations, 0 disagreements.

Baseline churn is 14 removed, 0 added — the baseline shrinks, deletions only. That is deliberate: leaving stale entries would mask a future re-break at those same sites.

4. func_ov006_020e5450 — a (void) veneer the gate could not see

It reported 0 disagreements only because nothing defines the symbol, so the veneer's (void) was invisible. Now void (char *, int), agreeing with func_ov006_020e5b7c.c, still 0. The veneer was byte-gated individually, positive control first: it matched before and after.

Gates

  • check_src_tu_compiles: 177/177
  • check_decl_agreement --check: no new disagreements
  • rombuild --no-rom: PASS, 11,205/11,205, 106/106 exact — unchanged from baseline

Rebased onto ca1511f81: origin/main moved mid-session (1547d5fbfca1511f81) because sibling worktrees share the ref store. Every gate above was re-run on the new base.

What is deliberately not here

A structural note on the func_ov006_020e5450 ordering rules was written and then dropped. Section 6cz, landed on main while this branch was in flight, derives the same two rules, names this branch's exact 193/db5e3dcf cell, and carries the line 191 → 187. Publishing a competing "191 is the honest measurement" beside it — under a section letter 6cz had just taken — would have been noise. The prose and the attempt row point at 6cz instead.

Plain English

Four bookkeeping fixes, no change to any compiled byte. The main one: a reconstruction we had recorded as "perfect size, 17 details wrong" was only the right size because of a mistake in it — remove the mistake and it's a word too short, which means the real gap is bigger than the record claimed. Separately, a wrong figure from an old run had been copied into five different files, one of which was steering people toward a draft that doesn't exist. And a function's declared argument types disagreed with its definition in fourteen places; fixing only the obvious one would have broken twelve others, so all twenty-four now agree.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G1Gdj5fTjMsW2VjRLpXxYA

Three in-tree records about ov006 near-misses were wrong. Each is
re-measured here rather than carried forward from the stale number.

1. func_ov006_020d27dc: the banked source's defect, and a floor mark.

The banked c_source spells `*(s32 *)((int)p + 0x5374)` at line 241. The
ROM addresses that site as `add r0,sb,#0x5000 / ldr r1,[r0,#0x374]`; the
cast instead forces `ldr r0,[pc,#0x8c4] / ldr r0,[sb,r0]` plus a
literal-pool word. Dropping that one cast gives 3652 bytes, md5
8462f11dba034fd64356bc06233a65cf, byte-equal to the ROM outside a single
word with the whole literal pool identical. The only residue is the ROM's
`blt` at +0x564, so the budget is exactly +1 word, not zero.

The corrected source is deliberately NOT banked. evaluate_full sentinels
its size mismatch at 999 and the strictly-improving upsert refuses it
against the stored 17, and the stored 17 is size-exact only by virtue of
that spurious pool word. The row therefore keeps divergences 17 and its
c_source unchanged, and carries a floor mark (class cond_opt) recording
the mechanism, the measurement and the refutations. A 14-divergence
size-exact variant exists but stores to p+0x46d4, which the ROM's low arm
does not do; it is logically wrong and is not banked either.

2. func_ov006_020e5450: the 29 was never a comparable number.

Five in-tree records claimed a best attempt of 29 divergences. That
attempt's own note records "size 0x52c vs 0x560": it was scored against a
candidate 52 bytes shorter than the target, so the 29 is a truncated
verdict over a short window, not a divergence count for this function. It
is not comparable with any size-exact score, and it made the function look
far closer than it is.

Re-measured at the exact size 0x560 (1376 bytes, 344 words): 191 divergent
words of 344, which is what the bank already held; the bank row is
unchanged. The stale 29 is corrected in the TU banner, the TU manifest,
the class-facts writer guidance (which also carried a stale "banked draft
is at 324"), and the attempt row itself. That row's divergences field is
cleared rather than rescored: the attempt's source no longer exists in the
tree, so nothing can re-score it, and a cleared field is the schema's own
shape for an attempt without a score. The honest re-measurement is logged
as its own attempt row instead of being attributed to someone else's run.

3. Two wrong parameter lists.

src/func_02012718.c defined `void func_02012718(void *a, int b)`. The ROM
and include/decl_common.h say (int, int), and the first parameter is an
integer sound ID, not a pointer. The definition was the wrong side, but
the agreement gate keys declarations against the definition, so correcting
it alone would have flipped twelve agreeing declarations into new
disagreements and reddened every push in the repo. Corrected across the
whole set instead: the definition, thirteen pointer-spelled mirror
declarations and their sixteen call sites (each passing the ID as
`(void *)0x...`), plus one (int, u32) spelling. func_02012718 now has
twenty-four declarations and zero disagreements, down from fourteen.

src/func_ov006_020e5b70.c declared `func_ov006_020e5450(void)`. The
target's prologue is `mov sb,r1; mov sl,r0`, which proves two parameters,
and src/func_ov006_020e5b7c.c already spelled (char *, int). The veneer
forwards r0/r1 untouched, so its own parameter list is the target's; it is
now spelled that way and still assembles to the same twelve bytes.

Verification: rombuild --no-rom reports 11,204 of 11,204 source-built
functions reproducing, 0 mismatching, module fidelity 106/106 exact --
identical to the pre-change baseline, so every retype is byte-inert.
check_src_tu_compiles 177/177. check_decl_agreement --check reports no new
disagreements, with fourteen banked disagreements gone and none added, so
the baseline is shrunk by exactly those fourteen (deletions only) rather
than left stale to mask a future re-break at the same sites.

Also: the cast lever's stated bound in notes/mwccarm-codegen.md was wrong.
It claimed that on a `char *` base both spellings compile to the same 3652
bytes, md5 8462f11d... That md5 is the cast-dropped object alone; the
banked source produces 163fe692... at 3656. Of the four removable `(int)p`
sites in that source, three are byte-inert and the fourth is worth four
bytes, so a byte-strided base does not make the cast inert, it makes it
inert at most sites. The cast object emits exactly one register-offset
load in the whole 914-instruction function, at the site whose value feeds
the if-converted compare. The passage now says to probe the site that
feeds the divergence rather than a convenient one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G1Gdj5fTjMsW2VjRLpXxYA
@tangos-validator

tangos-validator Bot commented Sep 14, 2026

Copy link
Copy Markdown

✅ PR validation — Passed

Committed merge introduces no reconstruction or attribution regression.

Full merge validation

Check Result
Committed test merge yes
Byte-verified functions 11,177 / 11,344 (98.53%, +0)
Byte-verified code bytes 2,136,384 / 2,211,124 (96.62%, +0)
Claimed, not byte-verified 147 functions, 51,284 bytes (+0)
Perfect source moves 0 R100
Contributor credit 0 added, 0 changed, 0 lost
Relocation check 47 checked; 47 VERIFIED
Port reference check 423 checked; 0 stale
Module fidelity 106/106 exact; 100.000000% compared bytes
Code linked from verified source 11,206 functions, 2,150,424 bytes (96.08%)
Module bytes from source 2,150,424 / 3,049,600 (70.5%); 806,508 (26.4%) are data no delink entry reaches
Retail-gap contribution 894,192 module bytes (87,684 function-code; 806,508 data/non-function)
ROM data reproduced from source 717 symbol(s) exact, 231 partial, 4 differ

Byte-verified means the range carries complete in a delinks.txt, so the ROM build compiled it and compared it to the cartridge. The 147 claimed functions have a src/ file named after the symbol with no NONMATCHING banner, and nothing compiles them -- dsd fills their addresses with the ROM's own bytes. Both together are the 11,324 this project calls matched.

Per-file link-check detail

All 17 changed file(s) compile to the ROM byte-for-byte with correct relocation targets.

File Symbol Result Slots checked
src/_ZN20cMgSmartball_board_c12SaveSnapshotEv.cpp _ZN20cMgSmartball_board_c12SaveSnapshotEv ✅ verified 1
src/_ZN21cMgSmartball_kinoko_c12SaveSnapshotEv.cpp _ZN21cMgSmartball_kinoko_c12SaveSnapshotEv ✅ verified 1
src/_ZN21cMgSmartball_pakkun_c12SaveSnapshotEv.cpp _ZN21cMgSmartball_pakkun_c12SaveSnapshotEv ✅ verified 1
src/actors/dScMgCurling2_c.cpp _ZN15dScMgCurling2_cD1Ev + _ZN15dScMgCurling2_cD0Ev + func_ov006_020e38b0 + func_ov006_020e3948 + _ZN15dScMgCurling2_c10SpawnValueEii + func_ov006_020e3b9c + func_ov006_020e3bc4 + _ZN15dScMgCurling2_c13StepYRampDownEi + _ZN15dScMgCurling2_c11StepYRampUpEi + _ZN15dScMgCurling2_c12StepYRestartEi + _ZN15dScMgCurling2_c11StepXSettleEi + _ZN15dScMgCurling2_c12StepXPushNegEi + _ZN15dScMgCurling2_c12StepXPushPosEi + _ZN15dScMgCurling2_c9StepXPickEi + _ZN15dScMgCurling2_c9StepXAndYEi + _ZN15dScMgCurling2_c15StepXSettleFastEi + _ZN15dScMgCurling2_c16StepXPushNegFastEi + _ZN15dScMgCurling2_c16StepXPushPosFastEi + _ZN15dScMgCurling2_c13StepXPickFastEi + _ZN15dScMgCurling2_c9StepXOnlyEi + _ZN15dScMgCurling2_c12PickStepModeEi + func_ov006_020e4800 + func_ov006_020e48d4 + func_ov006_020e4a84 + func_ov006_020e4b00 + func_ov006_020e4b78 + _ZN15dScMgCurling2_c10DragUpdateEv + _ZN15dScMgCurling2_c9DragBeginEv + func_ov006_020e4fe8 + func_ov006_020e507c + func_ov006_020e513c ✅ verified 31
src/func_02012718.c func_02012718 ✅ verified 1
src/func_ov006_020cb2b4.c func_ov006_020cb2b4 ✅ verified 1
src/func_ov006_020d7f5c.c func_ov006_020d7f5c ✅ verified 1
src/func_ov006_020e1b54.c func_ov006_020e1b54 ✅ verified 1
src/func_ov006_020e5b70.c func_ov006_020e5b70 ✅ verified 1
src/func_ov006_020fca1c.c func_ov006_020fca1c ✅ verified 1
src/func_ov006_020fcd8c.c func_ov006_020fcd8c ✅ verified 1
src/func_ov006_020fce04.c func_ov006_020fce04 ✅ verified 1
src/func_ov006_02102d6c.c func_ov006_02102d6c ✅ verified 1
src/func_ov006_021115cc.c func_ov006_021115cc ✅ verified 1
src/func_ov006_02112ad8.c func_ov006_02112ad8 ✅ verified 1
src/func_ov006_02114ec0.c func_ov006_02114ec0 ✅ verified 1
src/func_ov006_0211e8a8.c func_ov006_0211e8a8 ✅ verified 1

The private worker commits a test merge, builds the stock ROM profile, compares every executable module, measures matched and source-built code, checks contributor lineage, and verifies affected relocations. The mod profile is opt-in and is not part of this merge gate.

@andrewboudreau andrewboudreau left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Humanizer/integrator review of c74a9b9e0b2607157b88feed1e7798b686134498.

I read the sound-ID contract and tail-veneer changes, including their affected callers and the Curling2 evidence corrections. The sound call sites now pass numeric IDs directly, and the veneer explicitly forwards its two arguments. I independently ran prepush_linkcheck.py over this head's merge-base range: 47 checked, 47 VERIFIED, zero warnings and zero blocking results, including the complete touched Curling2 TU. This proves the scoped byte/relocation result, not complete source acceptance.

SOUND-2701-01 (correctness of the declared interface), src/func_02012718.c:2-4: the correction stops one call short. src/func_020127ec.c actually defines six int parameters, but the changed wrapper still declares its second parameter void* and now casts the sound ID back to (void*)a. Replace that declaration with extern void func_020127ec(int, int, int, int, int, int); and pass a directly. I tested exactly this two-line substitution in a scratch .c under 2004/b56: emitted function bytes and relocation records are unchanged, and func_02012718 at arm9 0x02012718, size 0x3c, is VERIFIED, diffs: [], blind: 0. This removes the contradictory interface rather than moving the integer-to-pointer cast into the callee wrapper.

Fix this finding and refresh proof before source acceptance. The live source gate also requires this head to contain the current reviewable main base and have independently published review evidence for that composition. Preserve the distinction between the obsolete attempt's cleared metric and the new measurement; do not turn the historical 29 into an attributed 191. I have not merged, rewritten, or registered a source-review pass for this PR.

andrewboudreau and others added 4 commits September 14, 2026 14:48
`src/func_ov006_020fce04.c` conflicted only because the two sides edited
ADJACENT declaration lines, not the same one. Both edits are kept:

  this branch  func_02012718's first parameter, void * -> int, and the
               matching `(void *)` cast dropped from the call
  main         data_ov006_0213d954 respelled from a scalar to `[]`, and the
               use respelled to `[0]`

The body had already merged to exactly that combination; only the two-line
declaration block needed resolving, and neither side touched the other's line.

`nearmiss/db.jsonl` merged through the union driver: 29 rows, no duplicate
`(module, addr)` keys, key set exactly main's union this branch's.
`config/match_attempts.jsonl` has no byte-identical duplicated lines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4FxdKHG3a6hQRYRAbx29c
Second merge of main, needed because #2715 landed between the first one and
now: it replaced `tools/check_decl_agreement.py` and regenerated the whole
`config/decl-agreement-baseline.json`, so the first merge's baseline was banked
by a gate revision that no longer exists.

Baseline REGENERATED, not hand-resolved: main's copy taken first, then
`--update`. The six contradictions it banks are this branch's own churn --
five are `func_02012718`'s remaining `void *` declarations against the
`void (int, int)` definition this branch retypes, and one is
`func_ov006_020e5450` declared `char *` against the `dScMgCurling2_c *`
definition this branch introduces. The byte gate proves the retype:
106/106 exact, mismatching 0, ROM-build PASS, 409 real compiles.

`nearmiss/db.jsonl` doubled two rows through the union driver. Resolved by
provenance against merge-base fe05a37, not by comparing `divergences`:
  ov074 0x02121380  only main edited `floor` -- kept main's
  ov006 0x020d27dc  only this branch edited `floor` -- kept this branch's
29 rows, no duplicate keys, key set exactly main's union this branch's.
`config/match_attempts.jsonl` has no byte-identical duplicated lines.

Follow-up, NOT done here: the five `void *` declarations of `func_02012718`
are now banked as disagreeing with the definition. Correcting them to
`int` is a separate change that has to be rebuilt, since a declaration edit
can move instruction selection at the call site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4FxdKHG3a6hQRYRAbx29c
SOUND-2701-01. The previous correction stopped one call short: func_02012718's
first parameter became int, but the wrapper still declared func_020127ec's
second parameter void* and cast the sound ID back with (void *)a -- moving the
integer-to-pointer contradiction into the callee's declaration instead of
removing it.

The tree already agrees on int, and this file was the only dissenter:

  src/func_020127ec.c:6            void func_020127ec(int a0, int a1, int a2,
                                                      int a3, int a4, int a5)
  src/actors/dScMgD3DBase_c.cpp:142  void func_020127ec(int a0, int a1, int a2,
                                                        int a3, int a4, int a5)
  src/func_02012718.c:2 (was)      extern void func_020127ec(int, void*, int,
                                                             int, int, int)

So the callee's own definition and the one other prototyped declaration both
say six ints.

Reverified: 8759 reused from cache, 1 compiled, mismatching 0,
106/106 exact, ROM-build analysis PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4FxdKHG3a6hQRYRAbx29c
@andrewboudreau

Copy link
Copy Markdown
Collaborator Author

SOUND-2701-01: fixed in 9bd34006d. You were right that it stopped one call short.

-extern void func_020127ec(int, void*, int, int, int, int);
+extern void func_020127ec(int, int, int, int, int, int);
 void func_02012718(int a, int b){
-  func_020127ec(2, (void *)a, 4, 0, 0, func_020126e8(b));
+  func_020127ec(2, a, 4, 0, 0, func_020126e8(b));
 }

I censused the tree before changing it, since the last round's lesson was that the callee's own file can be the odd one out. Here it isn't — this wrapper was:

site spelling
src/func_020127ec.c:6 (the definition) (int, int, int, int, int, int)
src/actors/dScMgD3DBase_c.cpp:142 (int, int, int, int, int, int)
src/func_02012718.c:2 (int, void*, int, int, int, int) ← the only dissenter

(src/func_020126ac.c:1 is unprototyped extern int func_020127ec();, so it doesn't vote either way.)

Full build, not a scoped linkcheck:

8759 reused from cache, 1 compiled
source-built functions: 11,206  reproducing: 11,206  mismatching: 0
module fidelity: 106/106 exact, 100.000000% of compared bytes
ROM-build analysis: PASS

On your other point — noted, and I won't restate it as a general rule. The claim I'd defend is only the measured one: this pointer/int correction, at this arity, was byte-inert here, and that says nothing about parameter or return types in general, which can and do change argument passing, conversions and caller code.

The historical 29 stays a cleared metric of the obsolete attempt and is not folded into the new 191.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y4FxdKHG3a6hQRYRAbx29c

func_ov006_020e5b70 is a tail-call veneer, so its parameter list is its
target's.  Fixing the arity from 0 to 2 was right -- the target's prologue
`mov sb,r1; mov sl,r0` proves two parameters, and the veneer forwards r0/r1
untouched.  But spelling the first one `char *` asserts a pointee type the
definition contradicts: src/func_ov006_020e5450.cpp:24 defines it
`void func_ov006_020e5450(dScMgCurling2_c *self, int idx)`.

That turned a banked `arity:0` disagreement into a banked `param:#1 char *`
one -- debt traded, not paid.  `void *` pays it.  It carries the arity, which
is the byte-relevant part, and check_decl_agreement's _declines_to_answer
rule treats `void *` against any object pointer as the absence of a claim
rather than a wrong one, so the symbol needs no baseline entry at all.
A C shard cannot name a C++ class, so `void *` is also the only honest
spelling available here.

  check_decl_agreement: 18,226 -> 18,225 disagreements (param 7,699 -> 7,698),
  no new ones; the baseline shrinks by removals only.
  rombuild -j16 --no-rom: 1 compiled, 11,206 reproducing / 0 mismatching,
  module fidelity 106/106 exact, ROM-build analysis PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4FxdKHG3a6hQRYRAbx29c

@andrewboudreau andrewboudreau left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent recheck at 3242291da1aa8cdbb86884b9f42384a1fc365f94: SOUND-2701-01 is fixed. The wrapper declares func_020127ec with six integer parameters and forwards the numeric sound ID directly. Fresh strict linkcheck of func_02012718, arm9 0x02012718, size 0x3c, returns VERIFIED, diffs: [], blind: 0.

This closes that narrow interface finding. The PR still includes source and manifest changes in addition to ledger corrections, so its remaining source review and current-main composition requirements continue to apply. I am coordinating integration and have not treated the repaired wrapper or green private validation as a whole-candidate Source review pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant