Samir, who publishes as snuri00, posted a project on GitHub called psp-web-recomp that takes God of War: Chains of Olympus, a 2008 PlayStation Portable title, and runs it in a browser tab at 60 frames per second. No emulator in the classical sense. The game’s MIPS machine code is translated ahead of time into C++, compiled to WebAssembly, and linked against a small reimplementation of the PSP operating system and its graphics chip, which draws with WebGL2.
The HN thread dutifully argued about whether this counts as emulation. It is a fair quibble and mostly a waste of time. Dynamic interpreters translate every instruction at runtime. Static recompilation does the translation once, on the author’s machine, and ships native code. The guest binary becomes C++ translation units, 16 KiB of PSP code each, which run against a register file and a memory model rather than an interpreter loop. The distinction matters for performance: there is no runtime translation overhead left, which is why a laptop browser can hit locked 60 fps on a title that pushed the PSP’s 333 MHz MIPS core hard.
How the pipeline works
The reusable part is PSPRecomp, an MIT-licensed static recompilation framework whose first working profile was GTA: Vice City Stories. It analyzes a decrypted Allegrex/MIPS executable, recovers functions, and emits C++. psp-web-recomp contributes fixes and a web target on top. The patches matter because control flow recovery in real game code is never clean: the author had to handle code generation loops on jumps to import stubs, jumps to targets that are not translation unit entry points, and functions only reachable through pointers, found by scanning for their prologue after a return instruction. There are also VFPU fixes and the 16 KiB scratchpad mapped at 0x00010000.
Everything the recompiler cannot translate, it links against high-level emulation: PSP system calls are answered by a cooperative threading layer with semaphores, event flags, and callbacks, plus memory partitions, a file system, controller input, audio, save data, and dialogs. The file system is the clever bit for a browser target: disc data streams in over HTTP Range requests, so the game does not need to load an entire ISO up front.
Graphics get their own treatment. The PSP’s GE display lists are decoded on the main thread in software (vertex formats, skinning, lighting, texture generation, clipping) and batched triangles go to a WebGL2 backend where framebuffers become render targets keyed by their place in VRAM. The GE and its WebGL context run on a worker thread with an OffscreenCanvas, so queuing a display list returns immediately and only explicit sync points wait. That mirrors the actual PSP, where the GE was a separate processor from the CPU, and it is also why the page needs SharedArrayBuffer and cross-origin isolation headers (COOP same-origin, COEP require-corp) to work multi-threaded. Browsers that cannot do WebGL2 on an OffscreenCanvas fall back to a single thread automatically.
Audio is split the same way: sound effects come from a reimplementation of the PSP’s voice synthesizer, 32 ADPCM voices with pitch and ADSR envelopes, while music and speech are ATRAC3+ streams decoded by FFmpeg’s decoder and mixed through an AudioWorklet.
The road from 6 to 60 fps
This is the section worth reading even if you will never touch a PSP binary. The first playable build ran at 6 fps. Getting to 60 was almost entirely about removing wasted work rather than making anything faster: a thread that swapped the framebuffer twice within a single blank period was doing eight times the necessary work per displayed frame, and a shared index buffer pattern that seems natural turned out to drop Firefox to 3 fps because of how the browser revalidates buffers. Ghost of Sparta, the 2010 sequel, came up through the same scripts afterward, hitting 55 to 60 fps at three times native resolution, and needed PGD DRM decryption handled (AES-128 with three KIRK keys) plus one lighting fix.
God of War: Chains of Olympus plays from boot through menus, cutscenes, and combat at up to four times the PSP’s native 480×272. Movies are skipped. The author is careful to note both working titles run on the same engine from Ready at Dawn, so a game from a different studio will likely trip over an unimplemented syscall or GE feature. That is the honest boundary of every recompilation project: the toolkit generalizes, the per-title profiles do not.
The legal shape of it
The repository contains no game code or data. You supply a disc image of a game you own, the scripts decrypt and translate it, and the README asks that the generated C++ and WebAssembly, which are translations of someone else’s executable, stay on your own machine. No hosted demo exists; you build and serve it locally on port 8613. The project credits PPSSPP and JPCSP for documented hardware behavior and uses PPSSPP’s standalone ATRAC3 decoder under LGPL 2.1, with Emscripten doing the WebAssembly build. It is a tidy example of how these projects can stay clearly on the right side of the line while still being trivially runnable.
Worth trying if the pipeline interests you: the setup is clone, run scripts/setup.sh, point it at your own ISO with scripts/port.sh, and serve. The README’s performance section reads as a reusable checklist for any WebAssembly plus WebGL2 project, regardless of whether there is a 2008 handheld involved.