Files
BFM-decomp/config/splat.ov_SC01_005.yaml
T
Drew T cb948a6bbc feat(decomp): the ov_SC01 reloc-only cluster + its 5th latent victim — 5 fns, 1,301 ins
S74 handed this forward as "1,116 instructions behind one question": family_remap
on ov_SC01_004/005/006/008 gated DIFF 4/4 against the banked exemplar
ov_SC01_009:func_8017EB08, and the class had been carried as a codegen wall since
S70. The four bodies were byte-identical to the exemplar the entire time.

Word-level classification vs the exemplar, computed independently twice (a Fable
agent's script, then mine from scratch against the retail images), identical:

    nins=279   EQ 213 · RELOC-HI16 23 · RELOC-LO16 24 · INTERNAL-J 19 · CODEGEN 0

Zero register-allocation, instruction-selection or scheduling differences.

ROOT CAUSE — tools/jtbl_carve.py reserved ONE WORD TOO MANY per table:
  * spimdisasm runs an island's LAST `jtbl_` dlabel one word into the following
    NON-ZERO data (string bytes 0x696F760A / 0x000013FF / 0x62647020), so the
    zero-word trim cannot see it; and
  * the over-span clamp that would have caught it was guarded by
    `len(sltiu_bounds) == 1` -- but `sltiu` is ALSO how gcc emits an unsigned
    range check ((u32)(x-lo) < n, I1). These four carry five distinct sltiu
    immediates, so the guard silently disabled itself on precisely the functions
    that needed it.
  0x2C reserved for a 0x28 table => image 4 bytes short => ~850 %lo immediates
  shift => whole-binary DIFF about a function whose own bytes are perfect.
Fixed with a PER-TABLE bound: gcc-2.7.2's dispatch is a fixed idiom, so the
`sltiu` nearest ABOVE that table's own %hi(jtbl_X) is unambiguous whatever else
the function tests. Second defect stacked behind it: a carve span whose
JTBL_PADS line lacks a `tables=` comment lost its existing table's start on
merge and refused "table starts do not fit the span" -- which harvest_verify
then "repaired" with a needless jr_isolate_all that walked back into the first.

THE NEGATIVE CONTROL IS THE STORY. Run over every other open table-bearing stub
fleet-wide, the fixed bound changed exactly one more table: ov_SC06_022/
func_80185B80 (185 ins), a FIFTH victim nobody had drafted against. A guard that
disables itself on a common idiom does not fail once -- it fails quietly across
the whole corpus.

Banked, each with its own byte-gate verdict (--no-propagate, clean re-gate):
  func_8017EB30  ov_SC01_004  279
  func_8017F2D4  ov_SC01_005  279
  func_8017F2D4  ov_SC01_006  279
  func_8017EC68  ov_SC01_008  279
  func_80185B80  ov_SC06_022  185

Also here:
  * dedup_propagate: memoize find_site's mask (lru_cache) -- 54 ms of masking
    per call over the whole source, recomputed though it depends only on the
    text. 2x on that loop (58.3 -> 33.0 ms/call), NC identical on 120 addrs.
    Scoped honestly: that loop is ~2.4 min of a 30-min run; the profiler puts
    43% in family_remap._alias_decl_for, which is NOT fixed here.
  * Makefile: `clean` says out loud that BINARY= is ignored and it is fleet-wide
    (cookbook §445) -- it silently deleted asm/ for all 213 binaries this session.
  * Cookbook §446 (the carve law: when a standalone-MATCH jtbl draft gates DIFF,
    diff the carve extent against 4 x sltiu before touching the body), §445, and
    SETUP rows for both tools (R21).
  * CURRENT_PHASE: the S75 log, incl. the measured fleet dedup-hygiene census
    (~2,073 fns / ~12,116 items, all ALREADY MATCHED -- cleanup, not work) and
    Drew's decision to leave it and gate --no-propagate from here.
2026-09-02 22:15:20 -06:00

178 lines
10 KiB
YAML

# splat config TEMPLATE — Brave Fencer Musashi location overlays (SCxx.CD/FILE_nnn/0.4.dec)
# =============================================================================
# Phase 13: the reusable flat-blob splat config for a LOCATION OVERLAY. ~134 overlays
# all stream into the SAME RAM slot (vram 0x80128158) and chain into the resident engine
# (0x800CEDF8) + the EXE. This is a TEMPLATE: the @TOKEN@ placeholders are substituted to
# produce config/splat.<alias>.yaml — by tools/new_overlay.sh (Phase 13 T4), or by hand
# (Phase 13 T1a for the first overlay). It is NOT a valid splat config until instantiated.
#
# Placeholders:
# ov_SC01_005 binary alias, ov_<SCxx>_<nnn> e.g. ov_SC01_077
# extracted/retail/SC01.CD.dir/FILE_005.dir/0.4.dec extracted 0.4.dec payload path e.g. extracted/retail/SC01.CD.dir/FILE_077.dir/0.4.dec
# 56760dbec7757f12d18d7e40242a0b05280ff444 sha1 of the 0.4.dec (the build-identity target; also -> config/check.<alias>.sha)
# 0x80128158 load vram (the shared overlay slot) e.g. 0x80128158 (constant across overlays today;
# parameterized so a future non-0x80128158 overlay needs no template edit)
# 0x5A20C file offset where code ends -> data tail begins (iterated vs the byte check, T1b)
# 0xA5F09 file size in bytes (the EOF marker; = the 0.4.dec length)
#
# Modeled on config/splat.resident.yaml (the Phase-10 flat-blob recipe), with overlays'
# differences: (1) code at file offset 0x0 (overlays open with a function prologue, NOT a
# leading data word like the resident — so NO leading-rodata trick by default; see the
# commented `head` slot for the rare pre-code-data overlay); (2) symbol_addrs_path ALSO
# stacks config/symbols.resident.txt because overlays reference resident engine globals;
# (3) vram is the overlay slot. Everything else matches the resident options verbatim.
#
# Byte-match is robust to the exact text/data split (splat round-trips the bytes — the
# resident precedent); iterate 0x5A20C against `make check BINARY=ov_SC01_005` (Phase-5/10
# precedent). LZSS is NOT in the verify path — we match the DECOMPRESSED 0.4.dec (repack is Gen3).
name: ov_SC01_005
sha1: 56760dbec7757f12d18d7e40242a0b05280ff444
options:
basename: ov_SC01_005
target_path: extracted/retail/SC01.CD.dir/FILE_005.dir/0.4.dec
elf_path: build/ov_SC01_005/ov_SC01_005.elf
# base_path is resolved relative to THIS yaml's dir (config/), so `..` = repo root
base_path: ..
platform: psx
compiler: PSYQ
asm_path: asm/ov_SC01_005
src_path: src/ov_SC01_005
# asset_path scoped per-alias so a `bin` subseg (the trailing-partial-word carve, below) lands at
# assets/ov_SC01_005/*.bin and never collides with another overlay's same-named asset. The Makefile's
# build/assets/%.o rule incbin's it into a linkable object.
asset_path: assets/ov_SC01_005
# build_path = build (NOT build/<alias>): splat writes the .ld's object paths under
# $(build_path), and the Makefile's pattern rules build them at build/asm/** + build/src/**.
# Only the elf/ld/output live in build/<alias>/.
build_path: build
ld_script_path: build/ov_SC01_005/ov_SC01_005.ld
ld_dependencies: True
# Per-binary undefined-symbol outputs (under build/<alias>/), so `make clean`'s `rm -rf build`
# covers them and binaries never share a stale file.
undefined_syms_auto_path: build/ov_SC01_005/undefined_syms_auto.txt
undefined_funcs_auto_path: build/ov_SC01_005/undefined_funcs_auto.txt
find_file_boundaries: False
# NO gp_value: overlays are -G0 (verify zero ($gp)/%gp_rel in the disasm). Omitting it -> no _gp.
o_as_suffix: True
use_legacy_include_asm: False
section_order: [".rodata", ".text", ".data", ".bss"]
# Stacked symbols: shared engine globals (symbols.us.txt) + resident engine globals the overlay
# calls (symbols.resident.txt) + the overlay-local names/boundaries (symbols.<alias>.txt, seeded
# T1c from sig_image + duplicates.cross.md; R13/R15-tagged, overlay-region only).
symbol_addrs_path:
- config/symbols.us.txt
- config/symbols.resident.txt
- config/symbols.ov_SC01_005.txt
subalign: 2
string_encoding: ASCII
data_string_encoding: ASCII
rodata_string_guesser_level: 2
data_string_guesser_level: 2
# Enable if maspsx reorders INCLUDE_ASM output in c-mode (unused for the EXE/resident):
# include_asm_macro_style: maspsx_hack
segments:
- name: ov_SC01_005
type: code
start: 0x0
vram: 0x80128158
# align 4 (not the code default 16): MIPS is word-aligned; mirrors the EXE/resident choice.
align: 4
subsegments:
# OPTIONAL leading pre-code data/rodata island (rare): if an overlay begins with a data
# word BEFORE its first function (the resident's case), uncomment a `head` subseg here and
# shift the code start. Mark it `rodata` (section_order places .rodata first) so it lands at
# 0x80128158 ahead of the code, a 1-word analogue of main's rodata-island (no ld_interleave).
# - [0x0, rodata, head]
- [0x0, c, ov_SC01_005]
- [0x2b88, c, ov_SC01_005_jr_8012ACE0]
- [0xd730, c, ov_SC01_005_jr_80135888]
- [0xd8f4, c, ov_SC01_005_jr_80135A4C]
- [0xdbc8, c, ov_SC01_005_jr_80135D20]
- [0xff88, c, ov_SC01_005_jr_801380E0]
- [0x13410, c, ov_SC01_005_o0c]
- [0x14834, c, ov_SC01_005_jr_8013C98C]
- [0x171f8, c, ov_SC01_005_jr_8013F350]
- [0x17e80, c, ov_SC01_005_jr_8013FFD8]
- [0x184b0, c, ov_SC01_005_jr_80140608]
- [0x1CA44, c, ov_SC01_005_o0b] # -O0 whale func_80144B9C (0x1CA44..0x1D64C)
- [0x1d64c, c, ov_SC01_005_after]
- [0x2c2f4, c, ov_SC01_005_jr_8015444C]
- [0x2cacc, c, ov_SC01_005_jr_80154C24]
- [0x30774, c, ov_SC01_005_jr_801588CC]
- [0x31b2c, c, ov_SC01_005_jr_80159C84]
- [0x32270, c, ov_SC01_005_jr_8015A3C8]
- [0x32cd4, c, ov_SC01_005_jr_8015AE2C]
- [0x341d4, c, ov_SC01_005_jr_8015C32C]
- [0x42a14, c, ov_SC01_005_jr_8016AB6C]
- [0x499f4, c, ov_SC01_005_jr_80171B4C]
- [0x4b364, c, ov_SC01_005_jr_801734BC]
- [0x50854, c, ov_SC01_005_jr_801789AC]
- [0x50be8, c, ov_SC01_005_jr_80178D40]
- [0x52354, c, ov_SC01_005_jr_8017A4AC]
- [0x52cd4, c, ov_SC01_005_jr_8017AE2C]
- [0x541e8, c, ov_SC01_005_jr_8017C340]
- [0x569ec, c, ov_SC01_005_jr_8017EB44]
- [0x56c04, c, ov_SC01_005_jr_8017ED5C]
- [0x5a20c, data, tail]
- [0xa2d00, .rodata, ov_SC01_005] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa2ddc, .rodata, ov_SC01_005_jr_8012ACE0] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa2ed0, data, tail2]
- [0xa2ed4, .rodata, ov_SC01_005_jr_80135888] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa2ee8, data, tail3]
- [0xa2eec, .rodata, ov_SC01_005_jr_80135A4C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa2f00, data, tail4]
- [0xa2f04, .rodata, ov_SC01_005_jr_80135D20] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa2f48, data, tail5]
- [0xa2f54, .rodata, ov_SC01_005_jr_801380E0] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa2fb8, .rodata, ov_SC01_005_o0c] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa313c, data, tail6]
- [0xa35c4, .rodata, ov_SC01_005_jr_8013F350] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa35f8, data, tail7]
- [0xa35fc, .rodata, ov_SC01_005_jr_8013FFD8] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3668, data, tail8]
- [0xa366c, .rodata, ov_SC01_005_jr_80140608] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3680, data, tail9]
- [0xa3698, .rodata, ov_SC01_005_jr_8015444C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3774, .rodata, ov_SC01_005_jr_80154C24] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa37b8, .rodata, ov_SC01_005_jr_801588CC] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3860, .rodata, ov_SC01_005_jr_80159C84] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3894, data, tail10]
- [0xa3898, .rodata, ov_SC01_005_jr_8015A3C8] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa38b4, data, tail11]
- [0xa38b8, .rodata, ov_SC01_005_jr_8015AE2C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa38f4, data, tail12]
- [0xa38f8, .rodata, ov_SC01_005_jr_8015C32C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3914, data, tail13]
- [0xa392c, .rodata, ov_SC01_005_jr_8016AB6C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa396c, data, tail14]
- [0xa39ac, .rodata, ov_SC01_005_jr_80171B4C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa39cc, .rodata, ov_SC01_005_jr_801734BC] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa39e0, data, tail15]
- [0xa3b70, .rodata, ov_SC01_005_jr_801789AC] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3b88, .rodata, ov_SC01_005_jr_80178D40] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3d8c, data, tail16]
- [0xa3d90, .rodata, ov_SC01_005_jr_8017A4AC] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3e1c, data, tail17]
- [0xa3e20, .rodata, ov_SC01_005_jr_8017AE2C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3e34, .rodata, ov_SC01_005_jr_8017C340] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa3e54, data, tail18]
- [0xa435c, .rodata, ov_SC01_005_jr_8017EB44] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa4384, .rodata, ov_SC01_005_jr_8017ED5C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa43d4, data, tail19]
- [0xA5F08, bin, trailing] # final 1 bytes (EOF not 4-aligned; spimdisasm drops a partial word)
- [0xA5F09] # EOF marker = the 0.4.dec byte length
# @TRAILING@ (above) is replaced by tools/new_overlay.sh: for a non-4-aligned overlay it becomes
# ` - [0x<word_floor>, bin, trailing]` (the final 1-3 bytes spimdisasm's data path drops);
# for a 4-aligned overlay the marker line is removed. The Makefile build/assets/%.o rule + the
# objcopy end-align TRIM finish the job. Hand-instantiated configs (ov_SC01_077) inline the carve.