# OpenSpec Change: enterprise-gaming-controller - **Change ID**: enterprise-gaming-controller - **Package**: `@suaveplan/controller` - **Category**: gaming/engine - **Target path**: `gaming/engine/controller` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/controller.md` --- ## Summary Deliver `@suaveplan/controller` on the genesis branch from scratch at enterprise quality. The package provides controller abstractions — a unified input layer that normalises keyboard, mouse, gamepad (Web Gamepad API), touch, and virtual on-screen controls into a single `ActionMap`. Actions are named bindings mapped to digital (pressed/released) and analog (axis) values. A `ControllerManager` polls inputs each tick, resolves conflicts, applies deadzone and sensitivity curves, and fires `@suaveplan/events` action events. Genesis adds Zod binding schemas, structured errors, OTel input-latency histogram, and 100% coverage. --- ## Current-state audit (genesis) Package does not exist on genesis yet — fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | `ControllerError`; `CONTROLLER.*` codes | | `@suaveplan/types` (`workspace:^`) | `Clock` | | `@suaveplan/events` (`workspace:^`) | Action-pressed/released events | | `zod` (`catalog:forms`) | Binding config schemas | | `@opentelemetry/api` (`catalog:observability`) | Input-latency histogram | --- ## Public API surface ```ts function createControllerManager(options?: ControllerManagerOptions): ControllerManager interface ControllerManagerOptions { deadzone?: number // analog deadzone 0–1, default 0.1 bindings?: ActionBinding[] clock?: Clock } interface ControllerManager { registerBinding(binding: ActionBinding): void removeBinding(action: string): void isPressed(action: string): boolean getAxis(action: string): number // -1 to 1 poll(dt?: number): void // called each game tick destroy(): void } type InputSource = 'keyboard' | 'mouse' | 'gamepad' | 'touch' | 'virtual' interface ActionBinding { action: string source: InputSource key?: string // keyboard: e.g. 'KeyW', 'Space' button?: number // gamepad button index axis?: number // gamepad axis index mouseButton?: number // 0=left, 1=middle, 2=right touchId?: number invert?: boolean sensitivity?: number } // Keyboard/mouse adapter (for environments with DOM) function createKeyboardAdapter(): InputAdapter function createGamepadAdapter(): InputAdapter function createVirtualAdapter(): InputAdapter & { setAxis(action: string, value: number): void } interface InputAdapter { readonly source: InputSource isPressed(key: string | number): boolean getAxis(index: number): number attach(): void detach(): void } ``` ### Error codes ```ts type ControllerErrorCode = | 'CONTROLLER_ACTION_NOT_FOUND' | 'CONTROLLER_DUPLICATE_BINDING' | 'CONTROLLER_INVALID_AXIS' | 'CONTROLLER_ADAPTER_ERROR' ``` --- ## New additions for genesis 1. **Multi-source conflict resolution** — when multiple sources bind the same action, the last non-zero value wins. 2. **Sensitivity curves** — linear, quadratic, and cubic response curves for analog axes. 3. **Virtual adapter** — headless adapter for touch/on-screen controls; state driven by caller (used by `@suaveplan/react-controller`). 4. **OTel** — `controller.poll_duration_ms` histogram; `controller.active_bindings` gauge. --- ## File layout ```text gaming/engine/controller/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/index.ts types/common.ts types/common.md core/ controller-manager/ controller-manager.ts controller-manager.md controller-manager.types.ts controller-manager.test.ts index.ts adapters/ keyboard-adapter/ keyboard-adapter.ts keyboard-adapter.md keyboard-adapter.test.ts index.ts gamepad-adapter/ gamepad-adapter.ts gamepad-adapter.md gamepad-adapter.test.ts index.ts virtual-adapter/ virtual-adapter.ts virtual-adapter.md virtual-adapter.test.ts index.ts utils/ sensitivity.ts sensitivity.md sensitivity.test.ts tests/ integration.test.ts ``` --- ## Impact **Downstream**: `@suaveplan/react-controller` wraps virtual adapter in React hooks; `@suaveplan/r3f-controller` drives Three.js camera/character from action map. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`.