Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
# enterprise-controller
**Package**: `@suaveplan/controller`
**Status**: PROPOSED
**Type**: new-package (migration from `../suaveplan/packages/universal/controller/`)
- **Design**: ./design.md
- **Spec delta**: ./specs/controller/spec.md
## Why
Genesis lacks a universal-tier input controller. The upstream `@suaveplan/controller` package
(67 files, ~5000 LoC) provides a unified `ControllerInput` shape across gamepad, keyboard, mouse,
touch, plus pattern recognition and input buffering. Games built on `@suaveplan/ecs` and the
forthcoming `@suaveplan/game-server` need a deterministic, injectable input layer that does not
read wall-clock time directly and can be exercised under a `FakeClock`.
The upstream code uses `Date.now()` throughout, mutates module-level state, and lacks structured
errors, Zod boundary validation, and OTel instrumentation. This migration re-implements the
public surface with genesis primitives (`Clock`/`IdGenerator` injection, `defineCodes`, OTel
counter + histogram, Zod schemas) while preserving the `ControllerInput` shape, the
`BaseController` lifecycle, the `ControllerMerger` semantics, and the device adapters.
## What changes
- ADDED: `BaseController` — abstract base with state, thresholds, deadzone/threshold logic,
pollRate gating, lifecycle, event emission, `Clock` injection.
- ADDED: `KeyboardController` — DOM keyboard adapter with configurable key→input mapping.
- ADDED: `GamepadController` — `navigator.getGamepads()` polling adapter with axis deadzone.
- ADDED: `ControllerMerger` — merges N child controllers into a single `ControllerInput`
state via max-value reduction with stable iteration order.
- ADDED: `InputEventEmitter` — typed event dispatcher with `onInput`/`onStateChange`/`onCombo`
and listener disposal.
- ADDED: `ControllerError` + `CONTROLLER_CODES` — stable error-code registry via `defineCodes`.
- ADDED: OTel `controller_inputs_total` counter (tags: `device`, `input`) and
`controller_pattern_match_duration_ms` histogram, no-op without exporter.
- ADDED: Zod-validated public input boundaries (options, snapshots, patterns).
- ADDED: Sibling `.md` (≥200 words, 6 sections) for every implementation file.
## Out of scope (Priority 2 — Deferred 2026-05-24)
Per the migration brief's explicit allowance ("ship the input-device adapters + controller
merger as Priority 1 and defer pattern-recognition + input-buffering"), the following upstream
modules are deferred to a follow-up change:
- `TouchController` — touch event adapter (Priority 2; requires DOM `Touch` polyfills in test).
- `MouseController` — 1296-line upstream module requires structural decomposition into
≤300-line genesis modules per CLAUDE.md §3; deferred to Priority 2.
- `InputBuffer` — circular buffer for combo sequences (Priority 2; pattern-recognition layer).
- `InputStateMachine` — pattern-recognition state machine (Priority 2).
- `ModalController` — input-mode router (Priority 2).
- Default `defaultPatterns` constant (hadoken / shoryuken / charge-shot / rapid-fire) — moves
to Priority 2 with the pattern engine.
- `demo/`, `test-utils/`, `e2e/` — non-shipping; demo + integration harnesses are downstream
concerns and not part of the published surface.
Justification for deferral (≤1 sentence per upstream §0a anti-patterns rule): every deferred
module depends on a co-shipped component that is itself deferred or on a structural
decomposition that requires its own design review — pattern recognition + input buffering
form one cluster (combo grammar + buffer + state machine) that should land atomically, and
the mouse rewrite needs its own 1296→multi-file split design.
## Source mapping
This change derives from `../suaveplan/packages/universal/controller/`. There is no upstream
`enterprise/controller.md` audit document; this section maps to upstream source files instead.
| Requirement | Source | Notes |
|---|---|---|
| ControllerInput shape | `src/types.ts` L1-L66 | verbatim 32-field shape |
| BaseController lifecycle | `src/base/base-controller.ts` L1-L386 | re-authored to inject `Clock` instead of `Date.now()`; threshold logic preserved |
| KeyboardController | `src/keyboard/keyboard-controller.ts` L1-L226 | re-authored; DOM event listeners + key map |
| GamepadController | `src/gamepad/gamepad-controller.ts` L1-L315 | re-authored; `navigator.getGamepads()` polling |
| ControllerMerger | `src/controller-merger.ts` L1-L298 | re-authored; max-value reduction preserved |
| InputEventEmitter | `src/input/input-event-emitter.ts` L1-L115 | re-authored; typed listener registry |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| `defineCodes` error registry | Genesis convention — upstream threw plain `Error` |
| OTel instrumentation | Genesis convention — upstream had no observability |
| Zod boundary schemas | Genesis convention — upstream had no runtime boundary validation |
| `Clock` injection | Genesis convention — upstream read `Date.now()` directly |
## Verification approach
- `src/base/base-controller.test.ts` — lifecycle, threshold, deadzone, poll-rate gating.
- `src/keyboard/keyboard-controller.test.ts` — key→input mapping, focus loss cleanup.
- `src/gamepad/gamepad-controller.test.ts` — axis deadzone, multi-pad fanout.
- `src/controller-merger/controller-merger.test.ts` — max-value reduction, add/remove.
- `src/event-emitter/event-emitter.test.ts` — listener add/remove/dispose.
- `src/errors/errors.test.ts` — every code in `CONTROLLER_CODES` reachable.
- `src/observability/observability.test.ts` — counter + histogram emit under in-memory meter.
### Affected dependents
None — additive, new package.