Read this page as markdown — for coding agents, or for anyone who prefers the source.
Virtual controls
On-screen touch controls for keyboard-only games, provided by the shell.
Status: prototype, enabled for Malaga only.
The problem
A game written for desktop reads arrow keys and space. On a phone there is no keyboard, so the game renders and then does nothing. Three of our five games are in this state.
The correct fix is native touch support, which lives in the game and is the developer's job. But that is a per-developer, per-game effort, and until it happens the game is dead weight on mobile.
The mechanism
The shell draws a control pad over the game iframe and turns presses into synthetic keyboard events delivered into the game document.
This works because games are same-origin and the iframe carries
allow-same-origin, so the shell can reach iframe.contentWindow. p5 registers
its key listeners on that window and reads event.which to populate the key
state that keyIsDown() queries. It never inspects isTrusted, so a synthetic
event is indistinguishable from a real one.
One wrinkle: keyCode and which are legacy read-only properties that the
KeyboardEvent constructor ignores, yet they are exactly what input code reads.
They have to be redefined on the instance after construction.
Events are dispatched on the game's document.body rather than the window, so
they propagate the same way a real key event does and reach listeners at either
level.
The game is never modified
This is the important part. Everything runs in the shell. public/games/<id>/
stays byte-for-byte as the developer shipped it.
The one thing we depend on is the key mapping, and we never guess it — it is declared in the catalogue entry:
virtualControls: {
left: [
{ label: "◀", ariaLabel: "Sinistra", key: "ArrowLeft",
code: "ArrowLeft", keyCode: 37, mode: "hold" },
{ label: "▶", ariaLabel: "Destra", key: "ArrowRight",
code: "ArrowRight", keyCode: 39, mode: "hold" }
],
right: [
{ label: "FUOCO", ariaLabel: "Spara", key: " ",
code: "Space", keyCode: 32, mode: "hold", wide: true }
]
}
Omit virtualControls and no pad is shown. Controls only render on touch-first
devices (pointer: coarse).
Button modes
| Mode | Behaviour | Use when |
|---|---|---|
hold |
keydown on press, keyup on release | The game polls key state (keyIsDown) |
tap |
keydown then keyup after 40ms | One discrete action per press |
repeat |
keydown re-fired while held (repeatMs, default 180) |
The game fires on the discrete keyPressed callback and should autofire |
Malaga polls with its own cooldown, so hold on fire is correct. Asteroiz
shoots from keyPressed, so it will need repeat.
This is a fallback, not a standard
A pad drawn over a canvas is worse than controls designed for touch. It covers part of the play area, it has no analogue precision, and it cannot express gestures.
Native touch support therefore stays a requirement in
SUBMISSION-RULES.md. Virtual controls exist so that an
otherwise good desktop game is playable on a phone while its developer does the
real work — not so that the real work can be skipped.
There is a genuine incentive risk here: a fallback that works reduces the pressure to build the proper thing. Games with native touch should be favoured in grid placement, and virtual controls should never be presented to developers as "mobile support, done".
Known limitations
Depends on allow-same-origin. Once third-party games run under a stricter
sandbox, direct dispatch stops working. The replacement is SDK-mediated input
over postMessage, where the SDK inside the iframe generates the events on its
own window. VirtualControls.mount() / unmount() is the whole public API
precisely so that swap stays internal.
Buttons cover the canvas. No layout negotiation with the game exists.
No analogue input. Fine for arrow-key games, wrong for anything wanting a continuous axis.
Games with three simultaneous inputs are awkward. Asteroiz needs rotate, thrust and fire together; four buttons on a phone screen is playable but not good.
Testing
Synthetic dispatch is verified against p5's actual key-handling code — the
listener registration and the _downKeys[e.which] assignment were read out of
p5.min.js, not assumed. Multi-touch, independent release and key isolation all
behave correctly under jsdom.
Real-device testing on iOS Safari and Android Chrome is still required before this leaves prototype status.