The repository root carried seven markdown files that were not onboarding
material, plus two that had been superseded by the code they described. This
sorts them: reader-facing docs to docs/, working notes to docs/internal/, and
the two superseded ones merged into the docs that replaced them.
Moved to docs/: WIDESCREEN.md, ENHANCEMENTS.md, TCP_COMMANDS.md
Moved to docs/internal/: PRINCIPLES.md, ISSUES.md, DEBUG.md, GTE_LM_FIX.md,
and from docs/: bios-hardening.md, overlay-status.md,
overlay-plan.md, overlay-recompilation-design.md,
overlay-discovery.md, TOMBA2_EXC_CONSULT_RESPONSE.md,
CASE_A_AOT_GAP.md, audit_inventory.md
Moved to docs/internal/upstream/: UPSTREAM_2026-08-20.md
The root keeps only what belongs there: README.md, CONTRIBUTING.md, LICENSE,
CLAUDE.md, THIRD_PARTY_ATTRIBUTION.md, RELEASE_NOTES.md, the dotfiles and
psxrecomp_cli.py.
MERGES
SPLITGEN_MIGRATION.md folded into docs/BUILDING.md as "Split generated C". Its
migration instructions were stale in a way that mattered: it told every game
repo to hand-edit its CMakeLists and add a file(GLOB ...) over
<serial>_full*.c, but runtime/runtime.cmake now does that itself. GEN_FULL_GLOB
is a multi-value argument of psxrecomp_add_game_runtime(); the loop at
runtime.cmake:1985 makes each pattern absolute against CMAKE_CURRENT_SOURCE_DIR,
expands it with file(GLOB), and concatenates the hits into
GAME_GENERATED_FULL_C. So a title names the pattern and gets however many shards
a regeneration produced. The rewritten section describes that, the two-step
fallback when the globs match nothing (GEN_FULL_FALLBACK, else <serial>_full.c
derived from the dispatch marker by substituting _dispatch.c), and the one
caveat the code actually has: that file(GLOB) is plain, not CONFIGURE_DEPENDS,
so a regeneration which changes the shard COUNT needs a re-configure to be seen.
psxrecomp-toml-readme.md folded into docs/GAME_PROJECT_SETUP.md as "EXE-only
autofill (psxrecomp-toml)", placed after "Disc autofill (probe_disc.py)" and
before "Project Studio" so the two autofill paths sit together. Its documented
flags were checked against the usage text in recompiler/src/main_toml.cpp:28-42
and against the argument parser at :150-165 -- all seven options match with no
drift, so the flag list is reproduced as-is. Added a pointer that probe_disc.py
is preferable when a cue exists, since that tool does the same EXE-header work
plus disc identity and netplay gates.
FUNCTIONAL FIXES
tools/gen_tcp_commands.py wrote to REPO/"TCP_COMMANDS.md". After the move that
path does not exist, so the generator would have created a new stray root file
and --check would have failed against it forever. Retargeted to
REPO/"docs"/"TCP_COMMANDS.md", along with the seven docstring and message
strings that name the path.
Running it surfaced two further problems, both fixed here:
* The doc was genuinely stale -- pre-existing drift, not introduced by this
commit (the moved file is byte-identical to origin/master, and no
debug-server command table is touched here). Four commands registered since
the last regeneration were missing from the index -- cdrom_timing_dump,
savestate_status, ws_ui_groups, xprobe_watch -- and scanline had gained a
backend. Regenerated: 307 -> 311 commands, and the change is confined to the
AUTOGEN block (the hand-written head and tail are byte-identical to master).
tools/gen_tcp_commands.py --check now exits 0.
* DOC.write_text() has no newline argument, so Python translated every newline
to os.linesep. On Windows that rewrote all 585 lines of the file to CRLF and
turned a four-line index update into a whole-file diff -- measured: 589 CRLF
/ 0 LF after a run, against 0 CRLF / 585 LF in the repo. Now written through
an explicit newline="" handle, verified to leave the file at 0 CRLF.
runtime/include/debug_server.h:9 and runtime/src/debug_server.c:8 cited "TCP.md
and DEBUG.md". TCP.md has never existed in this repo under that name; both now
name docs/TCP_COMMANDS.md and docs/internal/DEBUG.md.
LINK AND REFERENCE FIXES
Every inbound reference to a moved file was located by grepping each basename
across the whole tracked tree, not just the markdown. Markdown hrefs in
README.md, CONTRIBUTING.md, docs/ARCHITECTURE.md, docs/COMPILING_OVERLAYS.md,
docs/AOT_OVERLAY_PLAN.md, docs/RELOCATION_MANIFEST_FORMAT.md,
docs/config_schema.md, docs/OVERLAY_XREF.md, docs/TCP_COMMANDS.md and several
docs/internal/ files now point at the new locations; where two files became
siblings under docs/internal/ the path was shortened rather than lengthened
(bios-hardening.md's reference to overlay-discovery.md, for instance).
docs/COMPILING_OVERLAYS.md's "in this folder" phrasing was wrong once two of the
three files it listed moved, so it now names internal/ explicitly.
24 source-comment mentions of ENHANCEMENTS.md sections G1.x across 19 files
(recompiler, runtime, cmake) were retargeted to docs/ENHANCEMENTS.md with the
section numbers untouched. docs/SHADOW_ENHANCEMENTS.md is a DIFFERENT file and
was deliberately excluded.
20 rationale strings citing docs/CASE_A_AOT_GAP.md across the four
recompiler/seeds/*.json files were updated. Done as a byte-level substring
replacement rather than a JSON round-trip: two of those files are stored with
CRLF endings and two with LF, and re-serializing them reformatted 5,700 lines.
The byte-level version changes exactly 20 lines and each file still parses.
References that look similar but point elsewhere were deliberately left alone,
and are worth naming so a future pass does not "fix" them:
* "recomp-template PRINCIPLES.md" / "NES recomp template PRINCIPLES.md" in
CLAUDE.md:43, docs/SHADOW_ENHANCEMENTS.md:28,
docs/internal/HLE_SCHEDULER_CARVEOUT_PLAN.md:5,
runtime/src/debug_server.c:4934 and tools/parity_diff.py:8 are a different
repository's file. Only the five that mean this repo's PRINCIPLES.md were
updated.
* Every remaining bare ISSUES.md mention is a GAME repo's tracker
(MegaManX6Recomp 2/7/8/9, TombaRecomp 6), not this repo's.
* docs/disc-speed.md:4,54,56 and docs/internal/bios-hardening.md:5 name
overlay-discovery as a BRANCH, not a file, so no path applies.
INDEXES
README.md's "New here?" table gains rows for docs/WIDESCREEN.md,
docs/ENHANCEMENTS.md and docs/TCP_COMMANDS.md. docs/internal/README.md gains
sections indexing all fourteen documents that moved in, plus an "Upstream
contributor reviews" section describing upstream/ and the new sweep file, plus a
"Removed" bullet for CYCLE_TIMING_ARCH.md (deleted in the previous commit)
pointing at FAITHFUL_TIMING_PLAN.md, CYCLE_MODEL_BEETLE.md and
PRECISE_IRQ_SLICE.md -- following the SLJIT_PERSIST_CACHE.md precedent already
in that file.
docs/ENHANCEMENTS.md:7 claimed framework bugs live in "game repos' ISSUES.md
until a framework tracker exists". A framework tracker does exist; it now says
so and names the Beads epic.
VERIFICATION
A link checker was written to scan every tracked *.md for relative links,
resolving each against the tree being scanned. Baseline at origin/master: 129
files, 137 relative links, 3 broken. After both commits: 127 files, 159 relative
links, 3 broken -- the SAME three, all pre-existing and untouched here:
docs/DATA_SHARDS.md -> INSTANT_LOADS_PARKED.md (never existed)
docs/DATA_SHARDS.md -> INSTANT_LOADS_SPIKE2.md (never existed)
docs/NETPLAY.md -> ../lib/recomp-net/README.md (inside a submodule)
Zero new broken links. The checker's own coverage was checked against a raw grep
for markdown link openers: the 3-link difference is C++ lambdas inside code
blocks, correctly not treated as links.
Also verified: py_compile over all 113 tracked tools/*.py plus psxrecomp_cli.py;
all four seed JSONs still parse; the test-registration guard still reports 123
test files all registered; recompiler/tests/test_coverage_vault.py still passes;
and no file in this commit had its line endings flipped (checked mechanically
across all 50 modified files, after two files were caught and redone).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
14 KiB
Contributing to PSXRecomp
Thanks for your interest! PSXRecomp is a static recompiler, not an emulator, and it holds itself to a high correctness bar. This guide covers how the project is organized, the rules that keep it faithful, and how to get a change merged. Contributions are welcome — AI-assisted or not — as long as they are reviewed, tested, and keep the core game-agnostic.
New to the codebase? Read docs/ARCHITECTURE.md and
docs/EXECUTION_MODEL.md first, then
docs/BUILDING.md to get a build going.
Repository layout
mstan/psxrecomp(this repo) — the framework: the recompiler tool (recompiler/), the runtime engine (runtime/), shared tools, and docs. Framework changes go here.- Per-game repos (
mstan/TombaRecomp,mstan/MegaManX6Recomp,mstan/ApeEscapeRecomp, …) — one per title. Each keeps game code at the repo root and pins this framework (and usuallyrecomp-ui) as root-level submodules (psxrecomp/,recomp-ui/). Game-specific work goes in the game repo; nothing game-specific belongs in the framework.
Each game repo pins an exact framework commit — the git submodule pointer at
psxrecomp/ (some older game repos also carry a human-readable psxrecomp-v4.pin
record; the submodule pointer is the source of truth). The framework evolves on
its own cadence; a game only moves to a newer framework when someone deliberately
bumps that submodule pointer.
Starting a new title or shipping a setup-host zip? Follow
docs/GAME_PROJECT_SETUP.md (layout, CI template
under docs/ci/templates/setup-release.yml, release checklist). Also see
Linking the framework and
Framework changes and the pin below.
The core rules
These are non-negotiable because violating them is how past efforts turned into
un-debuggable emulators. The full engineering constitution is
CLAUDE.md; the debugging philosophy is
docs/internal/PRINCIPLES.md. The short version:
- Build the faithful core; no per-game hacks in the foundation. The correct
fix is the general, hardware-accurate one, not a surgical workaround for one
title. No title checks in the runtime, no magic PC addresses, no spoofed
return values for one game. (Per-game enhancements — widescreen, faster
loads — are legitimate once the faithful core is proven; see
docs/ENHANCEMENTS.md.) - No stubs. A function is fully implemented or it fails loudly. No
return 0;placeholders, no// TODObehavior, no hand-delivered fake events. If execution reaches code we can't handle, we stop and fix discovery/codegen — we don't fake the result. - Never hand-edit generated code. Files under
generated/and the recompiler's output are build artifacts. If the emitted C is wrong, fix the recompiler (recompiler/src/), then regenerate. (The one exception: a PR whose explicit purpose is to inspect or prove a generator bug.) - LLE is the baseline; HLE is a validated subsystem replacement, never a starting point. The recompiled BIOS is the reference and oracle. An HLE tier is allowed only where the LLE path has a genuine landmine, must be general (every game), must operate on the real guest structures, and must be continuously checked against the Beetle oracle — never a "produce the answer the BIOS would have" shim.
- No
printf/log-file debugging. Runtime inspection goes through the TCP debug server (docs/TCP_COMMANDS.md) and always-on ring buffers, notfprintf. - Fix broken tooling immediately — don't route around it with indirect evidence, and don't infer correctness from two implementations sharing a bug.
AI-assisted and human contributions
Both are welcome. Using AI does not reduce your responsibility for the result: review, understand, and test any code before submitting it, and don't submit a large AI-generated rewrite you can't explain or validate. A PR — however it was written — is likely to be rejected if it:
- Introduces broad, unfocused changes, or combines unrelated refactors with behavioral fixes.
- Hardcodes game-specific behavior into the framework, or adds a title check to the core runtime.
- Modifies generated output instead of fixing the generator/source.
- Introduces regressions in known-working games.
- Contains code the contributor cannot reasonably explain, or whose verification cannot be stated.
Game-specific work belongs in game repos
The framework stays game-agnostic. Per-game patches, launcher UI, assets, memory maps, metadata, config, and workarounds live in the appropriate game repo, not here. If a behavior differs by game, disc, executable, region, BIOS, or runtime configuration, it should be parameterized (config option, per-game manifest value, a parameter passed in from the game repo) or modeled as the real hardware/runtime behavior — never special-cased in the core.
Known game repos:
- TombaRecomp — Tomba!
- MegaManX6Recomp — Mega Man X6
- ApeEscapeRecomp — Ape Escape
Maintainers record evaluated external branches, game repos, exact revisions,
and reusable upstream candidates in the
ecosystem watch. Check it before porting work from
an older framework fork.
Framework changes and the pin
Because each game pins an exact framework commit (its psxrecomp/ submodule
pointer), a framework fix does not reach a game until that game bumps that
pointer. When your framework change is meant to fix or improve a specific title:
- Link the game repo in your PR (and a matching game-repo branch/commit/PR if you have one). This lets maintainers build and test the fix in its real integration environment.
- Build the game against your framework branch to prove it. The game's
CMakeLists.txtpointsPSXRECOMP_ROOTat the submodule; you can build against a working checkout via the junction/symlink dev setup, or overridePSXRECOMP_ROOTat configure time to point at your branch — no submodule surgery required. Seedocs/BUILDING.md. - Say whether a pin bump is required to consume the fix, and whether it requires a regen (codegen/emitter changes do; runtime-only changes usually don't — note which). Bumping the submodule pointer is a deliberate, reviewable change made in the game repo, not something a framework PR does on its own.
Verifying a change
Correctness is demonstrated, not asserted:
- Oracle-check against Beetle. Run
psx-beetlealongsidepsx-runtimeand compare via the shared TCP protocol; for timing/codegen, use the co-sim build. Seedocs/internal/COSIM_ORACLE.md. - Verify visually, end to end. A milestone is "the pixels appear on screen" — take a screenshot, don't trust a counter or a ring-buffer flag alone.
- A decoder / codegen change requires a playthrough check, not just a clean build: cosim/lockstep first, then run the affected area.
Automated tests — run these first
cmake -S recompiler -B recompiler/build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build recompiler/build
cd recompiler/build && ctest --output-on-failure
38 tests, under five seconds, no BIOS dump or disc image required. See
docs/TESTING.md for running individual tests, what the
suite covers, and the three known-failing tests that are deliberately not
registered.
If you add a test, register it with ctest in the same commit. An unregistered
test cannot fail, and a test that cannot fail is not a test.
Regression checklist
The automated suite does not cover whether a game still runs; only playing one does. A framework change should not break a game that worked before it. When practical, build against the known-working game repos above and confirm, for each:
- The game boots.
- The attract demo plays without issue.
- A save file can be created.
- A save file can be loaded.
- Gameplay is reachable after saving/loading.
Perfect compatibility across every game isn't required for every PR, but the PR must clearly document what was tested and on which games. Useful notes: host OS, compiler/toolchain, game repo + version/region, whether it boots / reaches gameplay / save-load works, and whether memory cards, audio, video, input, CD loading, or overlays were touched. Call out any known regression or uncertain area explicitly — a documented uncertainty is fine; a silent one is not.
No copyrighted assets
Do not submit copyrighted binary content to this repository. This includes, but is not limited to:
- PlayStation BIOS files
- Game ROMs, ISOs, BIN/CUE files, or disc images
- Extracted game assets — music, sound, textures, models, video, fonts, artwork
- Proprietary SDK files
- Decompiled copyrighted game source
Small factual references — addresses, symbols, hashes, structure descriptions, compatibility notes — are acceptable when used for tooling, debugging, or documentation. The local artifacts above are gitignored in every repo for exactly this reason (copyright + size); keep them out of commits.
Adding a new game
At a high level: create a game repo from the template, add this framework as the
psxrecomp/ submodule, seed function starts (typically from a Ghidra export in
seeds/), write game.toml, generate the game C with
psxrecomp-game --config game.toml, then build and bring the title up boot →
menu → gameplay, oracle-checking as you go. The existing game repos
(TombaRecomp, MegaManX6Recomp) are the working references.
Pull requests
Keep PRs focused — one thing: one bug, one feature, one diagnostic, one isolated refactor, or docs for one topic. Avoid combining unrelated refactors, formatting churn, and behavioral fixes in the same PR.
- Branch off
master; keep framework changes and game changes in their respective repos. - Do not commit local artifacts: BIOS images, disc images, generated C
(
generated/), memory cards, Ghidra databases, build outputs, or overlay capture files. - Explain what hardware behavior the change matches and how you verified it (oracle diff, screenshot, playthrough). Changes that can't state their verification are hard to accept.
- Keep the fix minimal and at the right layer (recompiler vs runtime vs game config). If you find yourself editing generated code or adding a stub, step back — the real fix is upstream of that.
When opening a PR, please include: what changed and why; whether it's game-agnostic or needs per-game configuration; what games/tests were run; any risks or known limitations; links to affected game repos/branches/commits when the change targets a specific game; and screenshots, logs, or traces when useful.
Bug reports
Good bug reports are extremely helpful. Please include:
- The game repo being used, and version/region if known.
- Your OS and build type/configuration.
- Steps to reproduce, expected behavior, and actual behavior.
- Logs, traces, screenshots, or video if available.
- Whether it happens in LLE (recompiled BIOS), HLE, and/or the dirty-RAM interpreter path — and in the software vs OpenGL vs Vulkan renderer, if relevant.
- Whether it's a regression from a previous commit, and the last good one if you know it.
For gameplay bugs, include the nearest save file or a reproduction path when
possible. For build failures, include gcc -v / your toolchain / the generator.
Dependencies and portability
Avoid unnecessary dependencies. When adding one, explain why it's needed and how it affects portability, build complexity, licensing, or distribution. The project should stay practical to build and run for contributors.
Style and maintainability
Prefer clear, boring, maintainable code that reads like the code around it: match the surrounding style and naming, keep changes localized, and comment non-obvious hardware behavior or timing assumptions. Prefer diagnostics that can be compiled out when not needed. Avoid cleverness unless it's clearly justified.
Licensing
By contributing, you agree your contribution is licensed under the same license as the project. Do not submit code you don't have the right to contribute, and do not copy from incompatible licenses or proprietary sources.
Upstream-derived work and credit
When a change adapts code or behavior from another branch or repository, add a
short record under docs/internal/upstream/. The record must include:
- clickable links to the source repository or PR and the exact source commit;
- the original author identity and how commit/co-author credit is preserved;
- the behavior or files actually adapted;
- related source material deliberately excluded from the focused change; and
- the validation performed after rebuilding the change on current
master.
Prefer preserving the source commit's author when a clean cherry-pick is
appropriate. For a reconstructed or partial port, add the human contributor's
exact Co-authored-by trailer and cite the source in the commit or PR body.
Attribution does not replace license compatibility or permission.
Be respectful
Contributors use different tools, workflows, and levels of AI assistance. Review the contribution, not the person. Technical criticism is welcome; personal attacks are not.
Questions / community
Development happens with the R.A.I.D. (Retro AI Development) community —
Discord invite in the README. Open a GitHub issue for bugs and
build problems (include gcc -v / OS / generator for build failures).