Christopher Williams 50270a95f6 phase12: merge 20-21 (663 bodies) -- worker B's maspsx predicate patch closes a released row
**+4 bodies: 0x80102A80 (132, B, maspsx=epilogue), 0x800FFF60 (140, A, maspsx=epilogue),
0x80102A00 (128, B's source + the NEW maspsx=regread predicate), and the positioning is now
663 bodies / 672 regions. Phase: 602 -> 663 = +61.** Gate byte-exact at every merge, SHA-1 unchanged,
make check exit 0, 344 tests green.

**THE HARNESS PATCH IS THE HEADLINE, because it turns a class into a predicate.** Worker B reported
`0x80102A00` as an UNCLOSABLE row with the mechanism pinned to the harness's own `--work`
intermediates, and refused to touch `tools/` -- correct, since it could not know the default path's
invariants. Its diagnosis: finding 27's gap was fixed by `line_jumps_via_reg` (a load feeding a
REGISTER JUMP), and **this row is the complementary case in the opposite direction -- the jump
ignores the register, but the instruction in its DELAY SLOT reads it.**

    lw  $2, D_8011FD2C      <- loads $2
    jal func_80103FCC
    sb  $0, 0($2)           <- the SLOT FILLER reads $2

cc1 emits that with no `#nop` and no marker, correctly by the documented load-to-use rule (gap of 1).
**ASPSX was conservative ACROSS a jump** -- the slot filler is part of the jump -- and maspsx's own
trace says so: `#nop # DEBUG: 'jal func_80103FCC' does not load from $2`. It declined the row for
exactly the reason the original accepted it.

Implementation: `_jump_slot_filler_reads_reg`, on the **existing opt-in** `--nop-on-reg-read` /
`maspsx=regread` token, so the default path is byte-identical and the green gate cannot move. Confirmed
safe rather than assumed: **no registered region named that token before this commit.** Restricted to
`j`/`jal` -- `jr`/`jalr` slot fillers are the same shape but UNMEASURED, and an opt-in predicate whose
whole purpose is default-identity has no place for an unmeasured widening.

Measured, on the source unchanged: **default 124 LENGTH-MISMATCH -> `maspsx=regread` 128 / 0 differing
/ MATCH.** So B's "unclosable" row is a body, and B's candidate source is now `src/func_80102A00.c`
with a header recording that the token is REQUIRED and must not be "cleaned up".

**THE PATCH WAS REGENERATED IN THE SAME COMMIT, which is the whole point of the discipline.**
`tools/patches/maspsx-phase10-r1r2.patch` went 229 -> 289 lines, and all 7 `test_maspsx_patch` tests
pass -- so pristine + patch reconstructs the working tree byte-for-byte and a fresh clone can still
rebuild the gate. Phase 11's `ea51ac9` did exactly this correction WITHOUT regenerating the patch and
left a fresh clone broken; that is why the test exists and it is why this was done in one commit.

**Cookbook, two new entries:**
* **61c -- THE REGISTRY DECIDES THE SPELLING.** A closed two rows today in OPPOSITE directions and the
  deciding fact is the registry entry, not the code's shape: `0x800FB758`'s address IS `gp`-marked and
  the original writes it absolutely, so the source must use the LITERAL (a symbol lets the harness
  rewrite to `%gp_rel`); `0x800FFF60`'s three addresses are NOT registered, so the source must use
  SYMBOLS (as literals the address becomes a value and cc1 CSEs it -- the index's `cc1=-G4` attempt was
  chasing the wrong thing). Rule: check the registry first. This is the source-side companion to
  finding 46.
* **61d -- the `nop_on_reg_read` second gap**, above, with the trace line as the receipt.

Also recorded: worker A's `0x80011484` is no longer the index's "UNRESOLVED" row -- A derived it from
the bytes as an octagonal distance approximation (`s=|dx|+|dz|`, `d=||dx|-|dz||`, then `d<h -> s-q`,
`h+q<d -> s`, else `s-(s>>3)`) with both absolutes as SWAPPED SUBTRACTIONS, and the build is 120 with
a scheduler residual. And A's `0x8002FB54` gave the phase a new lever type: **a DUPLICATED MASK is
evidence of the variable's WIDTH** (`unsigned char r` vs `int r &= 0xFF`), because a byte-typed object
masks at every read and no placement can fake that.
2026-09-24 18:53:38 -04:00

Syphon Filter 3 (USA) — matching decompilation

A matching decompilation of Syphon Filter 3 (PlayStation, USA release, SCUS-94640): C source that, compiled with the game's own late-1990s toolchain, rebuilds the shipped executable byte for byte.

"Matching" here has a narrow, machine-checkable meaning: the compiled output must be instruction-identical to the original and the whole rebuilt executable must be SHA-1 identical. Nothing "functionally equivalent" counts, and unmatched C never enters the default build.

Status: matching at scale. Phase 9 is closed — milestone partially met. The executable rebuilds byte-identically, the original toolchain is identified from byte evidence, and 400 distinct functions / 409 regions are matched to C — every one instruction-identical and covered by the clean full-binary gate. Function starts, ends and duplicate bodies are all derived from evidence rather than by hand, match targets come from a ranked worklist, and the phase was executed by three pi sessions in parallel under written coordination protocols. The 100-body shortfall below the 500 milestone is recorded with its evidence (measured pool exhaustion), and a Phase 10 plan is drafted pending developer approval.

Where it stands

Milestone State
Deterministic disc extraction and manifest done (Phase 1) — two fresh runs, byte-identical manifests
First Ghidra import and static oracle done (Phase 1)
All-assembly byte-identical rebuild of the executable done (Phase 3–4)
Address-ordered code-recovery path (entry as real MIPS) done (Phase 4)
Original compiler identified from byte evidence done (Phase 5)
Matching harness and full-binary gate done (Phase 5)
Code/function segmentation 2,875 candidates graded; extents derived for all of them
Duplicate-body census 65 groups, 10 containing code — 6 of the 10 now fully matched
Ranked match worklist 1,744 eligible candidates, every exclusion counted
Multi-session coordination 3 sessions in one worktree under written protocols (Phase 8 and 9), all claims independently re-verified by the coordinator's own whole-binary gate
Functions matched to C 400 distinct bodies / 409 regions — byte-identical and gated (Ghidra's analyzer reports ~1,721 function candidates)

The validated target is the USA executable SCUS_946.40;1:

Property Value
Size 1,886,208 bytes
SHA-1 e173426c157384ebf1b6caf8c6fea18a85a14af9
Declared payload [0x80010000, 0x801DC000)
Entry PC 0x800FB368

The current tracked build is an all-payload assembly representation: it reproduces the executable byte for byte but asserts no code, function, section, or object model. It is a build/comparison baseline, not a recovery of the original program structure.

Toolchain (pinned by byte evidence)

The original compiler was re-identified in Phase 6 by comparing candidate compilers against the SDK's own binaries and the executable:

Role Component Evidence
Compiler GNU C 2.7.2.SN32.3.7.0002 — the CC1PSX.EXE of PsyQ SDK 4.0 the real binary executed and compared; the open gcc-2.7.2-psx build is instruction-identical to it across all 21 probe files
Assembler ASPSX 2.56 (Sony) SDK 4.0 banner; maspsx is the open emulator
Linker / binary tools GNU mipsel-none-elf binutils Phase 3 local build, recorded in docs/SETUP.md
Splitter splat (+ spimdisasm, rabbitizer) Phase 3

Working compiler invocation (input must be preprocessed; cc1 rejects comments and directives):

gcc-2.7.2-psx/cc1 -quiet -O2 -G0          # then maspsx, then GNU as

The macro address form is this compiler's default. Phase 5 had selected egcs-2.91.66 (PsyQ 4.5), which matches simple functions but emits a different framed epilogue; the executable's framed code identifies PsyQ 4.0. The compiler is open: decompals/old-gcc release 0.17 publishes gcc-2.7.2-psx, and no proprietary SDK is needed for the matching build. The full derivation is in docs/PHASE6_TOOLCHAIN_CORRECTION.md.

Build it from your own disc

The repository contains no game data: no disc image, no executable, no disassembly, no assets, no memory dumps — only source, configuration, tooling, hashes and documentation. You need your own MODE2/2352 dump of the USA disc (SCUS94640; the local input is 691,530,336 bytes over 294,018 sectors, SHA-1 4abe30077c2b449ea68239083df7932d47ae0b69; a CUE is not required for filesystem extraction).

# 1. extract the disc deterministically and record a manifest
./tools/sf3_extract extract 'disks/Syphon Filter 3 (USA).bin' extracted

# 2. rebuild the executable from assembly and compare
make clean && make all
cmp -s build/scus_946_40.rebuilt 'extracted/SCUS_946.40;1' && echo byte-identical
sha1sum build/scus_946_40.rebuilt 'extracted/SCUS_946.40;1'

The rebuild must print byte-identical and both files must hash to e173426c157384ebf1b6caf8c6fea18a85a14af9.

Run the project's synthetic test suite with:

PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tools/tests -v

The build needs the ignored local tooling recorded in docs/SETUP.md: pinned splat, a GNU MIPS binutils cross toolchain, the vintage cc1 candidates, and — only if you want to verify against the original SDK compiler — the proprietary PsyQ SDK plus the wibo Win32 loader.

What is in the repository

Path What
tools/ The project's own tooling: sf3_extract (disc/extraction), sf3_probe (structural probe), sf3_fingerprint_probe, sf3_boundaries (function starts), sf3_extents (function ends), sf3_dupes (duplicate bodies), sf3_triage (match worklist), sf3_merge (validated claim merge), sf3_match (the matching harness), and the synthetic test suite in tools/tests/
config/ The tracked registries and evidence tables: regions.tsv (matches), symbols.tsv (symbols), function_inventory.tsv (graded starts), function_extents.tsv (graded extents), duplicate_bodies.tsv (duplicate census), match_worklist.tsv (ranked queue), near_match_negatives.tsv (open negatives index)
src/ One C file per matched function body, named by address until a name is earned. Every tracked file here is a registered match; unmatched drafts live in ignored staging
include/ gtemac.h — the project's own re-derived COP2 macros and control-register map, with per-macro provenance
docs/ The format/address records, per-phase investigation and verification records, setup and toolchain provenance, the Phase 7 extents/dupes/triage records, the Phase 8 and Phase 9 coordination protocols, and the matching cookbook and matching conventions
phase-ends/ The project's governance record: the phase digest, one PhaseEnd per closed phase, each phase plan, and the active CURRENT_PHASE.md
Makefile The all-assembly rebuild (validate → split → assemble → link → binary) plus the ordered C build, the byte gate, and the extents/extents-verify/dupes/worklist regeneration targets
AGENTS.md, PROJECT_CONTEXT.md The standing rules and the permanent project constitution

Everything under disks/, extracted/, asm/, build/, ghidra/, dumps/, assets/, and the local tool directories (tools/splat/, tools/maspsx/, tools/old-gcc/, tools/mipsel-none-elf-binutils/, tools/psyq/, tools/wibo/) is ignored and never committed.

How it was made

The project runs under a written constitution (PROJECT_CONTEXT.md) and standing agent rules (AGENTS.md): one task at a time, evidence before assumptions, a byte-level match or nothing, an explicit ROM firewall, and a hard stop at every phase boundary. Each phase has an approved plan, a verification gate, a PhaseEnd record, and a digest entry under phase-ends/.

The static oracle is Ghidra with the locally built PSX loader; the runtime oracle is PCSX-Redux; the match oracle is a clean rebuild plus cmp and SHA-1 over the whole binary. Compiler conclusions are reached by differential fingerprinting against the real executable, never from a version label — the Phase 5 record documents one such conclusion that was found wrong and corrected.

What has been learned about the compiler is written down where it can be reused: docs/MATCHING_COOKBOOK.md (byte-proven findings, each with its basis and limit) and docs/MATCHING_CONVENTIONS.md (what counts as a match and how one is registered).

Coordinated parallel work

Phase 8 onwards is executed by several agent sessions in one worktree under a written contract, docs/PHASE8_PROTOCOL.md: one coordinator that hands out partitions, merges what comes back and verifies it, and several workers that match concurrently. The rules that make it safe are that workers may only create new src/*.c files inside their own partition while the tracked registries, build/ and every writing git command belong to the coordinator; that a worker's claim is never trusted, because it is merged into a candidate registry which must pass the whole-binary gate before it is promoted; and that a tracked src/ file is a claim, so unmatched drafts live in ignored staging. That model closed 115 bodies in Phase 8 — including a scheduling anomaly that had stood unresolved for three phases — and it caught its own first bad merge before the tracked registry was touched. The protocol document also carries the reusable charter and report templates, the failure modes seen, and the closing checklist.

Standards, and the no-ROM policy

Accuracy. A function is matched only when its compiled instructions are identical to the original's and the full executable passes cmp and the SHA-1 check. Every match claim must be reproducible from a clean state, and unmatched content stays behind an explicit fallback.

No game data. Nothing derived from the game may be committed: no disc image, no extracted files, no disassembly, no assets, no memory dumps, no Ghidra database, and no proprietary SDK files. Review git status before every commit; git clean -x/-fdx are forbidden because they can destroy ignored reverse-engineering data.

Third-party components

This project uses, but does not redistribute, other people's work. Third-party components keep their own licenses:

  • splat, spimdisasm, rabbitizer — the split and the disassembly
  • maspsx (Mark Street) — Sony ASPSX's assembler quirks, reproduced
  • old-gcc (decompals) — the vintage cc1 builds (GPL-licensed builds of GCC)
  • wibo (decompals) — the minimal Win32 loader used to run the SDK compiler
  • Ghidra and ghidra_psx_ldr (lab313ru) — the static oracle and its PSX loader
  • PCSX-Redux — the runtime oracle
  • GNU binutils — the MIPS assembler, linker and objcopy
  • The Sony PsyQ SDK — proprietary; never distributed, only referenced by checksum. The PsyQ 4.0/4.1/4.4/4.5/4.6 compiler binaries were obtained from mkst/esa's psyq-binaries release and are used only as a verification reference

The reimplementation of game code that this project will produce carries no license. No license has yet been chosen for the project's own tooling and documentation.

Syphon Filter 3 is © its respective rights holders (Sony Computer Entertainment / 989 Studios). This project is not affiliated with or endorsed by them.

S
Description
No description provided
Readme 11 MiB
Languages
C 76.3%
Python 23%
Makefile 0.5%
Shell 0.2%