Skip to content

Migrate decomp source to a submodule (Milestone 1)#129

Merged
JRickey merged 6 commits intomainfrom
agent/decomp-submodule
May 4, 2026
Merged

Migrate decomp source to a submodule (Milestone 1)#129
JRickey merged 6 commits intomainfrom
agent/decomp-submodule

Conversation

@JRickey
Copy link
Copy Markdown
Owner

@JRickey JRickey commented May 4, 2026

Summary

  • Move the vendored SSB64 decomp source out of the in-tree src/ and include/ directories into a new submodule at decomp/ (fork: JRickey/ssb-decomp-re, branch port-patches, base: upstream 977cf4780).
  • Retarget CMake to compile from decomp/src/ and decomp/include/. Vendored copies (1031 files) deleted; only include/reloc_data.h (port-generated) remains.
  • Trim the submodule's port-patches branch to only the files the port actually consumes (drops 342 files / ~275k lines of upstream matching infra, splat configs, debug subsystems, and asset-extraction scripts).
  • Add three new tools under tools/ for future upstream sync + semantic-equivalence auditing.

What this unlocks

This is Milestone 1 of the broader refactor described in .claude/plans/piped-squishing-popcorn.md. The next milestone (compile-from-source for decomp/src/relocData/) is what enables modding and custom characters; that work consumes upstream's typed reloc data via this submodule rather than ROM-extracting blobs through Torch.

Provable semantic equivalence

The build wasn't enough validation — header-include order changes, macro redefinitions, or hunk-wrap producing different-but-valid C could all silently change behaviour. So I ran the per-TU __text section comparison against main's build:

  • 446 / 475 TUs byte-identical in their compiled instruction stream
  • 29 differ only in mov w2, #<imm> immediates — the __LINE__ constants baked into PORT_RESOLVEportRelocResolvePointerDebug(token, FILE, LINE). Verified by tools/audit_text_diffs.sh.
  • 0 structural divergence

The two scripts that produce this evidence (tools/compare_obj_text.sh, tools/audit_text_diffs.sh) are committed alongside the migration so future reconciles can re-prove the same thing.

Files changed at a glance

  • + decomp/ submodule (fork on port-patches)
  • + tools/{upstream_reconcile,compare_obj_text,audit_text_diffs}.{py,sh}
  • ~ CMakeLists.txtsrc/ GLOBs → decomp/src/, include paths split between port-tree (include/reloc_data.h) and submodule (decomp/include/)
  • ~ scripts/new-worktree.sh — third submodule + credits-encoder cwd
  • ~ scripts/package-windows.ps1 — credits-encoder cwd
  • ~ tools/generate_reloc_stubs.pySRC_DIR prefers decomp/src/
  • ~ CLAUDE.md, docs/architecture.md, docs/build_and_tooling.md — reflect new layout
  • src/ (1014 files), include/ (16 files), tools/relocData.py

Test plan

  • Clean rebuild from a fresh main-tree checkout: cmake --build build --target ssb64 -j 4 → 243/243 steps green, 20MB BattleShip executable
  • Per-TU __text audit vs main baseline: 446 identical / 29 LINE_ONLY / 0 STRUCTURAL
  • Runtime smoke: BattleShip launches, loads BattleShip.o2r + f3d.o2r + ssb64.o2r, parses all audio assets, no crashes during eval window
  • git submodule update --init --recursive from a fresh clone resolves all three submodules cleanly
  • Windows release script (untested locally — relevant change is only the credits-encoder cwd)

Heads-up for in-flight branches

The 7 currently-open agent/* branches (android-port, crash-investigation, defensive-debug, issue-103-heap-token-fixes, issue-58-locate-noexcept, issue-77-mk-crash, save-editor-test) all forked off main before this refactor and edit src/foo.c / include/foo.h directly. After this PR lands, each will need either:

  • Merging to main first (preferred — simpler), or
  • Rebasing afterward, with src/include changes re-targeted at decomp/src/ and committed inside the submodule on port-patches (then bumping the outer pointer)

🤖 Generated with Claude Code

JRickey added 6 commits May 3, 2026 19:14
Migrates the SSB64 decomp source out of the in-tree src/ and include/
directories into a real git submodule at decomp/, on the local
port-patches branch carrying our #ifdef PORT-guarded modifications.

Submodule:
- Path: decomp/
- URL:  git@github.com:JRickey/ssb-decomp-re.git (JRickey fork of upstream)
- Branch: port-patches (contains port modifications wrapped in #ifdef PORT)
- Pinned SHA: f74646d59 (one commit ahead of upstream main 977cf4780)
- Submodule's .git/config remote.origin.url points at fork; remote
  upstream points at VetriTheRetri/ssb-decomp-re for tracking.

CMakeLists.txt changes:
- src/<module>/ → decomp/src/<module>/ for all 14 game-code GLOBs
- src/libultra/{gu,n_audio,audio}/ → decomp/src/libultra/...
- src/credits/ encoder working dir + generated outputs → decomp/src/credits/
- ssb64_game include path now: include/ + decomp/include/ + decomp/src/ + port/
- Executable include path: decomp/src/ + port/ ONLY — decomp/include/ is
  intentionally excluded so port/*.cpp don't pick up the C decomp shim
  headers (stdlib.h, stddef.h) over libc++. Only the C decomp library
  consumes the shim headers.

Hand fixes (decomp/ submodule, in port-patches branch):
- src/it/item.h: rewrote macro definitions to wrap each #define in
  #ifdef PORT/#else/#endif rather than wrapping inside the macro body.
  upstream_reconcile.py's hunk-wrap broke on macros where a continuation
  line (\) was followed by a preprocessor directive.
- src/sc/sc1pmode/sc1pstageclear.c: changed
  #include "../../../port/bridge/framebuffer_capture.h" to
  #include <bridge/framebuffer_capture.h> — the relative path stopped
  working when the file moved into decomp/.

scripts/new-worktree.sh:
- Added decomp to the per-submodule clone loop, with a guard that skips
  submodules not yet configured in the main tree (so this branch can
  spawn worktrees from main even before the submodule lands).

Verified: ssb64_game compiles (180 objects), BattleShip executable
links (20 MB), no errors.

Vendored src/ and include/ are still in the worktree and will be
deleted in a follow-up commit (Phase 4), once Phase 3 (reloc tooling
migration) lands.
Phase 3 + Phase 4 of the decomp submodule migration: the port now consumes
all decomp source from the decomp/ submodule. Vendored copies are removed.

Phase 3 (reloc tooling):
- tools/generate_reloc_stubs.py: SRC_DIR now scans decomp/src/ when present
  (falls back to legacy src/ during the migration window). The vendored
  reloc_data_symbols.us.txt stays — it uses a port-curated symbol-table
  format incompatible with upstream's decomp/tools/relocFileDescriptions.us.txt
  (which encodes the same data as -NNN: Name + bracketed [N] sections).
  Migrating the format is a follow-up; the build doesn't depend on it.
- tools/relocData.py: deleted. The vendored copy was unused (no port script
  imports it) and was an older snapshot of upstream's tools/relocData.py
  which has since been refactored for JP/US.

Phase 4 (delete vendored src/ + include/):
- src/: 1014 files removed. All decomp game code now sourced from
  decomp/src/ via the CMake glob retarget in the previous commit.
- include/: 17 files removed. include/reloc_data.h is kept — it's a
  port-side auto-generated header (regenerated by generate_reloc_stubs.py
  on each build), and the build's include path resolves it from
  ${CMAKE_CURRENT_SOURCE_DIR}/include before falling through to
  decomp/include/ for the rest of the C decomp shim headers.

Net diff: 1034 files changed, +1773 / -301108. That delta is the size of
the vendored decomp source — its replacement (the submodule pointer) takes
two lines in .gitmodules.

Verified end-to-end:
- Clean rebuild: 511/511 build steps green, 20MB BattleShip executable
- Codegen idempotency: generate_reloc_stubs.py + generate_yamls.py +
  generate_reloc_table.py all produce expected outputs
- Runtime smoke: BattleShip launches, loads BattleShip.o2r + f3d.o2r +
  ssb64.o2r, parses all audio assets (sounds1, sounds2, music, FGM),
  initialises controllers, no crashes during 3-second eval window.

Milestone 1 of the decomp submodule refactor is complete. Milestone 2
(compile-from-source for relocData) starts next.
Pulls in the cleanup commit on JRickey/ssb-decomp-re port-patches
(0583cc8df) which drops 342 files (~275k lines) of upstream matching
infrastructure, splat configs, debug subsystems, and asset-extraction
scripts the port doesn't use. Submodule SHA goes from f74646d5 to
0583cc8d.

Net effect on a fresh clone:
- decomp/ shrinks from ~150 MB to ~70 MB (most of which is
  src/relocData/ kept for Milestone 2/3 compile-from-source work)
- decomp/ no longer has its own .gitmodules → no nested-submodule
  init step required when bootstrapping

Verified: full clean rebuild green from main tree, 243/243 steps,
20MB BattleShip executable.
…teardown

7a0da1e74 on JRickey/ssb-decomp-re port-patches re-applies two
hand-fixes from Milestone 1 that never made it into a submodule commit
(they were uncommitted in the agent/decomp-submodule worktree which got
torn down):
- src/it/item.h: PORT-wrap moved from inside macro bodies to whole-#define
  blocks so the preprocessor doesn't choke on directives in continuation
  lines.
- src/sc/sc1pmode/sc1pstageclear.c: relative include changed to angle
  bracket form so it resolves from decomp/src/.

Without these, fresh clones of the outer branch + submodule init would
hit the original item.h preprocessor errors and a missing-header error
in sc1pstageclear.c. Now self-contained.
Two real breaks and three doc updates that the M1 commits skipped:

scripts/new-worktree.sh: line 133 still cd'd into $WT_DIR/src/credits
which doesn't exist after Phase 4 (the credits .txt sources moved into
decomp/src/credits/). A fresh worktree would fail at the credits-encode
step. Update to decomp/src/credits/. Also update the usage-comment
mentioning src/ as an editable area.

scripts/package-windows.ps1: same break — Push-Location on src/credits
during the credits-encode step before the cmake build. Windows release
script would fail. Update to decomp/src/credits/.

docs/architecture.md: the source-organization tree was the pre-refactor
layout. Replace with the decomp/{include,src,tools} tree and the new
port-target structure (port/, include/reloc_data.h, libultraship/, torch/,
decomp/). Update the "What NOT to Include in Port" section to describe
what was removed from port-patches (db/.c, ovl8/, audio/, particles/,
libultra/{io,libc,os,sp,vimodes}/) and what was kept.

docs/build_and_tooling.md: the LP64 audit grep example pointed at the
old in-tree include/ src/ paths. Now decomp/include/ decomp/src/ port/.

CLAUDE.md: refresh the worktree-workflow section to reference all three
submodules (decomp, libultraship, torch) plus the per-submodule push
target (decomp → port-patches on JRickey/ssb-decomp-re).
These were one-off scripts I built during Milestone 1 (decomp submodule
migration) and lived in a gitignored .claude/ scratch dir. Promoting them
to tools/ because each handles a workflow we'll need again whenever
upstream advances or someone refactors decomp consumption:

tools/upstream_reconcile.py
  Mechanically PORT-guards port modifications against an upstream
  baseline. For each modified file in a git checkout: revert mode-only
  and whitespace-only diffs, drop auto-generated artifacts (e.g.
  include/reloc_data.h, src/credits/*.encoded), and wrap each
  substantive diff hunk in #ifdef PORT/#else/#endif via difflib's
  SequenceMatcher. Files that already contain ANY #if(def)? PORT marker
  are left alone (assumed hand-curated).
  Caveats: hunks landing inside macro continuation lines (`\` followed
  by a preprocessor directive) produce invalid C. Run
  tools/compare_obj_text.sh + tools/audit_text_diffs.sh after every
  reconcile to catch any structural breakage; we hit this once with
  src/it/item.h during M1 and had to hand-fix it.

tools/compare_obj_text.sh
  Compares per-TU __TEXT,__text section bytes between two
  ssb64_game builds (e.g. main branch vs a refactor branch). Hashes
  each .o's text section after stripping the file-path header line
  and address column from `otool -t`. Flags any TU whose compiled
  instruction stream has changed.

tools/audit_text_diffs.sh
  Classifies the diffs flagged by compare_obj_text.sh:
  - LINE_ONLY  : every changed line is `mov w2, #<imm>` — debug
    line-number drift via __LINE__ baked into PORT_RESOLVE call
    sites (portRelocResolvePointerDebug(token, FILE, LINE)).
    Semantics-preserving and expected.
  - STRUCTURAL : at least one diff is something other than a w2
    immediate move. Real instruction-level change. Investigate.

Together these proved Milestone 1 was provably semantics-preserving:
446/475 TUs byte-identical, 29 LINE_ONLY, 0 STRUCTURAL.

All three are AArch64/macOS-tested. compare_obj_text.sh and
audit_text_diffs.sh use otool, so Linux users would need to swap the
otool calls for `objdump --section=.text -d`. The LINE_ONLY pattern
in audit_text_diffs.sh is AArch64-specific (mov w2, #imm); on x86
it'd be `mov esi, <imm>` or similar.
@JRickey JRickey merged commit 0254113 into main May 4, 2026
@JRickey JRickey deleted the agent/decomp-submodule branch May 4, 2026 00:16
JRickey added a commit that referenced this pull request May 4, 2026
Bump decomp submodule to JRickey/ssb-decomp-re#agent/msvc-build-fixes
(commit 62ba3fd23) which contains two pre-existing MSVC build breaks
that have been blocking Windows RelWithDebInfo builds since the
src/ → decomp/ submodule migration (#129) landed:

(1) decomp/include/stdarg.h re-`#define`s `va_list __builtin_va_list`
    on MSVC, where that intrinsic doesn't exist (C2061). Fixed by
    guarding the GCC-builtin branch with `#ifndef _MSC_VER`; MSVC
    falls through the existing `#ifdef _MSC_VER` block at top via
    <vadefs.h> + __crt_va_*.

(2) decomp/src/mn/mnmaps/mnmaps.c (line 1073) puts `#ifdef PORT`
    directives inside the `lbRelocGetFileData(...)` macro argument
    list. C standard says `#` in function-like macro args is
    undefined behavior; clang/gcc accept leniently, MSVC rejects
    with C2059. Fixed by hoisting the conditional values out into
    local variables before the macro call. A tree-wide scan turned
    up 30 candidate sites in 11 files but 29 of them sit inside
    regular function calls (accepted), so this was the only one
    that needed restructuring.

Verified: clean RelWithDebInfo Windows build (MSVC 19.44.35225)
succeeds end-to-end and produces BattleShip.exe.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JRickey added a commit that referenced this pull request May 4, 2026
…omp submodule migration

The Linux + macOS package scripts still cd into $ROOT/src/credits, which
no longer exists after PR #129 moved decomp source into the decomp/
submodule. Both jobs of the v0.7.4-beta Release workflow failed at the
"Encoding credits text" step before cmake even started. The Windows
.ps1 script was already updated; the two shell scripts were missed.
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