Developers

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.