Fixes the wrong 'do not edit symbol_addrs*.txt' rule caught during
the D_80070F84 → gSpeciesBaseStats rename. Adds a new section
explaining that symbol_addrs.txt and undefined_syms.ld contain full
symbol declarations (not just address pins), the rename must
propagate to them, and which linker scripts are NOT declarations.
Updates:
- Out-of-scope section: removes the wrong rule, points to the new section
- Function workflow: adds step 5 (linker-script check) + renumbers
- Global-variable workflow: corrects the 'leave linker alone' line
- ps-rename-check: now checks linker-script consistency as part of
the exit-0 contract (covers both <old> removed and <new> added)
Still matching: md5 ed1378bc… (docs-only change, build unaffected)
The per-species base-stats table (HP/Attack/Defense/Speed/Special)
and its sibling all-unk_NN struct. Used in CalculateStatValue
(src/22630.c), func_88600020 (src/fragments/27/fragment27.c),
and battle code (src/fragments/62/*).
Note: also updates linker_scripts/us/symbol_addrs.txt and
undefined_syms.ld — the original 'do not edit linker scripts'
rule from the rename plan was incomplete: when a symbol is
declared by name in symbol_addrs.txt or undefined_syms.ld, the
rename MUST propagate to the linker script, otherwise the new
symbol is undefined and the link fails. The address (0x80070F84)
is preserved.
D_80070FA0[] stays as-is (different table, different layout, kept
on the renamed PokemonStatsExtended type via the extern decl).
Still matching: md5 ed1378bc… (verified by `make`)
The per-species battle animation / effect configuration table.
Indexed by species ID (decremented to 0-based) in the func_80048060-
area dispatcher. Fields unk_03/unk_04 are sound/animation parameters
returned via var_a3 / sp24.
The struct typedef (unk_D_80079070) and its fields (unk_00..unk_04)
are left untouched per the rename plan — table-level only.
Still matching: md5 ed1378bc… (verified by `make`)
The per-species sound/SE variant table. Indexed by species ID
(decremented to 0-based) in func_800367A0's cases 0x33 and 0x32.
The struct typedef (unk_D_80077970) and its fields (unk_00..unk_04)
are left untouched per the rename plan — table-level only.
Still matching: md5 ed1378bc… (verified by `make`)
Nine sites where 151 is the Gen-1 species count:
- L239, L246: mem_pool_alloc size for D_8006FF00 pointer arrays
- L240, L247: for loops over D_8006FF00[] (species metadata)
- L244, L251: func_8002CBB0 calls with species count + size args
- L6260: reverse for loop (i = 151 down to 1) over species
Introduces #include "pokemon.h" in this file.
Still matching: md5 ed1378bc… (verified by `make`)
Four sites in src/20470.c and src/fragments/50/fragment50.c where
151 is used as the Gen-1 species count:
- src/20470.c:514 — for loop over D_8006FF00[] (species metadata)
- src/fragments/50/fragment50.c:69 — D_82608568 array size
- src/fragments/50/fragment50.c:1679 — for loop counting owned species
- src/fragments/50/fragment50.c:2613 — for loop loading portrait textures
Introduces #include "pokemon.h" in both files.
Other 151 occurrences in fragments/50/fragment50.c (lines 2574,
2630, 2632) are explicit Mew species IDs, not species counts —
left untouched (would need a separate MEW = 151 constant if
named, but that's out of scope for the count-sentinels pass).
Still matching: md5 ed1378bc… (verified by `make`)
Three sites where arg0 (species ID) is compared/assigned/switched:
- L1089: range check ((arg0 == 0) || (arg0 >= SPECIES_BLANK))
- L1179: default species assignment in case 1
- L1214: switch case fallthrough to default handler
Introduces #include "pokemon.h" in this file.
Other 0x98 occurrences (L86, L276, L570) are D_80079070[] data
table entries (per-species animation/sound parameter values), not
species IDs — left untouched. Other 0x9C/0x9D/0x9E/etc. cases in
the same switch are Stadium-specific species IDs beyond the
national dex range, distinct from SPECIES_BLANK — left as-is.
Callers: func_80048060-area (src/3D140.c)
Still matching: md5 ed1378bc… (verified by `make`)
Four sites in func_800367A0 (case 0x33 and case 0x32) where
arg1 (a species ID) is checked against the upper-bound sentinel
before decrementing to a 0-indexed array. Introduces
#include "pokemon.h" in this file.
Other 0x98 occurrences in 373A0.c (lines 24, 31, 41) are
D_80077970[] data table entries (per-species animation/sound
parameter values), not species IDs — left untouched.
Callers: func_80048408-area (src/3D140.c:857, etc.)
Still matching: md5 ed1378bc… (verified by `make`)
Upper bound of the wider species ID range accepted by func_80022A60
(maps to D_800732FF). Returns SPECIES_BLANK (already named) on
out-of-range.
Callers: func_80021D9C, func_800228F0 (src/232C0.c:121, others)
Still matching: md5 ed1378bc… (verified by `make`)
The canonical Gen-1 species range check. Used as the upper bound
of the lookup in D_80073267.
Callers: func_80021F04, func_80026268 (src/232C0.c:155, others)
Still matching: md5 ed1378bc… (verified by `make`)
The out-of-range return value in the wider species ID lookup
function. Introduces #include "pokemon.h" in this file.
Callers: func_80021D9C, func_800228F0 (src/232C0.c:121, others)
Still matching: md5 ed1378bc… (verified by `make`)
Graphics cluster: draws the score/counter panel (layout by D_8122C4E2), then
lays out the value's digit run right-to-left via the D_8120E580 glyph-width
table (u8[], two-level lookup at +0x80). Same composer pattern as func_81202210;
strides (var_s4-0x266 bytes, u16 dst step, D_8120D7F3 u8[]) verified vs asm.
Bare GLOBAL_ASM: 34 -> 33.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Graphics cluster: clears the GB screen buffer, draws the border glyphs, then
lays out the digit run using a two-level glyph-width lookup in D_8120E480
(width index at +0x43, then advance). Reverse-engineered the three glyph tables
from the asm: D_8120E480 (u8[]), D_8120E680 (u16[]), D_8122AD50 (u8[]).
Bare GLOBAL_ASM: 35 -> 34.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Graphics cluster: scales each source pixel's luminance (blue channel, 0-31) by
the arg2 RGB565 tint and writes it to the render target. IDO 4x-unrolled loop
rewritten as a simple double loop (all 4 unrolled pixels verified identical).
Bare GLOBAL_ASM: 36 -> 35.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Graphics cluster: blits corner/edge glyphs from D_8122C748 into the render
target, then paints the 0xC14 grid lines. m2c mistyped every pointer stride as
a u16 element count; verified against the asm that they are all byte offsets
(u8* base + byte offsets, cast to u16* at each access). Bare GLOBAL_ASM: 37 -> 36.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Graphics cluster: copies a tile's set (odd) pixels from D_8122C748's plane data
into the render target at +0x5B4, 4 pixels per step over an 0xB x 0xC region;
arg1 selects source plane and scan direction. Multiplier (arg2*0x108), source
step (+/-1 u16) and framebuffer offset verified vs asm. Bare GLOBAL_ASM: 38 -> 37.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Play-sound path: search the 3-slot cache for the sound id, evicting+DMA-loading
the file, player object and wave data on a miss, then dispatch by event type to
start/stop a voice via the slot's command table. Built on reverse-engineered
audio types: File2SubHeader2.files[] table, ALSeqFile seqArray (D_800FC700 wave
entries), and the 6-byte command record at D_800FC6C0[slot]+0x10 (all strides/
offsets verified vs asm). Includes 373A0.h for the audio globals (match-safe).
Bare GLOBAL_ASM: 40 -> 39... crosses 80% C-first.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
373A0.c allocates D_800FC6F0/D_800FC6FC as a File2SubHeader2 header followed by
num_files u32 file offsets; the event/sound dispatchers index that array. Add it
as files[ENTRY_SIZE] (flexible array) so sizeof stays 0xC and the matching build
is unaffected (verified md5). Documents the real audio file-table layout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Model initializer: copies the header block into the model's 0x138 sub-struct,
records the matrix's per-axis scale into position, runs the base transform and
rebuilds the joint command list. This clears the last bare function in 33FE0.c,
which is now entirely C (build-free). Bare GLOBAL_ASM: 40 -> 39.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The leftover-$f0 pair: both used a distance value returned via $f0 by
func_80035434 / func_80035538, which were declared void ending in a bare
sqrtf(...). Changed those to return f32 (verified still byte-matching), then:
- func_80035B20: spring-blends target vertices toward sources by scaled distance
- func_800357F4: builds the transform-command list from the joint adjacency graph
This completes the 33FE0 model cluster. Bare GLOBAL_ASM: 42 -> 40.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds an orthonormal frame from the p0->p1 direction (picking an up-vector via
D_8007C5B4, two cross products), stores a new StadiumTransform with its four
control points and matrix, then computes each vertex's projection weights.
Largest 33FE0 function (0x510). Bare GLOBAL_ASM: 43 -> 42.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clears per-vertex accumulators, adds each triangle's length-120 face normal
(cross product) to its three vertices, then stores the per-vertex average as
the vertex color. m2c's 4x-unrolled clear/average loops rewritten as simple
loops; triangle index stride (3x s16) and (s32)len>0 guard verified vs asm.
Bare GLOBAL_ASM: 44 -> 43.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same barycentric deform as func_80034824, gated on func_80033568 projecting the
vertex onto the transform's p0->p1 edge within (0, maxDist); clears the bit
otherwise. arg0 is the segment forwarded to Memmap (as func_800361C4 passes it).
Bare GLOBAL_ASM: 45 -> 44.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Transforms a StadiumTransform's four corners by its matrix (guMtxXFMF), then
sets each flagged vertex's offset to the weight-interpolated corner minus base.
Offsets verified against the asm: nextIndex (0x92), disabled (0x8E), and the
per-transform weights at vtx + arg2*0x10 + 0x14. Bare GLOBAL_ASM: 46 -> 45.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zeros sub-threshold offsets, folds the drawGroup-averaged offset into base,
and decays the offset by stiffness. m2c read the count via the position
pointer at +0x2C (= ModelVertex.drawGroup, 0x90); resolved against the struct.
Bare GLOBAL_ASM: 47 -> 46.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adjusts flagged vertices' Y position by mode; retyped m2c's void* args to
ModelSegment*/ModelVertex*. Defining it before func_800361C4 lets that
function's forward-decl be dropped. Bare GLOBAL_ASM: 48 -> 47.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Jump-table dispatch on model type calling per-type transform sequences, then
a vertex remap loop copying the 0x30-byte pos/color/tex tail. Offsets and the
inline copy verified against the asm; Memmap_GetSegmentVaddr returns uintptr_t
so results are cast. Bare GLOBAL_ASM: 49 -> 48.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
func_81207698 (threshold lookup switch, hand-decompiled from asm since m2c
gave a degenerate draft) and func_81208C08 (message-dispatch switch, m2c via
the rodata jtbl input) in fragment1_86CB0. Bare GLOBAL_ASM: 51 -> 49.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tile-grid draw routine sourcing glyph data from D_8122C748->unk0000; each
m2c "D_8122C748 + offset" maps to &D_8122C748->unk0000[offset]. Bare
GLOBAL_ASM: 52 -> 51.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
m2c resolves switch jump tables when the rodata .s defining jtbl_* is passed
as an extra input. Unblocks the 14 remaining jump-table functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correct the prototype to (u16*, s32) to match the actual function, avoiding
a future conflict when func_81202758 is decompiled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
func_81202FCC (timer-driven counter update) and func_812006AC (GB-pak ROM
read with checksum, mirrors sibling func_812008C8) in fragment1_7F9A0.
Bare GLOBAL_ASM: 54 -> 52.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
C-first: wrap func_80033D1C, func_800414B8, func_8003F4C0 (3D140/33FE0) and
func_812075C0, func_81209690 (fragment1_86CB0) in #ifdef NON_MATCHING. The
matching build still uses GLOBAL_ASM and stays byte-exact; NON_MATCHING now
compiles these from C. Bare GLOBAL_ASM: 61 -> 56.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add docs/c-first-plan.md (campaign to write C for every function before
matching), document the macOS build gotchas and Rust matching toolkit in
README/CLAUDE, and record m2c as an optional dev dependency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Support the C-first campaign: globalasm now detects whether each GLOBAL_ASM
is behind a NON_MATCHING guard; ps-status --c-coverage reports guarded vs
bare counts (the "bare -> 0" metric) with --list/--json; ps-fdiff --file
summarizes every function in a source object, ranked closest-first.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The map parser dropped symbol-less contributions and vram==0 archive
members, undercounting progress by 6980 bytes vs progress.py. Skip
unallocated (vram==0) contributions and synthesize a $_static_symbol for
the leading gap before a contribution's first named symbol, matching
mapfile_parser symbol-for-symbol (8240 symbols / 2204464 bytes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
macOS's BSD iconv mis-encodes an ASCII backslash as the EUC-JP fullwidth
backslash (0xA1C0) after a multibyte character, corrupting \n escapes in
Japanese string literals and breaking the byte-match (~293-byte divergence
in fragment62). Prefer GNU libiconv from Homebrew when present, warn otherwise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a cargo workspace under tools/rust/ with a shared ps-core
library and three binaries for the byte-for-byte matching effort:
- ps-status: status of functions pending decompilation. Build-free mode
scans GLOBAL_ASM in src/ (validated: 190 funcs across 68 files, matching
grep); build-aware mode reports byte progress per folder from the linker
.map, porting the progress.py metric.
- ps-firstdiff: first difference(s) between built and expected ROM, with
function naming and jal-target resolution (like tools/first_diff.py).
- ps-fdiff: non-interactive per-function asm diff of built ROM vs baserom
(like ./diff.py -mwo <func>).
ps-core provides parsers for GLOBAL_ASM, symbol_addrs, and GNU ld .map
files (with vram/vrom lookups), a .z64 reader with md5 verification, and a
rabbitizer wrapper for disassembly. All parsers are unit-tested.
Wired into tools/Makefile (cargo build, skipped with a warning if cargo is
absent), target/ gitignored, and documented in README.md and
tools/rust/README.md.
The Python scripts (progress.py, tools/first_diff.py, diff.py) are kept in
place: only the build-free ps-status path can be validated without a ROM
and toolchain. Parity of the build-dependent tools must be confirmed in a
full build environment before removing the Python equivalents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>