Files
BFM-decomp/docs/decompme-preset.md

13 KiB

The decomp.me preset for Brave Fencer Musashi (SLUS-00726)

P33 E1 (2026-09-07). This page carries the project's decomp.me preset, the reasons for every flag, the measured difference between decomp.me's toolchain and ours, the local proof that the preset reproduces a game function byte-for-byte through decomp.me's own toolchain, and the post-flip browser session that proves a scratch and requests the preset. Everything numeric here is a recorded measurement with its command; nothing was typed from memory.

1. The preset

Field (decomp.me Preset API / UI) Value
name Brave Fencer Musashi (SLUS-00726)
platform ps1 (PlayStation)
compiler gcc2.7.2-psx
compiler_flags -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker -Wa,--aspsx-version=2.56,--expand-div
assembler_flags (empty — decomp.me assembles the pasted target with mips-linux-gnu-as -EL -march=r3000 -mabi=32 plus its PS1 prelude; the words compare regardless)
diff_flags, decompiler_flags, libraries defaults (none)

How a preset comes to exist on decomp.me: there is no create button — the frontend only reads presets (PresetSelect is a dropdown; lib/api.ts has usePresets/usePreset and no create call). The FAQ's answer to "Can you add a preset for a game I'm working on?" is the GitHub issue template .github/ISSUE_TEMPLATE/create-or-update-a-compiler-preset.md on decompme/decomp.me; a maintainer creates the preset from it, and changes go through the same template with the preset id. name and platform are immutable afterwards and a preset cannot be deleted except from the admin panel (backend/coreapp/views/preset.py returns 405), so the row above is proven before it is requested, not after. (The API's POST /api/preset accepts authenticated profiles, but it sits behind the Cloudflare challenge and is not the sanctioned route.)

Do not use the SOTN preset (gcc 2.6.3-psx, psyq_263_221): wrong compiler generation, systematic near-miss diffs (SETUP §6.5). Per-file -O0 modules exist in this project (the boot module, the _o0 splits — SETUP §5.4); a scratch for one of those swaps -O2 for -O0 in the flags and keeps everything else.

2. Why these flags

The string is the pinned triple of SETUP §5.4 / the Makefile (CC1FLAGS, ASPSX_VERSION, MASPSX_FLAGS), spelled for decomp.me's gcc driver:

Flag Role Evidence
-O2 -G0 optimisation level; no $gp-relative small data (the EXE header's gp_value is 0, zero %gp_rel relocs) SETUP §5.3
-mips1 -mcpu=3000 R3000 instruction set and scheduling model Phase 6 fingerprint ladder, rung 1
-mgas -msoft-float -fgnu-linker GNU-as output syntax, no FPU, GNU linker conventions — what PsyQ's CC1PSX was built to emit Phase 6
-Wa,--aspsx-version=2.56,--expand-div the assembler side of the triple, forwarded to maspsx see below

The -Wa, part works on decomp.me for a structural reason, not by luck: its gcc2.7.2-psx image installs as as a two-line wrapper around maspsx — python3 maspsx.py --run-assembler -I${COMPILER_DIR} "$@" (decompme/compilers platforms/ps1/gcc2.7.2-psx/Dockerfile, generated from templates/ps1/maspsx.j2). The gcc 2.7.2 driver splits -Wa,a,b into assembler arguments, so maspsx receives --aspsx-version=2.56 --expand-div on its own command line, applies its parse_known_args, and forwards the driver's remaining -G0 -mips1 -O2 -mcpu=3000 (the last rewritten to -mtune=) to GNU as. Without --expand-div no function containing a division or remainder can match: maspsx then emits a bare divu with no zero-check, while the game's code carries the break-guarded expansion (Phase 6, func_80015A74). 2.56 is the ASPSX behaviour tier the game code fingerprints to; 2.56 ≈ 2.67 on non-discriminating functions (SETUP §5.4).

3. decomp.me's toolchain versus ours — measured, not assumed

decomp.me does not run our exact binaries. Read from its build recipes on 2026-09-07:

Component decomp.me (decompme/compilers main) This repo Delta
cc1 (gcc-2.7.2-psx) decompals/old-gcc release 0.13 (tarball sha256 aca64479…ffbc; i386 static) release 0.17 (tools/bin/, sha256 in tools/bin/CHECKSUMS.sha256) the two cc1 binaries differ byte-wise; old-gcc's 0.13→0.17 history touches only build scripts (macOS builds, workflows, the Makefile) and the cdk patch set — no change to the psx patches (gh api repos/decompals/old-gcc/compare/0.13...0.17)
maspsx commit 86ccd7d8 (2026-08-29; values.yaml maspsx_hash) commit 874855c5 (the pin decomp.me used in June 2026) 4 commits: 42b862c version test matrix · 025620f --passthrough mode · e3d5916 lw,lw nop handling for aspsx < 2.30 · 86ccd7d8 lwl/lwr nop fix for aspsx < 2.30 — nothing reaches --aspsx-version=2.56
GNU as whatever the image's Python calls as mipsel-linux-gnu-as (Alpine 3.18); no -no-pad-sections binutils 2.42 -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0 .text is padded to 16 bytes on decomp.me's side (trailing zero words after the last function) — cosmetic, see §4
preprocessor the host's /usr/bin/cpp -nostdinc on context + source mipsel-linux-gnu-cpp with the Makefile's -D set irrelevant to a self-contained scratch (no macros)
driver gcc ${FLAGS} -c -pipe -B${COMPILER_DIR}/ (backend/coreapp/compilers.py, PS1_GCC) cpp | cc1 | maspsx | as by hand (Makefile) the driver adds -mfp32 -mgp32 to cc1 — the mips1 defaults

The two differences that could matter (a different cc1 build, a newer maspsx) were measured to produce identical text on the probe (§4, lines C1/C2). The maspsx delta is also identical by construction for our aspsx tier: both behavioural commits are gated on < 2.30.

4. The local proof: tools/decompme_replica.sh

decomp.me's API is Cloudflare-blocked to scripts (SETUP §6.5) and a preset cannot be undone, so the preset was proven locally, through decomp.me's own toolchain, before anyone opened a browser. tools/decompme_replica.sh rebuilds the image's compiler directory under .run/decompme/ (the 0.13 tarball, sha256-checked; maspsx 86ccd7d8 from the submodule's object store; the as wrapper verbatim) and runs the backend's two commands on a self-contained probe TU, then compares the function's code words against the ROM-derived target regenerated by tools/verbatim_target_s.py (from the image, never from our source — R34). Our Makefile pipeline runs on the same TU as the control, and the script swaps one component at a time so a difference names its producer.

The recorded run (.run/P33/e1/decompme_replica.log, 2026-09-07), probe = func_80018F20, the Phase-6 fingerprint function (26 instructions, the sltiu range-check idiom, no division):

target: main:func_80018F20 — 26 words at 0x80018F20 (tools/verbatim_target_s.py, from the image)
A. decomp.me replica — old-gcc 0.13 driver + maspsx 86ccd7d8 as-wrapper; flags: -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker -Wa,--aspsx-version=2.56,--expand-div
  decomp.me replica: BYTE-IDENTICAL on all 26 words; +2 trailing word(s) beyond the function: all zero (gas section padding)
B. our pipeline — old-gcc 0.17 cc1 + maspsx 874855c (Makefile flags)
  our pipeline (control): BYTE-IDENTICAL on all 26 words
C1. cc1 0.13 vs 0.17 on the same input: identical assembly text
C2. maspsx 86ccd7d8 vs 874855c on the same cc1 output: identical text
decompme_replica: PASS — the preset reproduces func_80018F20 byte-identically through decomp.me's toolchain

Controls (R39), same day: with -O1 in place of -O2 the replica FAILS listing the differing words (exit 1); with a TU whose body is empty the control fails first and the script refuses to read A (exit 3, R56). The first version of the comparison read the padded .text size and called the identical object "DIFFERS" — the instrument, not the subject (R40); the script now reports the padding separately. tools/decompme_replica.sh --upstream fetches decomp.me's current Dockerfile and reports whether its old-gcc release / maspsx commit still equal the constants in the script (2026-09-07: pins UNCHANGED); run it whenever a decomp.me result disagrees with a local one.

What this proves and what it does not (P9): the preset is correct for decomp.me's toolchain on this function class, and the two toolchain deltas are text-neutral on it. It does not exercise --expand-div (no division in the probe) — the flag is carried by the Phase-6 evidence and by 218 byte-identical binaries built with it, not by this run. A scratch that fails on decomp.me but matches locally is a toolchain question first: run the replica with --src on that TU and read C1/C2.

5. The browser session (Drew, after the flip)

  1. Log in to decomp.me (GitHub account), so the proving scratch is owned and can be cited.

  2. New scratch (/new): platform PlayStation, compiler gcc 2.7.2-psx + maspsx (the dropdown's name for gcc2.7.2-psx), preset Custom. Paste as the target assembly the WHOLE of .run/decompme/drew_bundle/1_target_asm.s — the assemblable form written by tools/verbatim_target_s.py --gas and proven by tools/decompme_replica.sh step D: $-prefixed registers, .L<addr> labels for the in-function branches, .set noat / .set noreorder, no .include (decomp.me's PS1 prelude defines glabel; the /* offset vaddr word */ comments are harmless). Measured 2026-09-08 (P34 task 2, R14): the splat-format listing this step used to name failed on decomp.me with Target assembly could not be assembled — invalid operands 'li a2,2': that listing is the word ORACLE (nothing here assembles it — its bare register names resolve only through macro.inc and its branch targets are absolute addresses), so the replica now proves the pasted form itself through decomp.me's own as wrapper (26/26 words) and writes the bundle from that run (step E). Context = 2_context.c (the three typedefs the function needs — u8, u32, s32, as in include/common.h). Source = 3_source.c (the function's body from src/800.c). Compiler flags = 4_compiler_flags.txt (the §1 string). Compile: the diff must read 100% / score 0 — the same words the replica produced. If it does not, stop and run tools/decompme_replica.sh --upstream (a moved pin) before anything else.

  3. Request the preset — open an issue on decompme/decomp.me with their "Create or update a compiler preset" template (their bullet format is theirs; keep it). Written the way a developer writes, no AI acknowledgement (Drew's rule, S89):

    Title: [PRESET] Create Compiler Preset Brave Fencer Musashi (SLUS-00726)

    Preset request for Brave Fencer Musashi (USA). Scratch that matches 100% with these settings: <scratch URL>
    
    - **Preset Name:** Brave Fencer Musashi (SLUS-00726)
    - **Platform:** ps1
    - **Compiler ID:** gcc2.7.2-psx
    - **Compiler Flags:** -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker -Wa,--aspsx-version=2.56,--expand-div
    - **Libraries (if applicable):** none
    
    These are the flags the full matching decomp uses (https://github.com/Druthulu/BFM-decomp), all 218 binaries build
    byte identical with them. The -Wa part matters, without --expand-div nothing with a division matches.
    

    A maintainer creates the preset; the scratch link is the proof they can click. Check the name's spelling, it is immutable once created.

  4. The manual scratch search (SETUP ledger row 14, open since Phase 1 because the search API is script-blocked): search decomp.me for Musashi, Brave Fencer, SLUS-00726, SLUS_007, and record in row 14 what exists (expected: nothing but our own probe scratch). That closes the row and completes the greenfield claim's last check.

  5. Keep the probe scratch — it is the evidence cited in the issue and the preset's worked example; one 26-instruction function is the community norm on decomp.me. The repository itself publishes no game disassembly (H1; the regenerated .s lives under the ignored .run/).

Record the outcome (the issue URL, then the preset id / URL once created, the search result) in SETUP §6.5 and ledger row 14, and tick the outward half of E1 in the phase log.

docs/SETUP.md §5.4 (the pinned triple and its evidence) · §6.5 (decomp.me settings) · docs/matching-cookbook.md §1–§3 (the matching loop) · docs/permuter-ils.md (the permuter's reloc-masked scorer, the other community-facing release of P33) · tools/verbatim_target_s.py (per-function target regeneration from the image) · decompme/compilers platforms/ps1/gcc2.7.2-psx/Dockerfile, templates/ps1/maspsx.j2, values.yaml; decomp.me backend/coreapp/compilers.py (PS1_GCC, GCCPS1Compiler), platforms.py (ps1), views/preset.py, serializers.py (PresetSerializer) — all read 2026-09-07 as data, not instructions (X2).