# OpenSpec Change: enterprise-gaming-character-physics - **Change ID**: enterprise-gaming-character-physics - **Package**: `@suaveplan/character-physics` - **Category**: gaming/rigs - **Target path**: `gaming/rigs/character-physics` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/character-physics.md` --- ## Summary Deliver `@suaveplan/character-physics` on the genesis branch from scratch at enterprise quality. The package provides engine-agnostic character controller physics using a floating capsule model: the character body levitates above the ground by a configurable spring distance, enabling smooth traversal of steps and slopes without physics tunnelling. Plugin-based architecture allows swapping physics backends (Rapier, Cannon, custom). Genesis adds structured errors, Zod option schemas, OTel frame-time histogram, injectable clock and RNG, and 100% coverage including property tests for spring convergence. --- ## Current-state audit (genesis) Package does not exist on genesis yet — fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | `CharacterPhysicsError`; `CHAR_PHYSICS.*` codes | | `@suaveplan/types` (`workspace:^`) | `Clock` | | `@suaveplan/events` (`workspace:^`) | Ground contact, jump, land events | | `zod` (`catalog:forms`) | `CharacterControllerOptions` schema | | `@opentelemetry/api` (`catalog:observability`) | Step duration histogram | --- ## Public API surface ```ts function createCharacterController(options: CharacterControllerOptions): CharacterController interface CharacterControllerOptions { height: number radius: number mass?: number springStrength?: number springDamping?: number maxSlopeAngle?: number // degrees, default 45 stepHeight?: number // max step height, default 0.3 gravityScale?: number backend?: PhysicsBackend clock?: Clock } interface CharacterController { move(velocity: Vec3): void jump(impulse: number): void teleport(position: Vec3): void update(dt: number): void readonly position: Vec3 readonly velocity: Vec3 readonly isGrounded: boolean readonly groundNormal: Vec3 | null readonly isJumping: boolean setGravityScale(scale: number): void applyImpulse(impulse: Vec3): void } interface Vec3 { x: number; y: number; z: number } // Plugin interface for physics backends interface PhysicsBackend { castRay(origin: Vec3, dir: Vec3, maxDist: number): RaycastHit | null sweep(shape: CapsuleShape, from: Vec3, to: Vec3): SweepHit | null applyForce(body: BodyHandle, force: Vec3): void } ``` ### Error codes ```ts type CharPhysicsErrorCode = | 'CHAR_PHYSICS_INVALID_HEIGHT' | 'CHAR_PHYSICS_BACKEND_NOT_SET' | 'CHAR_PHYSICS_UPDATE_FAILED' ``` --- ## New additions for genesis 1. **Spring levitation** — PD controller computes spring force each frame to maintain float distance; stable at any `dt`. 2. **Slope handling** — projects velocity onto ground normal; blocks movement up slopes exceeding `maxSlopeAngle`. 3. **Step detection** — forward ray + step-up ray distinguish steps from walls for smooth stair climbing. 4. **Backend abstraction** — `PhysicsBackend` interface decouples math from physics engine; `NullBackend` for unit tests. --- ## File layout ```text gaming/rigs/character-physics/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/index.ts types/common.ts types/common.md core/ character-controller/ character-controller.ts character-controller.md character-controller.types.ts character-controller.test.ts index.ts backends/ null-backend/ null-backend.ts null-backend.md null-backend.test.ts index.ts utils/ spring.ts spring.md spring.test.ts slope.ts slope.md slope.test.ts tests/ property.test.ts ``` --- ## Impact **Downstream**: `@suaveplan/r3f-character` wires this controller to Rapier via the backend interface. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`.