15 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 release? Follow
docs/GAME_PROJECT_SETUP.md (layout, CI template
under docs/ci/templates/game-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.
Commit messages
Use a subject only by default. Add a body only when the reason is not clear from the diff, and keep it to 1-3 lines. Do not add per-file changelogs or repeat the diff or PR description.
BIOS emitter changes
After changing a BIOS fingerprint input, run
bash tools/regen_bios.sh --config bios/OpenBIOS.toml and commit the regenerated
OpenBIOS output and stamp together. PR CI checks the committed stamps without
requiring a retail BIOS dump. Never update a stamp without regenerating.
Retail-dump users must also regenerate each linked retail stem, for example
bash tools/regen_bios.sh --config bios/SCPH1001.toml. Retail dumps and generated
retail C/stamps stay local and gitignored. In the PR validation notes, name the
BIOS stems checked and whether their output changed; explicitly say when a
retail stem was not checked. Unchanged OpenBIOS output does not establish
unchanged SCPH1001 output.
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.
Runtime comments cite hardware facts to PSX-SPX, and emulators are used only as
black-box references whose outputs we compare. See
docs/PROVENANCE.md for the rules and for the passages
still to be replaced.
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).