4.6 KiB
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
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
type ControllerErrorCode =
| 'CONTROLLER_ACTION_NOT_FOUND'
| 'CONTROLLER_DUPLICATE_BINDING'
| 'CONTROLLER_INVALID_AXIS'
| 'CONTROLLER_ADAPTER_ERROR'
New additions for genesis
- Multi-source conflict resolution — when multiple sources bind the same action, the last non-zero value wins.
- Sensitivity curves — linear, quadratic, and cubic response curves for analog axes.
- Virtual adapter — headless adapter for touch/on-screen controls; state driven by caller (used by
@suaveplan/react-controller). - OTel —
controller.poll_duration_mshistogram;controller.active_bindingsgauge.
File layout
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.