Root-caused the post-rebuild boot failure: 1. MSBuild's duckstation-qt target does NOT include the Qt resource compile. The separate duckstation-qt-rcc target produces build/bin/resources/duckstation-qt.rcc (~740 KB). Without it the binary shows a "duckstation-qt.rcc could not be loaded" dialog and exits. build.sh now builds both targets in one MSBuild invocation. 2. A first-time GUI launch is also required so DuckStation's setup wizard can write out a working settings.ini. Our auto-generated settings.ini (with SetupWizardIncomplete=false etc.) is insufficient — the wizard records other fields (probably Graphics API init, controller profile, achievement opt-out) that are needed for headless emulation to start. Thanks to mstanley for running the wizard interactively. After those two fixes, verified end-to-end: - `python3 tools/duckstation/launch.py -bios -nogui -fastboot` runs - port 4371 binds - read_ram 0x00079D9C returns an incrementing VSync counter - read_ram 0x000DFEE0 returns 0x8005A5BC (VBlank chain handler — patch live) - pc_break_list responds (proves my compile-but-untested pc_break family from the uncommitted additions is now tested via embedded binary) tools/duckstation/launch.py — detached-spawn wrapper (uses CREATE_NEW_PROCESS_GROUP only, no DETACHED_PROCESS so the GUI stays visible on first-run). Moved from tools/launch_ds_detached.py to live alongside setup.sh / build.sh / README.md. README updated to replace the "known issue" section with the correct recovery steps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DuckStation oracle (dynamic comparison harness)
PSXRecomp v4 uses a patched build of stenzek/duckstation as a live oracle for the recompiled BIOS. Our runtime speaks JSON-over-TCP on port 4370; the patched DuckStation speaks the same protocol on 4371, so tools like debug_client.py compare can diff live state between the two at any moment.
Layout
duckstation/(git submodule) — pristine upstream DuckStation source tree, pinned to commitffb33c281d196eb8ee0f559085ca285de7cdd51b(release-20260328 era). Never edited directly.tools/duckstation/psxrecomp_oracle.patch— our changes as a unified diff against the pinned upstream base. Touches 7 files (~1200 lines). Addssrc/core/psxrecomp_debug_server.{cpp,h}, wiresPSXRecompDebug::Initialize(4371)intoSystem::Initialize, exposes three GPU debug accessors, registers a log channel.tools/duckstation/setup.sh— idempotent. Initializes submodule, fetches + verifies + extracts prebuilt Windows deps (SDL3, Qt6, ffmpeg, …), normalizes the absolute paths baked into the prebuilt CMake metadata, applies the oracle patch.tools/duckstation/build.sh— runs CMake (Visual Studio 17 2022, x64, Release) and MSBuild onduckstation-qt. Requires the Visual Studio 2022 "Desktop development with C++" workload (CMake + MSBuild come with it).tools/fix_duckstation_deps_paths.py— helper used bysetup.shto rewrite stale_IMPORT_PREFIXvalues in the extracted prebuilt deps.
First-time setup
cd /f/Projects/psxrecomp-v4
bash tools/duckstation/setup.sh # ~5 min: clone submodule + fetch/extract deps + apply patch
bash tools/duckstation/build.sh # ~15-30 min: compile duckstation-qt Release x64
The resulting binary is at duckstation/build/bin/duckstation-qt.exe. Launch for headless oracle use:
./duckstation/build/bin/duckstation-qt.exe -bios -nogui -fastboot &
# Wait ~5s for BIOS boot, then:
echo '{"cmd":"ping"}' | ncat -w2 localhost 4371
Regenerating the patch
If our oracle changes need to be updated, edit files in duckstation/ directly, then regenerate the patch against the pinned upstream base:
cd duckstation
git diff ffb33c281d196eb8ee0f559085ca285de7cdd51b > ../tools/duckstation/psxrecomp_oracle.patch
The pinned base SHA lives in one place only: UPSTREAM_BASE at the top of tools/duckstation/setup.sh. Update it there if we ever rebase onto a newer upstream commit.
Why this layout (vs a hosted fork)
- Keeps the upstream source untracked in v4's git history — no 2.1GB of upstream code in our blame.
- Keeps our 1200-line patch reviewable as a single text diff in
tools/duckstation/— in-tree, versioned, diffable across sessions. - Matches the nestopia setup in sibling project
F:\Projects\nesrecomp\runner\nestopia_cmake.cmake+runner/nestopia_oracle.patch. Same mental model: submodule upstream, patch on top, auto-apply at setup time. - No private/public GitHub fork to maintain or keep in sync.
Protocol parity with native runtime
Both servers implement the same JSON-over-newline command set where possible so that tools/debug_client.py compare <cmd> diffs state between them. See TCP_COMMANDS.md at the v4 root for the full command table with "native-only / duckstation-only / both" annotations.
First-time setup: the duckstation-qt.rcc resource file
The MSBuild duckstation-qt target does NOT include the Qt resource compile
step. You must also build the duckstation-qt-rcc target, which produces
build/bin/resources/duckstation-qt.rcc (~740 KB). Without it the binary
opens a dialog "duckstation-qt.rcc could not be loaded. Your installation is not complete." and exits. tools/duckstation/build.sh builds both targets;
do not short-cut to -t:duckstation-qt alone.
After the rcc is in place, a first-time GUI launch is also required so
DuckStation's setup wizard can write out a working settings.ini. Launch
without any command-line flags:
python3 tools/launch_ds_detached.py # spawns GUI, no flags
Click through the wizard (BIOS directory → pick duckstation/build/bin/bios/,
then continue, then quit). After that, headless launches work forever:
python3 tools/launch_ds_detached.py -bios -nogui -fastboot
Verify with a quick ping:
NC='/c/Program Files (x86)/Nmap/ncat'
(printf '{"cmd":"ping"}\n'; sleep 1) | "$NC" localhost 4371
# expect: {"id":0,"ok":true,"frame":N} with N > 0
The settings.ini that the wizard writes is needed but not tracked (it's under
the gitignored duckstation/build/). If you rebuild from scratch (clobbering
build/), repeat the GUI-first-launch once.
Troubleshooting
- "oracle patch does not apply cleanly" — either upstream commit has been moved past the pinned base (check
git -C duckstation log --oneline -1), or the patch file was regenerated against a different base. Fix by either resetting the submodule to$UPSTREAM_BASEor updating the pin. - "sha256 mismatch" on deps archive — the prebuilt deps version in
duckstation/dep/PREBUILT-VERSIONhas been bumped. Either updatePREBUILT_SHA256insetup.shto the new hash fromduckstation/dep/PREBUILT-SHA256SUMS, or pin the submodule to an older commit that expects the cached archive's version. - "UNSUPPORTED CONFIGURATION" warning from DuckStation CMake — cosmetic. The upstream build system prefers
msbuilddriven via a VS solution (which is whatbuild.shdoes). The warning fires any time CMake runs on Windows; safe to ignore.