Initial Commit
This commit is contained in:
171
openspec/changes/enterprise-gaming-animation-events/proposal.md
Normal file
171
openspec/changes/enterprise-gaming-animation-events/proposal.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# OpenSpec Change: enterprise-gaming-animation-events
|
||||
|
||||
- **Change ID**: enterprise-gaming-animation-events
|
||||
- **Package**: `@suaveplan/animation-events`
|
||||
- **Category**: gaming/rigs
|
||||
- **Target path**: `gaming/rigs/animation-events`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/animation-events.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/animation-events` on the genesis branch from scratch at enterprise quality. The package provides animation event dispatching, foot contact detection, and procedural animation modifiers. An `AnimationEventTrack` associates named events with normalized animation times (0–1); an `AnimationEventDispatcher` fires `@suaveplan/events` messages when playback crosses event timestamps. `FootContactDetector` analyses Y-velocity of ankle bones to determine ground contact frames. Procedural modifiers (head bob, breathing sway, aim offset) compose additively. Genesis adds Zod schemas, structured errors, OTel event-count counter, 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:^`) | `AnimationEventsError`; `ANIM_EVENT.*` codes |
|
||||
| `@suaveplan/types` (`workspace:^`) | `Clock` |
|
||||
| `@suaveplan/events` (`workspace:^`) | Event bus for dispatching animation triggers |
|
||||
| `@suaveplan/ik-solver` (`workspace:^`) | `solveLookAt` for aim-offset modifier |
|
||||
| `zod` (`catalog:forms`) | Track and modifier schemas |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Event dispatch counter |
|
||||
|
||||
---
|
||||
|
||||
## Public API surface
|
||||
|
||||
```ts
|
||||
// Event track
|
||||
interface AnimationEvent {
|
||||
name: string
|
||||
normalizedTime: number // 0–1
|
||||
data?: unknown
|
||||
}
|
||||
|
||||
function createEventTrack(events: AnimationEvent[]): AnimationEventTrack
|
||||
|
||||
interface AnimationEventTrack {
|
||||
getEvents(fromNorm: number, toNorm: number): AnimationEvent[]
|
||||
readonly events: readonly AnimationEvent[]
|
||||
}
|
||||
|
||||
// Dispatcher
|
||||
function createAnimationEventDispatcher(options: DispatcherOptions): AnimationEventDispatcher
|
||||
|
||||
interface DispatcherOptions {
|
||||
eventBus: EventBusLike
|
||||
namespace?: string // event name prefix, default 'anim'
|
||||
}
|
||||
|
||||
interface AnimationEventDispatcher {
|
||||
update(clipName: string, track: AnimationEventTrack, normalizedTime: number, prevNormalizedTime: number): void
|
||||
}
|
||||
|
||||
// Foot contact detection
|
||||
function createFootContactDetector(options: FootContactOptions): FootContactDetector
|
||||
|
||||
interface FootContactOptions {
|
||||
ankleThresholdY?: number // velocity below which foot is considered planted, default 0.05
|
||||
sampleWindow?: number // frames to average, default 3
|
||||
}
|
||||
|
||||
interface FootContactDetector {
|
||||
update(leftAnkleY: number, rightAnkleY: number, dt: number): FootContactState
|
||||
}
|
||||
|
||||
interface FootContactState {
|
||||
leftGrounded: boolean
|
||||
rightGrounded: boolean
|
||||
leftPlantedAt?: Vec3
|
||||
rightPlantedAt?: Vec3
|
||||
}
|
||||
|
||||
// Procedural modifiers
|
||||
interface ProceduralModifier {
|
||||
update(dt: number, input: ModifierInput): BonePose[]
|
||||
}
|
||||
|
||||
function createHeadBobModifier(options: HeadBobOptions): ProceduralModifier
|
||||
function createBreathingModifier(options: BreathingOptions): ProceduralModifier
|
||||
function createAimOffsetModifier(options: AimOffsetOptions): ProceduralModifier
|
||||
```
|
||||
|
||||
### Error codes
|
||||
|
||||
```ts
|
||||
type AnimEventErrorCode =
|
||||
| 'ANIM_EVENT_TRACK_EMPTY'
|
||||
| 'ANIM_EVENT_INVALID_TIME'
|
||||
| 'ANIM_EVENT_DISPATCH_FAILED'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
1. **Looping event detection** — dispatcher handles wrap-around (prevNorm > normTime) correctly, firing events in the wrapped range.
|
||||
2. **Foot IK plant correction** — `FootContactDetector` outputs planted world positions; caller feeds into `@suaveplan/ik-solver` for foot placement.
|
||||
3. **Aim offset modifier** — uses `solveLookAt` to rotate spine/neck toward aim target additively.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
gaming/rigs/animation-events/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
constants/index.ts
|
||||
types/common.ts
|
||||
types/common.md
|
||||
core/
|
||||
event-track/
|
||||
event-track.ts
|
||||
event-track.md
|
||||
event-track.types.ts
|
||||
event-track.test.ts
|
||||
index.ts
|
||||
dispatcher/
|
||||
dispatcher.ts
|
||||
dispatcher.md
|
||||
dispatcher.types.ts
|
||||
dispatcher.test.ts
|
||||
index.ts
|
||||
foot-contact/
|
||||
foot-contact.ts
|
||||
foot-contact.md
|
||||
foot-contact.types.ts
|
||||
foot-contact.test.ts
|
||||
index.ts
|
||||
modifiers/
|
||||
head-bob/
|
||||
head-bob.ts
|
||||
head-bob.md
|
||||
head-bob.test.ts
|
||||
index.ts
|
||||
breathing/
|
||||
breathing.ts
|
||||
breathing.md
|
||||
breathing.test.ts
|
||||
index.ts
|
||||
aim-offset/
|
||||
aim-offset.ts
|
||||
aim-offset.md
|
||||
aim-offset.test.ts
|
||||
index.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream**: `@suaveplan/r3f-animation` hooks these modifiers into Three.js AnimationMixer. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/ik-solver`.
|
||||
52
openspec/changes/enterprise-gaming-animation-events/tasks.md
Normal file
52
openspec/changes/enterprise-gaming-animation-events/tasks.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Tasks: enterprise-gaming-animation-events
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## Phase 1 — Scaffold
|
||||
|
||||
- [ ] Create `gaming/rigs/animation-events/` (`@suaveplan/animation-events`, `0.1.0`, `sideEffects: false`)
|
||||
- [ ] `tsconfig.json`, `vitest.config.ts`
|
||||
|
||||
## Phase 2 — Error codes & constants
|
||||
|
||||
- [ ] `src/error-codes.ts` + `src/constants/index.ts`
|
||||
|
||||
## Phase 3 — Types
|
||||
|
||||
- [ ] `src/types/common.ts` + `common.md`
|
||||
|
||||
## Phase 4 — Event track
|
||||
|
||||
- [ ] `src/core/event-track/event-track.ts` — sorted events, range query, wrap-around
|
||||
- [ ] Tests + docs
|
||||
|
||||
## Phase 5 — Dispatcher
|
||||
|
||||
- [ ] `src/core/dispatcher/dispatcher.ts` — normalised time delta dispatch, loop wrap, OTel counter
|
||||
- [ ] Tests + docs
|
||||
|
||||
## Phase 6 — Foot contact detector
|
||||
|
||||
- [ ] `src/core/foot-contact/foot-contact.ts` — Y-velocity windowed average, plant position tracking
|
||||
- [ ] Tests + docs
|
||||
|
||||
## Phase 7 — Procedural modifiers
|
||||
|
||||
- [ ] `src/modifiers/head-bob/head-bob.ts` — sinusoidal bob driven by movement speed
|
||||
- [ ] `src/modifiers/breathing/breathing.ts` — chest/spine sway at breathing rate
|
||||
- [ ] `src/modifiers/aim-offset/aim-offset.ts` — `solveLookAt` additive spine rotation
|
||||
- [ ] Tests + docs for all three
|
||||
|
||||
## Phase 8 — Integration tests
|
||||
|
||||
- [ ] `tests/integration.test.ts` — track events dispatched over full animation loop, foot contact + IK plant
|
||||
|
||||
## Phase 9 — Pre-publish gate
|
||||
|
||||
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/animation-events` green
|
||||
- [ ] 100% coverage
|
||||
- [ ] Publish: `bun scripts/publish.ts --package @suaveplan/animation-events`
|
||||
Reference in New Issue
Block a user