Files
psxrecomp/DEBUG.md
Matthew Stanley 685687f36f Restructure duckstation as submodule + patch; salvage TCP docs + debug_client
Recovery + restructure after rm -rf destroyed duckstation/build/ earlier this
session. Adopts the nesrecomp pattern (see F:/Projects/nesrecomp): pristine
upstream as submodule, our changes as a patch file that gets auto-applied by a
setup script.

Submodule:
- duckstation/ now pins stenzek/duckstation@ffb33c281 (release-20260328 era)
- .gitmodules records the pin; .gitignore stops ignoring the submodule path but
  still ignores build/ and dep/prebuilt/ (the submodule's own .gitignore also
  handles these)

Patch + setup:
- tools/duckstation/psxrecomp_oracle.patch (1187 lines, 7 files) captures our
  TCP debug-server on port 4371 plus the unfinished pc_break family of commands
  (with the VirtualMemoryAddress compile fix)
- tools/duckstation/setup.sh clones submodule, fetches + verifies + extracts
  prebuilt Windows deps, rewrites stale absolute paths baked into the prebuilt
  CMake metadata, applies the oracle patch. Idempotent.
- tools/duckstation/build.sh configures CMake (VS 17 2022) + runs MSBuild, then
  provisions bin/ with portable marker, BIOS copy, and settings.ini post-fixes
- tools/duckstation/README.md documents the layout, the regeneration flow, and
  the known headless-boot configuration issue

TCP documentation (salvaged from v3):
- TCP_COMMANDS.md: full command table with native (4370) / duckstation (4371)
  / both columns, reflecting actual v4 implementation including wtrace_* and
  pc_break/pc_hit_last families
- tools/debug_client.py: unified CLI from v3 (ports 4370/4371 match v4)
- DEBUG.md: merged v3's END-TO-END COMPLETION RULE, Stale Hypothesis Rule,
  TOOLING RULE, and TOOL LIFECYCLE RULE sections into v4's existing loop

Phase 4 diagnostic artefacts from this session:
- generated/relocation_proofs/vblank_registrar/ — corrected VBlank-stall
  diagnosis. Prior handoff claimed the shell VBlank handler was never installed;
  live TCP inspection of both runtime and DuckStation oracle proved handler IS
  installed at 0x800DFEE0. Real gap is one level up in the kernel IRQ chain at
  *(0xA000E014).
- tools/duck_query*.py, duck_search.py, disasm_ram.py, scan_*.py — focused
  probes used to establish the above; reusable against either server

CLAUDE.md §2 and new §15 document the submodule-based oracle setup and the
"never edit submodule, regenerate patch instead" rule.

Known issues (tracked in tools/duckstation/README.md):
- Fresh settings.ini doesn't complete BIOS boot under -nogui; one-time launch
  with GUI is required to warm up the settings before headless mode works

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 01:34:27 -07:00

185 lines
5.8 KiB
Markdown

# DEBUG LOOP (EXECUTION CONTRACT)
Follow EXACTLY. No deviation.
---
# RULE 0 — TOOL VALIDATION (FIRST USE)
On the FIRST use of ANY tool:
You MUST validate what it is doing and its output
Required:
1. Cross-check against another source
2. Verify structure AND content
3. Confirm assumptions
Never:
- Trust output blindly
- Assume correctness
If not validated:
-> ALL reasoning is INVALID
---
# LOOP
0. Tool validation (first use only)
1. Sync state (NOT frame number)
2. Dump full state (native + oracle) via TCP debug server
3. Diff bytes
4. Find FIRST divergence
5. Trace writer (function + instruction + call path)
6. Classify (codegen / runner / timing / config)
7. Fix the tool (never generated output)
If ANY step is skipped:
-> STOP and restart
All inspection must use TCP (Phase 2+).
Before TCP exists (Phase 1), use structured JSON artifacts only.
---
# PSX-SPECIFIC INSPECTION TARGETS
When comparing native vs DuckStation oracle:
- CPU registers (32 GPRs, HI, LO, PC, COP0)
- RAM contents at known kernel addresses (PCB, TCB, EvCB tables)
- VRAM contents (byte-for-byte)
- MMIO read/write sequences
- Timer counter values
- Interrupt fire ordering
- DMA transfer sequences
- GPU command streams
---
# CLASSIFICATION
codegen:
- Wrong C emitted by strict_translator
- Fix: recompiler/src/strict_translator.cpp
runner:
- Wrong hardware simulation (MMIO, DMA, IRQ, GPU, timers)
- Fix: runtime/src/*.c
timing:
- Events fire at wrong cycle / wrong order
- Fix: runtime timing logic
config:
- Missing function seed, wrong address alias
- Fix: seed files, address_aliases.json
discovery:
- Function not found, dispatch miss
- Fix: function finder pipeline
NEVER fix generated output. Fix the tool that produced it.
---
# END-TO-END COMPLETION RULE (NON-NEGOTIABLE)
When debugging boot/runtime flows, **never accept an internal milestone as success
unless the full user-visible loop closes.**
Examples of FALSE success:
- "counter reached 0" is NOT success if the shell still retries
- "IRQ fired" is NOT success if the waiting code still loops
- "callback returned" is NOT success if the owning state machine does not advance
- "data structure written" is NOT success if the consumer still rejects it
- "handler installed" is NOT success if the VBlank counter never increments
- "chain head populated" is NOT success if pixels don't reach the screen
Required method:
1. Define full end-to-end success criteria FIRST — what does the USER see when it works?
2. Trace producer AND consumer — follow the data from origin to final consumer
3. Find the first semantic divergence — where does the consumer's view differ from expected?
4. Fix subsystem behaviour — correct the root cause in the producing subsystem
5. Rerun the full flow — verify the entire loop closes, not just the intermediate step
6. Reject any patch that only changes intermediate state without closing the loop
## Stale Hypothesis Rule
If a handoff contains stale claims contradicted by new logs, **update the problem
statement before changing code**. Never continue debugging from an invalidated
hypothesis. If there are contradictions or stale data, STOP and inform the user
immediately before proceeding.
(Seen in the 2026-04-13 VBlank stall: the handoff said "handler is never installed"
but live TCP inspection proved the handler WAS installed at `0x800DFEE0` — the real
gap was one level up, in the kernel root-counter chain head at `*(0xA000E014)`. The
entire static-analysis cascade below that stale claim was wasted effort.)
---
# TOOLING RULE (NON-NEGOTIABLE)
If at ANY point during debugging the TCP debug server cannot answer a question
you need answered — **STOP IMMEDIATELY**. Do not:
- Guess the answer
- Work around it with printf/fprintf
- Proceed with partial information
- Make assumptions based on code reading alone
Instead:
1. **STOP** all current work
2. **INFORM the user** exactly what data is missing and what command is needed
3. **ASK the user** how to proceed (build the command? change approach?)
4. **BUILD the tooling** if approved, then resume
Adding a new command is cheap — the pattern is one handler in `runtime/src/debug_server.c`
(native) and a matching handler in `duckstation/src/core/psxrecomp_debug_server.cpp`
(DuckStation oracle, then regenerate `tools/duckstation/psxrecomp_oracle.patch`).
See `TCP_COMMANDS.md` for the protocol.
A wrong answer from missing tooling costs MORE than the time to build the tool.
Every workaround becomes technical debt that hides the real bug longer.
---
# TOOL LIFECYCLE RULE (NON-NEGOTIABLE)
Claude builds, launches, and closes ALL tools. The user NEVER gatekeeps this.
- Build native: `(cd runtime && cmake --build build)` (with `PATH=/c/msys64/mingw64/bin:$PATH`)
- Build DuckStation oracle: `bash tools/duckstation/build.sh`
- Launch native: `./runtime/build/psx-runtime.exe &`
- Launch DuckStation oracle: `./duckstation/build/bin/duckstation-qt.exe -bios -nogui -fastboot &`
- Close / restart: `taskkill //F //IM psx-runtime.exe` or `//IM duckstation-qt.exe`
Do NOT ask the user to launch, close, or restart anything.
Do NOT tell the user what commands to run — just run them.
### Kill-and-rebuild procedure (Windows)
The exe file lock can persist after process exit (AV, delayed handle release).
Always use this sequence:
```bash
# 1. Kill any running instance
taskkill //F //IM psx-runtime.exe 2>/dev/null
taskkill //F //IM duckstation-qt.exe 2>/dev/null
# 2. If linker still fails with "Permission denied", delete the exe first
rm -f runtime/build/psx-runtime.exe
# 3. Rebuild
PATH=/c/msys64/mingw64/bin:$PATH
(cd runtime && cmake --build build -j)
# 4. Relaunch (one instance at a time — memory policy)
./runtime/build/psx-runtime.exe &
```
Never use `C:/msys64/msys2_shell.cmd` — it spawns orphan processes. Use `bash` with
`PATH=/c/msys64/mingw64/bin:$PATH` inline.