Skip to content

Latest commit

 

History

History
184 lines (131 loc) · 5.76 KB

File metadata and controls

184 lines (131 loc) · 5.76 KB

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

  1. Tool validation (first use only)
  2. Sync state (NOT frame number)
  3. Dump full state (native + oracle) via TCP debug server
  4. Diff bytes
  5. Find FIRST divergence
  6. Trace writer (function + instruction + call path)
  7. Classify (codegen / runner / timing / config)
  8. 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:

# 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.