Skip to content

Point seven PathPtr shadow structs at the real header - #2729

Merged
andrewboudreau merged 1 commit into
mainfrom
cpp/pathptr-header
Sep 18, 2026
Merged

andrewboudreau merged 1 commit into
mainfrom
cpp/pathptr-header

Conversation

@andrewboudreau

Copy link
Copy Markdown
Collaborator

First slice of phase 4 from notes/plan-cpp-language-mode.md — retiring shadow declarations.

Ten .cpp files declared their own struct PathPtr rather than including include/PathPtr.h, and all ten disagreed with each other:

file its idea of PathPtr
func_ov062_0211b3ac.cpp int a, b;
_ZN14UnchainedChomp13InitResourcesEv.cpp char pad_00[8]; + 4 methods
_ZN7SkiLift13InitResourcesEv.cpp char b[8];
_ZN8MantaRay8BehaviorEv.cpp char pad[8]; + 3 methods
func_ov019_0211127c.cpp no fields at all
func_ov092_021313b0.cpp int GetNode(...) const — the header says void

include/PathPtr.h is a real reconstructed header: named fields with offsets, documented meaning, and sizeof == 0x8 asserted. Seven of the ten now include it and delete their copy.

The other three are not a spelling problem

They declare PathPtr path; as a POD local — their shadow has no constructor — and then call the constructor explicitly by mangled name further down the body:

PathPtr path;                  /* shadow is POD: no code emitted here */
...
_ZN7PathPtrC1Ev(&path);        /* the ROM constructs it HERE */

The real header declares PathPtr(), so that same declaration emits a constructor call at the declaration point, giving two calls in the wrong order. Both versions compile; only the bytes tell you. Fixing them means moving the local down to where the ROM constructs it, which also moves its allocation rank, so it wants its own measured pass rather than being forced into this one.

Gates

gate result
rombuild --no-rom --no-cache full cold rebuild, all 8760 enrolled filesmismatching: 0, 106/106 exact, PASS
tiers_ratchet --check PASS — CONVERTED 2812 to 2816
eligible.py name set identical, 11243 / 11302
check_decl_agreement no new disagreements
langmode_audit local struct bodies 1676 → 1671
port_refcheck 423 references, 0 stale
MSVC port smoke build 230/230

The cold rebuild is deliberate: an incremental run reported 0 compiled here, because the shared object cache is keyed on content and these objects were already built. 0 compiled is this repo's classic false-green, so the count above is from a run with no cache at all.

Branches from main; touches no file in #2725, #2726, #2727 or #2728.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y4FxdKHG3a6hQRYRAbx29c

Ten .cpp files declared their own struct PathPtr instead of including
include/PathPtr.h, and all ten disagreed: `int a, b`, `char pad_00[8]`,
`char b[8]`, `char pad[8]`, one with no fields at all, and one declaring
`int GetNode(...) const` where the real header says `void`. That is the
shadow-declaration problem notes/plan-cpp-language-mode.md phase 4 exists
to retire. Seven of the ten now include the header and delete the copy.

The other three are NOT a spelling problem and are left alone. They declare
`PathPtr path;` as a POD local and then call the constructor explicitly by
mangled name further down the body:

    PathPtr path;                         /* shadow has no constructor */
    ...
    _ZN7PathPtrC1Ev(&path);               /* ROM constructs it HERE */

The real header declares PathPtr(), so the same declaration would emit a
second constructor call at the declaration point. Both compile; the bytes
differ. Fixing them means moving the local to where the ROM constructs it,
which also moves its allocation rank, so it wants its own measured pass.

Gates: full COLD rebuild (--no-cache, all 8760 enrolled files), mismatching 0,
106/106 exact, ROM-build analysis PASS. CONVERTED 2812 -> 2816. Eligible set
identical at 11243. No new declaration disagreements. langmode_audit local
struct bodies 1676 -> 1671. port_refcheck 423/423, MSVC port smoke 230/230.

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

tangos-validator Bot commented Sep 16, 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 7 checked; 7 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 7 changed file(s) compile to the ROM byte-for-byte with correct relocation targets.

File Symbol Result Slots checked
src/_ZN14UnchainedChomp13InitResourcesEv.cpp _ZN14UnchainedChomp13InitResourcesEv ✅ verified 1
src/_ZN5Shark8BehaviorEv.cpp _ZN5Shark8BehaviorEv ✅ verified 1
src/_ZN8MantaRay13InitResourcesEv.cpp _ZN8MantaRay13InitResourcesEv ✅ verified 1
src/_ZN8MantaRay8BehaviorEv.cpp _ZN8MantaRay8BehaviorEv ✅ verified 1
src/func_ov019_0211127c.cpp func_ov019_0211127c ✅ verified 1
src/func_ov062_0211c2f4.cpp func_ov062_0211c2f4 ✅ verified 1
src/func_ov092_021313b0.cpp func_ov092_021313b0 ✅ 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

Copy link
Copy Markdown
Collaborator Author

Five-way merge tree gated (main + #2725 + #2726 + #2727 + #2728 + #2729)

Each of these five PRs is green on its own, but this repo has produced a red main from
two individually-green PRs before, so the set was gated as a merged tree rather than as
five branches.

Disjointness. 122 changed files across the five; all 10 pairs share zero files,
and the five branches merge onto 013370480 with no conflict.

Cold rebuild (rombuild.py -j 16 --no-rom --no-cache, all 8,760 enrolled files
compiled from scratch):

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

The rebuild is deliberately cold. A warm run on this same tree reported
8759 reused from cache, 1 compiled — the objects were already built on the individual
branches and the cache is content-keyed, so the compile step exercised nothing. 0/1 compiled is this repo's classic false-green, so the numbers above come from a run with
no cache at all.

tools/tu_manifest_resync.py --check ov023/daObjFm_Battan_c reads in sync on the
merged tree — that is the manifest that shifted earlier in this batch when a shared
header gained declarations.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y4FxdKHG3a6hQRYRAbx29c

@andrewboudreau

Copy link
Copy Markdown
Collaborator Author

Adversarial review — seven-PR set (#2725#2731)

Re-reviewed as a critic rather than as the author. Every check below is new work, not a
restatement of the PR's own gate. Head SHAs reviewed: 2725 4f3927bd9, 2726 81040f19c,
2727 4c08d9a4d, 2728 00b15daef, 2729 fe61a984c, 2730 38aaa986b, 2731 e9b49b072;
merge tree a7f68c8b3 off 013370480.

1. Relocation destinations — the hazard the byte gate cannot see

A relocated word is a wildcard: a bl retargeted to a different but valid symbol still
byte-matches. Every verification run on these PRs so far used build_pin.verify without
the strict tuple, so none of them checked this. Re-ran all of them with
strict=(reloc_audit, name_index, config_relocs, sym_index), which resolves each relocation
and fails on WRONG-DEST:

PR files result
2725 26 26/26 OK
2726 42 42/42 OK
2727 27 + 4 promoted TUs (61 fns) 27/27 + 61/61 OK
2728 10 10/10 OK
2729 7 7/7 OK
2730 13 13/13 OK
2731 header-only n/a

0 WRONG-DEST across 125 files and 61 TU functions. This also closes the cast-receiver
question on #2726/#2728: ((X *)p)->X::M() cast to the wrong class would resolve to a
different symbol, and that is exactly what this check would have caught.

2. Merge tree, cold

mergetest/seven = all seven merged onto 013370480, 0 overlapping file pairs,
rombuild.py -j16 --no-rom --no-cache (the [3/6] line prints no cache line, so the
enrolled stage really did run cold):

reproducing: 11,206   mismatching: 0
source-owned data claims: 26  (reproducing 26, mismatching 0)
module fidelity: 106/106 exact, 100.000000% of compared bytes
ROM-build analysis: PASS

3. Change-kind census

139 files changed in the merge tree, all M — zero adds, deletes or renames. The whole
rename-hazard family (stranded port/ references, phantom file citations, stem collisions,
a stranded near-miss bank) is therefore structurally absent, not merely untriggered.

4. Local-only gates, run on the merge tree (CI runs none of these on a merge tree)

  • port_refcheck.py423/423 references resolve, 0 stale (manifests 249, cmake-symbols 21, hal-links 153).
  • check_decl_agreement.pyno new declaration disagreements; 80 banked ones are now gone.
  • langmode_audit.py --check (baseline from chaos-data) — PASS. local struct body 1676 → 1660.
  • check_decl_return_types.py — output byte-identical to origin/main, so its 3 conflicting
    symbols are pre-existing and not introduced here.

5. The merged decl baseline is not a silent clobber

config/decl-agreement-baseline.json takes edits from several of these branches and git merges
it line-wise, which can silently drop meaning. Regenerated it from scratch on the merge tree and
compared structurally against the merged copy:

merged symbols: 3891   regenerated: 3887
in MERGED but not regenerated (stale/banked, harmless):  4
in REGENERATED but not merged (a real regression the merge hid):  0

0 hidden. Every difference is in the safe direction. (Separately: ~84 stale banked entries
could be pruned — worth its own PR, not worth touching here, since a baseline edit would conflict
across all seven branches.)

6. The MSVC port build — differential, not absolute

main's port build is red and no CI job builds it, so "the port is red" proves nothing about
these PRs on its own. Made it a differential instead.

LNK2001: 106    LNK2019: 30    LNK2005: 22    LNK1120: 9      <- identical in both
distinct failing symbols: 8 vs 8, set difference in both directions: EMPTY
full LNK line sets: IDENTICAL

The other six PRs introduce zero new port link errors. What remains is the pre-existing
Model / ModelBase / ShadowModel Destructor0/Destructor1 C1/C2 collapse, which needs its
own port/hal change and is untouched by this set.

7. Per-PR findings

Verdict

No defect found, and no change requested. Both incomplete migrations are incomplete on
purpose and say so in their titles (#2725 converts 26 of ~920 references; #2727 lands the header
ahead of the bulk of the call-site conversion) — that is slicing, not a gap.

The one thing this review cannot supply is the thing the review gate actually wants: I am the
author of these PRs, so none of the above is independent. All seven still show
reviewDecision: <empty> and need a human to look at them.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y4FxdKHG3a6hQRYRAbx29c

@andrewboudreau
andrewboudreau merged commit c1b266f into main Sep 18, 2026
13 of 15 checks passed
@andrewboudreau
andrewboudreau deleted the cpp/pathptr-header branch September 18, 2026 05:08
andrewboudreau added a commit to lunavyqo/sm64ds-decomp that referenced this pull request Sep 18, 2026
Conflict: src/func_ov019_0211127c.cpp, modify/delete. This branch deletes it
(the class TU absorbs the body); main's c1b266f (tangosdev#2729) retired its TU-local
`struct PathPtr` shadow in favour of `#include "PathPtr.h"`. Taken as a delete
only after confirming the absorbed body already carries main's change and goes
further: include/daPgRcer_c.h includes PathPtr.h and declares `PathPtr mPath`
at 0x364, and the absorbed func_ov019_0211127c calls
`((daPgRcer_c *)c)->mPath.GetNode(node, j)` -- the real header type through a
named member, not a cast onto a local shadow. Nothing to port.

Two gates were red and both were the promotion's own bookkeeping, not the code.

config/decl-agreement-baseline.json: this branch had renamed four bank keys
from `src/_ZN13RacingPenguin*.cpp` to `src/_ZN10daPgRcer_c*.cpp`, paths its own
promotion never creates, stranding them; the other thirty-odd entries were left
keyed to the per-function shards the promotion deletes. Rebuilt as
main's baseline minus what this branch heals plus what it newly banks, measured
by regenerating on a pristine origin/main tree and on this one and diffing:
40 added, every one `src/game/actors/d_a_pg_rcer.cpp`; 81 removed, every one in
a file this branch deletes or edits (68 in the deleted ov019 shards, 12 in
src/__sinit_ov019_021127a4.c where consolidating N declarations into one flipped
the plurality onto the sinit's `void *` / `V3` spellings, 1 in
include/decl_common.h). Main's own 98 stale entries are left untouched -- the
full scan still reports them, as it does on main.

config/tu_manifest.d/ov019/daPgRcer_c.json: 33 conflict notes still read
`tubuild create warning: CONFLICT:` while the promoted source carries zero
TUBUILD CONFLICT markers, so the check paired 33 notes against nothing. The
entry's own reconcile note already claimed they had been marked (RESOLVED) and
only one of the 34 actually was; rewritten to the wording tools/tubuild.py
emits, per check_tubuild_conflicts.py's own instruction. That note also named
src_tu/actors/daPgRcer_c.cpp, a file that does not exist; repointed at the
promoted source.

Verified: rombuild -j16 --no-rom PASS, 804 compiled, 11,206 reproducing /
0 mismatching, module fidelity 106/106 exact at 100.000000%, ROM-build analysis
PASS -- so the shadow-struct retirement added no constructor call.
check_decl_agreement PASS, check_tubuild_conflicts --list PASS,
check_dead_references PASS, check_src_tu PASS, both gates' unit suites OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G1Gdj5fTjMsW2VjRLpXxYA
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