Files
2026-05-27 23:29:53 +01:00

4.1 KiB

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

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

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

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.