Controller+BETADownload

Surface support

This table describes the source checked on 6 November 2026. It covers custom surface views. It does not certify an installed app or a physical device.

All three clients draw controls in the player's finish. They use painted keys and text. They do not open a system keyboard or render HTML.

“Yes” means the code draws or handles the field. “Stored” means it accepts the field but does not use it for that task. Bug numbers refer to Known gaps.

Surface and layout

Node or option Browser controller iOS Android
v: 1, root Required. Unknown nodes are dropped. Same. Same.
Surface id Resets text, toggles and strokes, and cancels touches (held buttons let go). Resets local state and cancels touches. Resets local state and cancels touches.
turn.you Blinking LCD lamp and shell glow. Same, plus feedback on a new turn. Same, plus feedback on a new turn.
turn.name, turn.color Default turn heading and lamp/glow colour. Same. Same.
accent Keyboard submit-key colour. Selection rings keep their own colour. Same. Same.
minApp View metadata. No renderer version gate. Same. Same.
legacy Host selects a fallback before delivery. Same. Same.
row, column, children Horizontal or vertical split. First 12 children. Same. Same.
Part id Stored. Child paths locate parts. Same. Same.
weight Share of free space, 0–50. Same. Same.
Part size Fixed extent on the parent axis, 8–2000. Same. Same.
Group gap Space between children, 0–80, including updates. Yes, including updates. Yes, including updates.
spacer Empty space. No touch target. Same. Same.
Automatic MENU key On the topmost LCD; rightmost breaks a tie. Same. Same.

minApp does not replace room requires. A surface needs an LCD to get an automatic MENU key. The first surface app versions are listed in SURFACE_APPS; this table adds no new version promise.

Key shapes

keys.shape Browser controller iOS Android
round Circular rubber key. A lone key can grow to buzzer size. Same. Same.
square Rectangle with capped height. Same. Same.
tile Broad rectangle with rounded corners. Same. Same.
screen Square glass display in a rubber bezel. Same. Same.
paddle Full-height rectangle. Label, colour, badge and selected ring work. Same. Same.

All shapes handle the same key events and buttons.

Key options

Option Browser controller iOS Android
keys.keys First 32 keys, within the shared node budget. Same. Same.
keys.columns 1–8, or automatic packing, including updates. Yes, including updates. Yes, including updates.
Key id Event/toggle identity. Missing IDs become key-N. Same. Same.
label Printed on every shape. screen also supports a caption below an icon. Same. Same.
icon All names in the icon list. Same, using SurfaceGlyphs.swift too. Same.
label with icon Both on screen. Other shapes draw the icon alone. Same. Same.
color Tints round, square, tile and paddle keys. Tints screen contents. Same. Same.
fill Fills screen glass when color is valid. Ignored on other shapes. Same, including updates. Yes on screen. Ignored on other shapes.
badge Uppercase pill on screen and paddle. Trimmed to 12 UTF-16 units. Same, including updates. Same. Width is uncapped. B5.
badgeTone alert: red. good: green. Omit for dark. Same. Same.
Badge on round, square, tile Stored but not drawn. Same. Same.
selected Ring on every shape. Used initially for a toggle; otherwise follows the host. Same. Same.
disabled Dimmed; presses ignored. Same, for touch and VoiceOver. Dimmed; presses ignored.
event, payload Release on the same key sends the event and payload. Same. Same.
button Standard button 0–15 stays down while held. Same. Same.
toggle Local selection group. Same-ID views keep the local choice. Same. Same.
Non-tool value Stringified during normalization. Used as a confirm target. Same. Same.
send Sends the selected value/ID as target. Disabled until a choice exists. Same. Same.
gestures: ['hold'] Event at 450 ms. Suppresses the release event. Same. Same.
gestures: ['double'] Delays a single tap for 280 ms. Paired releases send gesture: 'double'. Same. Uses the second press to recognize the pair.
tool: 'color', tool value Selects local ink for the first canvas. Same. Same.
tool: 'width', tool value Selects local brush width, 1–24. Same. Same.
tool: 'undo' Removes every chunk of the last stroke and sends its ID. Removes all local chunks with that stroke ID. Same as iOS.
tool: 'clear' Clears local strokes and sends the first canvas's clear event. Same. Same.
Key size Brush-dot size on round width-tool keys. Does not size a key rectangle. Same. Same.
image Inline PNG/GIF/JPEG/WebP accepted. Not painted. Same. Same.

Keep IDs distinct. Use one behaviour per key. In particular, do not combine a held button with event gestures. fill needs a valid colour. Badges do not depend on fill.

LCD

Option Browser controller iOS Android
lcd Glass display with text and indicators. Same. Same.
title, text, sub Uppercase, single lines. Shrunk or squeezed to fit; clipped to glass. Same. Same.
Text wrapping or rich text Unsupported. There is no wrap, font or HTML option. Same. Same.
tone good, bad, alert tint the main text. normal and dim use normal ink. Same. Same.
timer.left, timer.total Local countdown, digits and progress bar. Same. Same.
meter.value, meter.max Segmented progress bar. Same. Same.
meter.label Visible on wide or large glass. Narrow glass shows the bar alone. Same. Same.
tips Rotate every 4.5 seconds. A nonempty sub takes priority. Same. Same.
flash.text, flash.tone, flash.id One shared flash, once per unseen ID, about 1.4 seconds. IDs survive surface changes. Same. Same.
entry Keyboard text/cursor or slots. Ignored when glass height exceeds 170 design units. Same. Same.
secret, cover Covered until held. Visual privacy only. Same. Also covers during screen capture. Covered until held; window capture policy is outside the renderer.
pages.index, pages.count Page dots when glass height is at most 170. No page-change event. Same. Same.

Use one entry LCD and one keyboard. Use one flash at a time. The timer is a display; the host decides when time runs out.

Canvas

Option or data Browser controller iOS Android
canvas, aspect Drawing paper centred in its slot, with the requested aspect ratio. Same. Same.
event Sends normalized stroke chunks with points, color, width, stroke. Same. Same.
enabled False blocks new drawing and dims the paper. Does not disable undo/clear tools. Same. Same.
clearEvent, undoEvent Tool event names. Default to clear and undo. Same. Same.
ink.value Blocks new drawing at zero. No automatic ink spending. Same. Same.
ink.max Stored. Does not set a separate visual meter. Same. Same.
Shared canvas[].points, color, width, stroke Restores when local strokes are empty. Clears when shared strokes become empty. New strokes number on from the restored ones. Same restore/clear policy. Reseeds the counter. Same as iOS.
Nonempty shared canvas replacement Does not replace existing local strokes. Same. Same.

Use one canvas. Keep authoritative drawing and ink limits on the host. Shared strokes are not a general drawing synchronizer.

Keyboard and trackpad

Node or option Browser controller iOS Android
keyboard Painted keys. One shared editor per surface. Same. Same.
language Game language, player language and English. Unsupported tags fall back to English. Same, from bundled layout data. Same, from bundled layout data.
charset letters, text, digits. Text adds spaces and punctuation. Same. Same.
maxLength Grapheme limit when Intl.Segmenter exists; otherwise code points. Swift character limit. Extended grapheme limit.
slots Fixed entry boxes. Overrides maxLength. Same. Same.
submit.event, submit.label Sends trimmed, nonempty { text }. Clears the editor. Same. Same.
Keyboard enabled False dims keys and blocks typing. Same. Same.
Layout pages, shift, RTL, Hangul and kana Handled. Chinese has pinyin letters, without Hanzi candidates. Same. Same.
Layout alt characters Available in the data API. No touch menu exposes them. This limits accented and Indic entry. Same. Same.
trackpad Relative pointer, tap, drag and two-finger gestures. Same. Same.
Trackpad event Sends { dx, dy } motion. Same. Same.
clickEvent Tap/hold-drag sends { button: 0, down }. Two-finger tap uses button 2. Same. Same.
scrollEvent Two-finger scroll sends { dx, dy }. Same. Same.
Trackpad enabled False blocks new touches. Same. Same.

Trackpads send game events. They do not grant system-control permission. See Keyboards for language limits.

Accessibility

Feature Browser controller iOS Android
Painted labels, keys and LCDs Canvases are hidden from assistive technology. No semantic control tree. B6. VoiceOver elements and key actions. Secret text stays covered. Accessibility nodes and click actions.
Badge value and selected state No semantic value/state. B6. Badge value and selected trait. Missing from nodes. B6.
Hold/double actions Touch only. B6. VoiceOver custom actions on enabled keys. Touch only. B6.

Known gaps

These are source findings. No app code was changed for this table.

Bug Finding
B1 Fixed: the browser re-lays a surface out when gap, key shape or columns change.
B2 Fixed: iOS repaints a key when its fill, badge or badge colour changes.
B3 Fixed: a new browser surface ID cancels active touches and releases held buttons (test: tests/testing-surface-swap.test.mjs).
B4 Fixed: Android cuts badges to 12 characters, as the browser and iOS do.
B5 Fixed: Android caps the badge pill at the key's width and squeezes its text.
B6 Browser surfaces lack semantic controls. Android omits badge values, selected state and gesture actions that iOS exposes.
B7 Fixed: iOS offers VoiceOver's hold and double-tap actions only on enabled keys.
B8 Fixed: browser undo removes every chunk of the last restored stroke, as the apps do.
B9 Fixed: after restoring a shared drawing, the browser numbers new strokes after the restored ones.

Source map

Browser behaviour comes from surface builders and layout (in the SDK download: src/surface.mjs), touch and editor handling (in the SDK download: web/surface.mjs), and painting (in the SDK download: web/surface-paint.mjs). surface-layout.mjs (in the SDK download: web/surface-layout.mjs) handles the separate wheel/flight controllers; custom trees use layoutSurface in src/surface.mjs.

iOS uses Sources/Surfaces/SurfaceModel.swift, SurfacePaint.swift, SurfaceGlyphs.swift, GameSurfaceView.swift, SurfaceKeyboard.swift and SurfaceTrackpad.swift.

Android uses GameSurfaceModel.kt, GameSurfacePaint.kt, GameSurfaceView.kt, SurfaceKeyboard.kt, SurfaceTrackpad.kt and SurfaceKeyGestures.kt in controller/plus.