Skip to content

tu: promote ov100/daObjPathLift_c to one translation unit - #1979

Merged
andrewboudreau merged 1 commit into
mainfrom
tu/pathlift-promote-v2
Aug 30, 2026
Merged

tu: promote ov100/daObjPathLift_c to one translation unit#1979
andrewboudreau merged 1 commit into
mainfrom
tu/pathlift-promote-v2

Conversation

@andrewboudreau

@andrewboudreau andrewboudreau commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

The second half of #1878, now that #1977 has landed the tool half. Eight one-function
sources and eight delinks.txt entries become one .cpp and one .text span — the shape
the original translation unit had.

-src/_ZN15daObjPathLift_cD1Ev.cpp:              +src/actors/daObjPathLift_c.cpp:
-    .text start:0x02146d7c end:0x02146dec      +    complete
-src/_ZN15daObjPathLift_cD0Ev.cpp:              +    .text start:0x02146d7c end:0x02147328
-src/func_ov100_02146e70.c:
-src/func_ov100_0214700c.c:
-src/_ZN15daObjPathLift_c16CleanupResourcesEv.cpp:
-src/_ZN15daObjPathLift_c6RenderEv.cpp:
-src/_ZN15daObjPathLift_c8BehaviorEv.cpp:
-src/_ZN15daObjPathLift_c13InitResourcesEv.cpp:

src_tu/actors/daObjPathLift_c.cpp moves to src/actors/ and is enrolled, so the ROM
build now compiles all eight functions from one object and byte-compares the whole
0x5AC-byte span against the cartridge. mwcc emits one .text section per function in the
reverse of source order, so the file is written highest-address-first; the D1/D0 pair's
relative order is the compiler's own.

Why this needed #1977

Combining the objects makes mwcc emit an extra vague-linkage _ZN7Vector3D1Ev into the
merged object — include/types.h gives Vector3 a deliberately empty inline destructor,
so every TU that destroys one re-emits the 4-byte body. The cartridge keeps a single copy
at arm9:0x020072c0 that src/_ZN7Vector3D1Ev.cpp owns. The manifest licenses this
object's copy with the deadstrip-duplicate disposition from #1977, which reads the
cartridge's own bytes at that address and refuses unless this copy is byte-identical
STB_LOPROC output.

Coverage cost, stated plainly

This file is still a shadow TU. Its D0/D1 are free functions carrying @symbol
markers that stamp the base vptr directly (*(void***)p = (void**)_ZTV10dBgActor_c;), not
a real class daObjPathLift_c. So this object emits no _ZTV/_ZTI/_ZTS.

The two per-function destructor files it replaces did emit them, and romdata_check
compared 128 of 128 _ZTV15daObjPathLift_c bytes against the cartridge. After this
commit those three data symbols leave the checked set entirely:

symbols present in base but GONE after: 3
   ov100 _ZTI15daObjPathLift_c  (0x02148538)  PARTIAL   emitted 12,  romExtent 12
   ov100 _ZTS15daObjPathLift_c  (0x02148544)  PARTIAL   emitted 18,  romExtent 20
   ov100 _ZTV15daObjPathLift_c  (0x0214857c)  PARTIAL   emitted 128, romExtent 132
symbols whose verdict CHANGED: 0

Measured by diffing rombuild --data-json per symbol, not the summary line — that counts
one record per data symbol per object, so it falls on consolidation for reasons that
have nothing to do with verification. No symbol changed verdict; three stopped being
checked. romdata_check is the only gate that looks at vtables and 106/106 is blind to
them, so no check in this pipeline reports this. It is called out here because it
would otherwise land silently.

This matches how the one already-promoted TU on this tree (ActorBase_SceneNode) behaves —
it emits no ROM-data records either.

Follow-up, already measured. The class here is real — InitResources, Behavior,
Render and CleanupResources are all genuine daObjPathLift_c:: methods. Only the
destructor is shadowed, and restoring it to the real out-of-line
daObjPathLift_c::~daObjPathLift_c() {} that src/_ZN15daObjPathLift_cD1Ev.cpp already
carries on main makes both variants byte-match under 2004/b56 (build_pin: D1
0x02146d7c/0x70, D0 0x02146dec/0x84) and brings _ZTV/_ZTI/_ZTS straight back. The
generated file's claim that this form "measured 999 differing words" is stale — it
predates include/PathLift.h getting its inline ~PathLift() {}.

It cannot ship in this PR because it needs a tools change first, and the validator
restores all of tools/ from base. With a real destructor the TU also emits
_ZTV8PathLift, _ZN8PathLiftD1Ev/D0Ev and the whole ancestor RTTI chain as vague
linkage. objisolate.derive_deadstrip's multi-symbol path refuses any non-.text
content, and deadstrip_plan accepts STT_FUNC only — so the data half cannot be
licensed. Licensing just the three extra functions fails closed on the entanglement:

isolate: compiler-only deadstrip refused: surviving section[44] .data
references compiler-only _ZN8PathLiftD0Ev at 0x4c

_ZTV8PathLift holds a relocation to the D0 body, so the vtable and the function have to
be licensable together. That is one more tools-then-manifest split, the same shape as
#1977 → this PR.

Test plan

  • python tools/rombuild.py -j 16106/106 exact, 100.000000% of compared bytes;
    11,082 source-built functions, 11,082 reproducing, 0 mismatching. Enrolled sources
    11,081 → 11,074 (−7: eight files collapse into one).
  • rombuild --data-json diffed per symbol against the base — 0 verdict changes, the
    3 losses above.
  • python tools/port_refcheck.py → 405 checked, all references resolve
  • python tools/prepush_attribution.py --base origin/main --head HEAD → 0 changed, 0 lost

Unblocks the rest of the stack: #1880#1882#1884#1914 all restack behind this.

Eight one-function sources and eight delinks entries become one .cpp and one
.text span (0x02146d7c-0x02147328), the shape the original TU had. mwcc emits
one .text section per function in reverse source order, so the reconstructed
file is written highest-address-first; the destructor pair's D1/D0 order is the
compiler's own.

Merging the objects makes mwcc emit an extra vague-linkage _ZN7Vector3D1Ev into
the combined object -- types.h gives Vector3 a deliberately empty inline
destructor, so every TU that destroys one re-emits it. The cartridge keeps a
single copy at arm9:0x020072c0 that src/_ZN7Vector3D1Ev.cpp owns, so the
manifest licenses this object's copy with the deadstrip-duplicate disposition
added in #1977: rombuild reads the cartridge's bytes at that address and
objisolate refuses unless this copy is byte-identical STB_LOPROC output.

Known coverage cost, stated plainly: the file is still a SHADOW TU. Its D0/D1
are free functions carrying @symbol markers that stamp the base vptr directly,
not a real `class daObjPathLift_c`, so this object emits no _ZTV/_ZTI/_ZTS. The
two per-function destructor files it replaces did emit them, and romdata_check
compared 128 of 128 _ZTV15daObjPathLift_c bytes against the cartridge. After
this commit those three data symbols leave the checked set entirely -- verified
by diffing `rombuild --data-json` per symbol, not the summary, which counts
records per object and falls on consolidation for unrelated reasons. No symbol
changed verdict; three stopped being checked. 106/106 is blind to vtables, so
no gate reports this. Restoring it means giving the TU a real class whose
out-of-line destructor is the key function; that is the next step, not this one.

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

tangos-validator Bot commented Aug 30, 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,044 / 11,347 (97.33%, -7)
Byte-verified code bytes 2,048,208 / 2,211,124 (92.63%, -1,340)
Claimed, not byte-verified 168 functions, 55,068 bytes (+7)
Perfect source moves 0 R100
Enrolled ranges (delinks complete) 11,053 functions, 2,050,484 bytes (92.73%, +0) -- differs from byte-verified by +9
Contributor credit 0 added, 0 changed, 0 lost
Relocation check 0 checked; no affected slots
Port reference check 405 checked; 0 stale
Module fidelity 106/106 exact; 100.000000% compared bytes
Code linked from verified source 11,082 functions, 2,064,484 bytes (93.37%)
Module bytes from source 2,064,484 / 3,049,600 (67.7%); 811,492 (26.6%) are data no delink entry reaches
ROM data reproduced from source 446 symbol(s) exact, 239 partial, 15 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 168 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,212 this project calls matched.

Warnings: 8 address range(s) left the byte-verified set while enrolled totals held steady: ov100:0x02146d7c-0x02146dec, ov100:0x02146dec-0x02146e70, ov100:0x02146e70-0x0214700c, ov100:0x0214700c-0x02147054, ov100:0x02147054-0x021470a4, +3 more; 7 more function(s) now claim a match that nothing compiles; enroll them in a delinks.txt to have the ROM build check them.

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 merged commit e8b557e into main Aug 30, 2026
6 checks passed
@andrewboudreau
andrewboudreau deleted the tu/pathlift-promote-v2 branch August 30, 2026 04:04
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