Report emulated time in cpu.status, and note headless/wsdbg traps in AGENTS.md

cpu.status only reported raw CPU ticks, which a client can't turn into a time:
the PSP's clock frequency is changeable and games do change it, so the
ticks-per-second ratio isn't fixed over a run. CrossCraft Classic runs at
333MHz, so assuming the default 222MHz reads 9.1s where the truth is 6.3s -
enough to put scripted input injection in the wrong place entirely.

Adds "us" (emulated microseconds) and "clockHz" alongside "ticks".
CoreTiming::GetGlobalTimeUs() can't be used directly for this: it rebases its
own internal counters as a side effect, and cpu.status is deliberately served
straight from the WebSocket thread rather than queued to the CPU thread (it's
meant to be cheap and frequently pollable). So PeekGlobalTimeUs() computes the
same value without the rebasing.

AGENTS.md picks up the things that cost time while driving headless over the
websocket API: --sync silently desynchronises on raw JSON lines because only
wsdbg's key=value shorthand gets a ticket; headless reports HAS_DEBUGGER as
false so anything gated on it silently does nothing there; the memstick is
hardcoded next to the executable; a leftover headless process turns a build
into an LNK1168 that looks like a compile error; wrapping the launcher in
`timeout` kills the emulator along with it, losing the crash you stopped at;
response field names aren't uniform (value vs uintValue); broadcast.config.set
rejects two of the four keys the docs list.

Also writes down the ELF-as-oracle technique that cracked the relocation bug -
when homebrew ships app.elf next to app.prx, the pre-link ELF still has the
symbols and the relocation symbol indices the PRX format discards, so the
loader's output can be checked exhaustively offline instead of by re-running.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-08-17 18:51:54 +02:00
1 parent 05f5668dfe
commit 59eb8a9a81
5 files changed
+54 -1

No files matched your search

@@ -112,6 +112,11 @@ void WebSocketCPUResume(DebuggerRequest &req) {
// - paused: boolean, CPU paused or not started yet.
// - pc: number value of PC register (inaccurate unless stepping.)
// - ticks: number of CPU cycles into emulation.
// - us: microseconds of emulated time. Not derivable from ticks on the client side: the PSP's
// clock frequency is changeable (scePowerSetCpuClockFrequency), and games do change it, so the
// ticks-per-second ratio isn't fixed over a run. This is what to use to answer "how far into
// the game am I", e.g. to line up input injection with a wall-clock repro.
// - clockHz: the CPU clock frequency right now, for reference.
// Deliberately not routed through Core_RunOnCPUThread(): this is meant to be a cheap, frequently-pollable
// status check, and the "pc" field is already documented as inaccurate unless stepping - queuing it would
// add up to a frame of latency to every poll for no real accuracy benefit. Matches how SteppingBroadcaster
@@ -127,6 +132,8 @@ void WebSocketCPUStatus(DebuggerRequest &req) {
json.writeUint("pc", pspInited ? currentMIPS->pc : 0);
// A double ought to be good enough for a 156 day debug session.
json.writeFloat("ticks", pspInited ? CoreTiming::GetTicks(currentMIPS) : 0);
json.writeFloat("us", pspInited ? (double)CoreTiming::PeekGlobalTimeUs() : 0);
json.writeUint("clockHz", pspInited ? CoreTiming::GetClockFrequencyHz() : 0);
}
// Retrieve all regs and their values (cpu.getAllRegs)