NO APP · NO ACCOUNTS · NO PAIRING
Every phone is a controller.
The game shows a code. Everyone scans it. Their phone becomes a gamepad: a d-pad, a stick, a buzzer, a steering wheel, whatever the game needs. Presses reach the game in tens of milliseconds over a direct WebSocket relay.
Three steps. One of them is "play".
1
The game opens a room
It connects to /ws/host/CODE and puts a QR code and a five-letter code on the big screen.
2
Players scan it
Their phone opens a controller in the browser, tinted in their player colour, with the layout the game asked for.
3
Mash buttons
Every change is a 16-byte binary snapshot relayed straight to the game. Holds, rolls, sticks, taps: nothing is lost or stuck.
Controllers for every kind of game
These are live. Go on, press them.
Or describe your own
Set the layout to custom and send a list of controls: buttons, tiles, pills, shoulders, buzzers, d-pads,
sticks, sliders and touchpads, each placed as a fraction of the screen with its own label and colour. The phone builds it
on the spot. Change it mid-game and every phone follows.
Edit the JSON; the preview updates. Links work too: /j?preview=custom#layout=…
For game developers
In the browser
import { ControllerHost } from "https://controller.onewheelgeek.net/sdk.js";
const host = new ControllerHost({
title: "My Game",
look: { layout: "dpad-ab", labels: { A: "JUMP", B: "FIRE" } },
});
host.on("open", (code, url) => showQr(url));
host.on("join", (pad) => host.setPad(pad.pad, { color: "#3a86ff", label: "P" + (pad.pad + 1) }));
function frame() {
for (const [n, p] of host.poll()) {
if (p.pressed & (1 << 4)) jump(n); // A, latched: never misses a tap
move(n, p.state.lx, p.state.ly); // analog, -1..1
}
requestAnimationFrame(frame);
}
Anything with a WebSocket
connect wss://controller.onewheelgeek.net/ws/host/ABCDE?key=<your secret>
send {"t":"config","title":"My Game","look":{"layout":"stick-ab"}}
receive {"t":"welcome","code":"ABCDE","joinUrl":"https://…/j/ABCDE"}
receive {"t":"join","pad":0,"device":"…"}
receive <16 bytes> kind u8 · pad u8 · seq u16 · buttons u32
lx ly rx ry i8 · px py i16 (little endian)
send {"t":"haptic","pad":0,"ms":40}
Buttons: Up 0, Down 1, Left 2, Right 3, A 4, B 5, X 6, Y 7, L 8, R 9, Start 10, Select 11. Sticks also set the d-pad bits, so a d-pad game works with any layout. Full spec: protocol.md.
Built for latency
Frames leave the phone inside the touch handler, never queued to the next paint. Binary, 16 bytes, on change only.
Next to your game
Each room is a Durable Object that lives in the Cloudflare location nearest whoever opened it: the big screen.
Nothing gets stuck
Every frame is a whole snapshot. Phones release everything when they lock or drop, and reconnect to the same seat.