ppsspp_step
Step the MIPS CPU forward by one instruction for instruction-level debugging. Use after pausing emulation to execute single code steps.
Instructions
PURPOSE: Step the MIPS CPU forward by ONE instruction (cpu.stepInto). USAGE: For instruction-level debugging — set a breakpoint, hit it, then step. NOT a frame-advance — one MIPS instruction is much smaller than one frame. To advance a frame's worth of execution, set a breakpoint at the start of the next frame's render and use ppsspp_resume. BEHAVIOR: Modifies emulator run state. Executes one MIPS instruction, then returns to stepping mode. Returns an error if emulation isn't currently in stepping mode (call ppsspp_pause first). RETURNS: Single line 'Stepped one instruction. PC: 0xADDR'.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/tools.ts:299-307 (registration)Registration of the 'ppsspp_step' tool — defines its name, description (purpose/usage/behavior/returns), and empty input schema (no parameters required).
{ name: "ppsspp_step", description: "PURPOSE: Step the MIPS CPU forward by ONE instruction (cpu.stepInto). " + "USAGE: For instruction-level debugging — set a breakpoint, hit it, then step. NOT a frame-advance — one MIPS instruction is much smaller than one frame. To advance a frame's worth of execution, set a breakpoint at the start of the next frame's render and use ppsspp_resume. " + "BEHAVIOR: Modifies emulator run state. Executes one MIPS instruction, then returns to stepping mode. Returns an error if emulation isn't currently in stepping mode (call ppsspp_pause first). " + "RETURNS: Single line 'Stepped one instruction. PC: 0xADDR'.", inputSchema: { type: "object", properties: {} }, }, - src/tools.ts:504-507 (handler)Handler for the 'ppsspp_step' tool — calls PPSSPP's 'cpu.stepInto' WebSocket RPC, which executes one MIPS instruction and returns the new program counter. On success, returns a single-line string showing the PC address.
case "ppsspp_step": { const r = await pp.call<{ pc?: number }>("cpu.stepInto"); return ok(`Stepped one instruction. PC: ${r.pc !== undefined ? addrHex(r.pc) : "(unknown)"}`); } - src/tools.ts:401-403 (helper)Helper function 'addrHex' used in the handler to format the program counter as a zero-padded 8-digit hex address (e.g., 0x08900000).
function addrHex(n: number): string { return `0x${n.toString(16).toUpperCase().padStart(8, "0")}`; } - src/ppsspp.ts:217-263 (helper)The PpssppClient.call() method used by the step handler — sends a ticketed request (cpu.stepInto) over WebSocket and awaits the correlated response. This is the transport mechanism for the RPC call.
/** * Send a request and wait for the ticketed response. `event` is the * PPSSPP method name (e.g. "memory.read_u16"). `params` is merged into * the request object alongside `event` and `ticket`. * * The returned object is the FULL response (including `event` and * `ticket` fields); callers usually want a specific field like * `value` or `base64` — pull it from the result. */ async call<T extends Record<string, unknown> = Record<string, unknown>>( event: string, params: Record<string, unknown> = {}, ): Promise<T> { // Auto-(re)connect on demand. PPSSPP can be launched, closed, relaunched // at any point during the MCP server's lifetime; ensureConnected() will // bring the socket back up (or throw a clear error if PPSSPP isn't // reachable). Without this, a single failed connect at MCP boot would // leave every subsequent tool call broken until MCP-client restart. await this.ensureConnected(); return new Promise<T>((resolve, reject) => { const ticket = `t${this.nextTicket++}`; const pending: PendingCmd = { ticket, resolve: (r) => resolve(r as T), reject, }; const timer = setTimeout(() => { this.inflight.delete(ticket); reject(new Error( `PPSSPP call "${event}" timed out (${this.timeoutMs}ms) — ` + `is PPSSPP running with "Allow remote debugger" enabled?`, )); }, this.timeoutMs); const origResolve = pending.resolve, origReject = pending.reject; pending.resolve = (r) => { clearTimeout(timer); origResolve(r); }; pending.reject = (e) => { clearTimeout(timer); origReject(e); }; this.inflight.set(ticket, pending); const msg = JSON.stringify({ event, ticket, ...params }); if (process.env.MCP_PPSSPP_DEBUG) { process.stderr.write(`[trace] TX: ${msg}\n`); } this.ws!.send(msg); }); } }