Touch-capable browsers show a movement pad and Jump/Run buttons while playing. Drag anywhere on the world view to look; tap the contextual interaction button to use the nearby object. Movement, looking and buttons can be held with separate fingers. The existing journal, satchel, chat, audio and menu remain available.
Menu → Make yourself comfortable → Touch controls turns the layout on or off for the current visit. The initial setting uses the browser's touch/coarse-pointer capability. Keyboard bindings and standard controllers remain available. Enabling the layout on a mouse-only machine does not turn mouse drags on the pad into touch input; use browser touch emulation to inspect that path.
The controls use the existing semantic action map and input cadence. They do not write player positions or bypass server authority. Trusted rooms still admit and acknowledge ordinary movement inputs, validate interaction reach, and own durable inventory. Room credentials and saves retain their existing formats.
#Input ownership and lifecycle
packages/input/touch.ts is browser-independent. TouchInput admits one contact per movement surface, look surface or declared button, with at most eight contacts and six button names. It owns its definitions and movement origin. A radial dead zone of 0.12 removes movement-pad jitter; diagonal movement clamps to unit magnitude. Look deltas are consumed once and bounded to 128 pixels per axis per frame. Games map these device coordinates and held button names to their own actions.
start, move, end, take and reset are explicit ingress. Unknown moves/releases cannot rearm a contact. A reset requires a fresh pointer-down even if the physical finger is still held. Malformed input rejects before mutation. The player scales look deltas using its normal sensitivity setting and keeps the existing camera pitch limits.
apps/player/touch.ts owns pointer capture, DOM listeners and button presentation. Pointer cancellation or lost capture releases that contact. Menus, blur, hidden pages, room disconnection, travel/revision preparation, recovery, orientation/viewport changes and disposal clear held touch input. A controller taking over hides and releases the touch surfaces. Once the controller is neutral, a fresh touch on the world view restores touch controls; an old held finger cannot rearm movement. A blocked main thread still relies on the unchanged simulation input-expiry policy.
A secondary touch does not reliably produce a native button click. A bounded button-tap bridge lets another finger open menus or interact while a movement finger remains down. It ignores disabled controls, cancels after a twelve-pixel drag or pointer cancellation, checks the release target, and suppresses a matching duplicate native pointer click. It retains at most eight tap candidates and eight click-suppression records with a 600 ms matching window. Jump and Run also support native keyboard/assistive activation; ordinary keyboard movement remains available.
Teardown aborts this adapter's listeners, releases captured pointers, cancels activation timers and removes its overlay. No touch coordinates or contacts enter world saves. Exported playtest reports include only whether touch controls are enabled.
#Layout and verification
The brass-and-forest overlay keeps movement and jump/run controls separate from the contextual interaction and utility buttons. Touch targets are at least 44 CSS pixels. Portrait and landscape welcome layouts have distinct arrangements; dialogs retain ordinary scrolling. The local fixtures cover 320×640, 390×844 and 844×390 viewports, including welcome content, target bounds and overlap checks.
npm run check
npm test
npm run test:browser:touch
npm run test:browser:touch:trustedThe touch browser fixture uses actual Chromium touch events, observes the production worker's semantic inputs and simulation frames, and checks movement received by a second traveler. It covers simultaneous movement/look/jump/run, ordinary interaction, secondary-finger menu activation and drag cancellation, stale held fingers after pause/reconnect, pointer cancellation, blur, rotation and controller/touch handoff. The trusted fixture adds 75 ms delivery delay in each WebSocket direction, checks acknowledged server movement and shared pickup ownership, then restores the same traveler, inventory and committed position from PostgreSQL after removing local pose data.
Desktop regression checks also cover keyboard rebinding, controller play, input cadence without animation frames, checkpoint restoration and graphics recovery. Reacquiring pointer lock guards cursor recentering before the request as well as after the lock-change event, preventing a menu return from turning the camera.
See acceptance evidence. Browser emulation proves the tested event and layout paths; it does not qualify physical phone/tablet GPU performance, Safari/Firefox behavior, device keyboards, screen readers, thermal limits, safe-area behavior on actual notched hardware or long sessions. Supported-device and full accessibility gates remain open. WebGPU is still required, and the existing unsupported-browser message remains in place. No numbered engine milestone is closed by this increment.