
OpenStrikefps
A Counter-Strike-shaped shooter on 2004 hardware: BSP maps, bots, and a HUD written in Solid JSX. 60 fps, with 2.2 ms of JavaScript per frame and a worst observed frame of 9.7 ms.
Read the story →PocketJS is a portable application runtime that turns modern component code into native pixels across radically different hardware.
Write components as you already do, in Solid, Vue Vapor or Octane. Each one compiles to the same native tree and runs on QuickJS, so the framework you pick changes your code and nothing below it.
import { createSignal, Show } from "solid-js"; import { Text, View } from "@pocketjs/framework/solid/components"; export default function App() { const [count, setCount] = createSignal(0); return ( <View class="w-full h-full flex-col items-center gap-4 p-4 bg-slate-50"> <Text class="text-xl text-slate-950 font-bold">Count: {count()}</Text> <View class="px-4 py-2 rounded-xl shadow-md bg-blue-600 focus:bg-blue-500" focusable onPress={() => setCount(count() + 1)}> <Text class="text-base text-white font-bold">Press Circle</Text> </View> <Show when={count() > 3}> <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text> </Show> </View> ); }
import { ref } from "vue"; import { Text, View } from "@pocketjs/framework/vue-vapor/components"; export default function App() { const count = ref(0); return () => ( <View class="w-full h-full flex-col items-center gap-4 p-4 bg-slate-50"> <Text class="text-xl text-slate-950 font-bold">Count: {count.value}</Text> <View class="px-4 py-2 rounded-xl shadow-md bg-blue-600 focus:bg-blue-500" focusable onPress={() => { count.value++; }}> <Text class="text-base text-white font-bold">Press Circle</Text> </View> {count.value > 3 ? ( <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text> ) : null} </View> ); }
import { useState } from "octane"; import { Text, View } from "@pocketjs/framework/octane/components"; export default function App() { const [count, setCount] = useState(0); return ( <View class="w-full h-full flex-col items-center gap-4 p-4 bg-slate-50"> <Text class="text-xl text-slate-950 font-bold">{`Count: ${count}`}</Text> <View class="px-4 py-2 rounded-xl shadow-md bg-blue-600 focus:bg-blue-500" focusable onPress={() => setCount(count + 1)}> <Text class="text-base text-white font-bold">Press Circle</Text> </View> {count > 3 ? ( <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text> ) : null} </View> ); }
$ pocket create my-app $ pocket check --target psp ok 480x272 · text.glyphs.baked · input.buttons $ pocket check --target vita ok same bundle, density 2, no component edited
Animation is compiled. Keyframe timelines and spring curves are baked into the style table at build time and advanced by the Rust core on its own clock, so a page can animate with no per-frame JavaScript at all. The panel beside this runs the yui540 motion studies in WebAssembly, inside the handheld they were written for.
Press left or right on the d-pad, or the L and R shoulders, to switch studies.
Your app runs as a QuickJS guest on a Rust core that draws every pixel itself. With no browser engine in the middle, the cost of a screen is close to what the bare metal can do, whether the metal is a 333 MHz handheld or a Mac.
A whole app, drawing a smooth interface, lives in 8 MB on a single 333 MHz core. That is a quarter of what the handheld has, one part in 1536 of the phone's memory, and a core clocked 13 times slower than the one in your pocket.
That budget holds OpenStrike: a bot-populated BSP map with a Solid JSX HUD, running at 60 fps on hardware from 2004.
Classic Vue does run here, and on this screen its virtual DOM costs about six times the frame work of the same UI without one. Compiled reactivity is why the other two fit in the budget at all.
Seven samples per app, same 16.67 ms budget. These two charts come from separate runs and the toolchain got faster in between, so read each on its own terms rather than across them.
On disk the same three are 10, 9 and 242 MB, and only the pocket build runs in a single process. Left alone with a document open, it redraws about twice a second: the caret blinking, and nothing else. The published report also says where the pocket build loses, and why.
PocketJS makes time the frame counter: one frame(buttons) call is a
transaction nothing outside it can interrupt.
Nothing waits on a wall clock, so tests run as fast as the CPU allows without changing the timing they
measure: a journey that takes six seconds in front of a user is a few dozen frames in CI.
A reply that arrives partway through frame +3 is queued, not applied. It is delivered at the start of frame +4, in FIFO order, before any app hook runs. Nothing lands halfway through a transaction: no wall clock, no microtask races, no mid-frame callbacks, and after() replaces setTimeout with a deadline measured in frames.
Same app, same awaited confirmation, same assertion. Driven by requestAnimationFrame against a wall clock it lands on 22 different frames, and the timing assertion passes 9 times out of 60. On the frame clock it lands on frame 144 in every run. That spread on the left is where a flaky test lives: the assertion is really a question about a race.

SUBSAMPLED · a whole session in 13 frames at 2 Hz, byte-identical to its counterpart in the 390-frame 60 Hz run.
Because history is a data structure, hypotheticals are cheap: fork the tape at frame 9, splice in a different press, re-run the fold, and a counterfactual world exists in 22 ms. Chaos mode proves the floor holds, injecting real sleeps, allocation churn and forced GC between frames without moving the trace by one bit.
PocketJS is an application runtime with a game engine's architecture, so a game and an app are built the same way.
Cores are independent native modules, loaded the way a kernel loads drivers. An app takes the ones its content needs and the rest never enters the build. Adding one widens what your program is allowed to ask for; it never changes how your program is written.
PocketJS has booted on every operating system below, on the real machine, and what changes between them is one native submission layer, never the application. Each entry links to the post or pull request that brought it up. Keeping the hardware bootable is its own work, so the Pocket Museum project repairs these machines and keeps them running.
PocketJS carries complete applications on the machines this runtime was built for: games, a design-file viewer, a desktop companion, a streaming client, and the debugger itself. Each card links to how it was built.

A Counter-Strike-shaped shooter on 2004 hardware: BSP maps, bots, and a HUD written in Solid JSX. 60 fps, with 2.2 ms of JavaScript per frame and a worst observed frame of 9.7 ms.
Read the story →
A creature-RPG town rebuilt as a walking voxel diorama. Game state lives in the JS guest; logic runs at 60 Hz while presentation holds a locked 30 fps beat, two ticks per presented frame.
Read the story →
A real 14,430-node design file, cooked into streamed tile pyramids and panned with the analog nub at 60 fps on a handheld with 32 MB of RAM.
Read the story →
A rigged VRM companion in a transparent always-on-top window, rendering skinned 3D at 60 fps in one process and 118 MB. The Electron build of the same idea takes 8 processes and 2184 MB.
Read the story →
Search, thumbnails, playback and seeking on a console that predates streaming, where the network is a USB cable and a Mac companion does the fetching.
Read the story →
Time-travel debugging over a USB cable at 2 bytes per frame. The inspector highlight is emitted by the core into the DrawList, so it renders on the real device, on every backend.
Read the story →Pocket Lab is an independent, non-VC-backed organization built on this runtime, so that the joy of creating belongs to everyone.