Skip to content

[FEATURE] Gamepad, action contexts, input feel helpers, local player slots and split-screen viewports #1686

Description

@Noisemaker111

Problem: no gamepad, no input contexts, no input buffering, no local players. Verified on main: zero hits for getGamepads and unadjustedMovement in packages/; packages/core/src/game/controlGate.ts is one boolean; packages/core/src/runtime/inputSnapshot.ts is one global held set; CameraKind = "splitScreen" in packages/core/src/visibility/camera.ts is unused.

Tasks, one PR each, in order:

  1. Gamepad model (core). New packages/core/src/input/gamepadModel.ts: GamepadSnapshot { id: string; axes: number[]; buttons: { pressed: boolean; value: number }[]; connected: boolean }, GamepadBindings mapping action names to pad:<buttonIndex> and padaxis:<axisIndex>[+-] codes (extend ActionBinding code parsing in packages/core/src/input/actionBindings.ts to accept these strings), resolveGamepadFrame(snapshot, bindings, { deadzone: { kind: "radial" | "axial"; inner: number; outer: number }; curve?: number }) -> { held: string[]; analog: Record<string, number> }. GamepadGlyphSet data for xbox, playstation, nintendo, generic and bindingLabel returns the glyph text for pad codes. Tests: deadzone, curve, glyphs. bun run gen. CHANGELOG Added.

  2. Gamepad source (shell). New packages/shell/src/input/gamepadSource.ts: polls navigator.getGamepads() in useFrame, merges the resolved frame into the tracker/analog refs FrameDriver reads, and exposes rumble(userId, { strong, weak, ms }) on ctx.input via vibrationActuator.playEffect. Mount it in GamePlayerShell.tsx. Since Playwright cannot attach a pad, add a synthetic GamepadSnapshot injector (?gamepad=1 dev overlay) and a test on the pure merge.

  3. Action contexts. New packages/core/src/input/actionContexts.ts: createActionContextStack() with push({ id; codes: ActionCodesMap; passthrough: boolean }), pop(id), active() -> ActionCodesMap (merged top-down until a non-passthrough), snapshot, restore. Replace the boolean in controlGate.ts with a menu context push, keeping playControlsActive as a wrapper. Wire the tracker in packages/shell/src/input/ to read active(). Tests.

  4. Input feel helpers. New packages/core/src/input/inputBuffer.ts: createInputBuffer({ windowMs }) with press(action, nowMs), consume(action, nowMs) -> boolean, coyote(groundedAtMs, nowMs, windowMs) -> boolean, hold(action, nowMs) -> ms held, doubleTap(action, nowMs, gapMs), snapshot/restore. Consume it in packages/core/src/movement/playerMovement.ts for jump (buffered jump and coyote time behind movement.jumpBufferMs / coyoteMs, default 0 so behavior is unchanged). Tests.

  5. Raw mouse. In packages/shell/src/input/mouseLook.ts request requestPointerLock({ unadjustedMovement: true }) with a fallback to the plain call when the promise rejects.

  6. Local player slots. New packages/core/src/runtime/localPlayers.ts: createLocalPlayers({ maxSlots }) with assign(deviceId) -> slotId (hot-join on first input), slots(), one InputSnapshot per slot, snapshot/restore. ctx.player stays the primary; add ctx.players.local(slotId) returning { userId; input }. The game loop spawns one entity per slot through onNewPlayer. Tests.

  7. Split-screen viewports. New packages/core/src/game/viewports.ts: ViewportDef { slot: string; rect: [x, y, w, h] (0..1); camera: GameCameraConfig }; packages/shell/src/camera/Viewports.tsx renders each viewport with gl.setViewport/setScissor in a useFrame at render priority, one camera rig per slot following that slot's entity, and a HUD root per viewport. Add viewports?: ViewportDef[] to the presentation config. Shots of two viewports.

  8. Probe. Two-pad couch example under examples/ with hot-join. Closes #1686.

Rules: wait for bun run agent:bootstrap --check; branch claude/<slug> off origin/main; claim comment first; one task per PR with Refs #1686; bun run gen after export changes (JSDoc on every export; create* factories need snapshot/restore); CHANGELOG bullet; check-types and test on core, shell, react as touched plus check-stateful-ratchet, check-doc-symbols, check-orphan-ratchet; merge origin/main before pushing; shots via bun run shoot; squash auto-merge; fix CI on the same branch.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions