/** * PhysicsActivation.ts * * Sleep/Wake activation system for character cloth and hair simulation. * Determines WHEN physics solvers run (not HOW they run), providing massive * performance savings for idle characters. * * 4-state machine: SLEEPING -> WAKING -> ACTIVE -> SETTLING -> SLEEPING * * Trigger sources: character velocity, wind force, external collision, * animation events, gravity changes. * * Integrates with: * - WeatherBlackboard (environment/WeatherBlackboard.ts) for global wind * - ClothSim (physics/ClothSim.ts) for PBD cloth * - PBDSolver (physics/PBDSolver.ts) for unified PBD pipeline * - Spring-chain hair and strand-based GPU hair (wraps around existing solvers) * * Vision: research/2026-03-26_holoscript-characters-as-code-vision.md Section 8D * Patterns: P.CHAR.005 (Physics Sleep/Wake), P.CHAR.006 (Environment Wind Zones) * Gotchas: G.CHAR.005 (no pop on wake), G.CHAR.006 (EMA smoothing for self-wind) * * @module physics */ import type { IVector3 } from './PhysicsTypes'; import type { WeatherBlackboardState } from '../environment/WeatherBlackboard'; /** * Physics activation state machine states. * * SLEEPING — Baked rest pose, zero simulation cost. * WAKING — Blending from rest pose to simulated state (0.2-0.5s). * ACTIVE — Full physics simulation running. * SETTLING — Sim continues with high damping (3-5x), capturing rest pose on sleep. */ export declare enum PhysicsActivationState { /** Baked rest pose. Zero sim cost. Hair/cloth perfectly still. */ SLEEPING = "SLEEPING", /** Smoothly blend FROM rest pose TO sim over wake_blend duration. Prevents pop. */ WAKING = "WAKING", /** Full physics simulation. PBD cloth, spring-chain hair, wind response. */ ACTIVE = "ACTIVE", /** Sim runs with cranked damping. When all vertices < epsilon, capture rest pose -> SLEEPING. */ SETTLING = "SETTLING" } /** * Types of triggers that can wake a sleeping simulation. */ export declare enum ActivationTriggerType { /** Character root bone velocity exceeds threshold */ VELOCITY = "velocity", /** Wind force on any vertex exceeds threshold */ WIND = "wind", /** External collision contact detected */ COLLISION = "collision", /** Animation state machine fires a wake event */ ANIMATION = "animation", /** Gravity vector changes */ GRAVITY = "gravity" } /** * Wind zone types for localized wind sources. */ export declare enum WindZoneType { /** Radiates from a point (e.g., fireplace updraft) */ POINT = "point", /** Directional cone (e.g., open window, vent) */ DIRECTIONAL = "directional", /** Uniform across entire scene (e.g., battlefield wind) */ GLOBAL = "global" } /** * Threshold-based trigger config (velocity, wind, gravity). */ export interface ThresholdTriggerConfig { /** Force/velocity magnitude that causes a wake from SLEEPING */ wake: number; /** Force/velocity magnitude below which the trigger is considered inactive */ sleep: number; /** How long the value must stay below `sleep` before trigger is considered cleared (seconds) */ sleepDelay: number; } /** * Collision trigger config (any contact = wake). */ export interface CollisionTriggerConfig { /** true = any collision wakes (default). false = disabled */ wake: boolean; /** Seconds after last contact before collision trigger clears */ sleepDelay: number; } /** * Animation event trigger config. */ export interface AnimationTriggerConfig { /** Animation state machine events that trigger a wake */ events: string[]; } /** * Combined trigger configuration block. * All fields are optional — if omitted, that trigger source is disabled. */ export interface ActivationTriggers { velocity?: ThresholdTriggerConfig; wind?: ThresholdTriggerConfig; collision?: CollisionTriggerConfig; animation?: AnimationTriggerConfig; gravity?: ThresholdTriggerConfig; } /** * A single point on the locomotion intensity curve. * Speed in m/s, intensity 0-1. */ export interface IntensityCurvePoint { speed: number; intensity: number; } /** * Default intensity curve from vision doc: * idle=0, walk(1.4)=0.2, jog(3.0)=0.5, run(5.0)=0.7, Sprint(8.0)=1.0 */ export declare const DEFAULT_INTENSITY_CURVE: IntensityCurvePoint[]; /** * Locomotion-driven physics intensity configuration. */ export interface LocomotionConfig { /** Enable self-wind from character movement (headwind opposing motion) */ selfWind: boolean; /** Scale factor for self-wind (0-1). 0.6 = 60% of velocity as opposing wind */ selfWindScale: number; /** Intensity curve mapping velocity to sim intensity 0-1 */ intensityCurve: IntensityCurvePoint[]; /** EMA alpha for velocity smoothing (G.CHAR.006). Lower = smoother. Default 0.1 */ emaAlpha: number; } /** * Default locomotion config. */ export declare const DEFAULT_LOCOMOTION_CONFIG: LocomotionConfig; /** * Gust cycle configuration for periodic wind surges. */ export interface GustConfig { /** Time between gusts in seconds */ interval: number; /** Peak force multiplier during gust */ strength: number; /** How long each gust lasts in seconds */ duration: number; } /** * An environment wind zone (localized or global wind source). */ export interface WindZone { /** Unique identifier */ id: string; /** Zone type */ type: WindZoneType; /** World-space position (for POINT and DIRECTIONAL) */ position?: IVector3; /** Force direction (normalized for DIRECTIONAL/GLOBAL) */ direction: IVector3; /** Base force magnitude */ force: number; /** Effective radius (for POINT zones) */ radius?: number; /** Cone half-angle in radians (for DIRECTIONAL zones) */ coneAngle?: number; /** Turbulence amount 0-1 (adds noise to force) */ turbulence: number; /** Optional gust cycle */ gust?: GustConfig; /** Whether zone is active */ enabled: boolean; } /** * Full physics activation controller configuration. * * HoloScript syntax maps directly: * ```holoscript * activation { * mode: trigger_based * rest_pose: "cape_rest.glb" * triggers { velocity: {wake: 0.1, sleep: 0.05, sleep_delay: 0.5s} ... } * wake_blend: 0.3s * settle_damping: 3.0 * settle_threshold: 0.001 * } * ``` */ export interface PhysicsActivationConfig { /** Activation mode: 'trigger_based' for sleep/wake, 'always_on' to skip activation */ mode: 'trigger_based' | 'always_on'; /** Trigger configuration */ triggers: ActivationTriggers; /** Duration of WAKING blend from rest pose to sim (seconds). Default 0.3 */ wakeBlendDuration: number; /** Damping multiplier during SETTLING state. Default 3.0 */ settleDamping: number; /** Velocity epsilon below which all vertices are considered at rest. Default 0.001 */ settleThreshold: number; /** Maximum SETTLING duration before forcing sleep (seconds). Default 2.0 */ maxSettleDuration: number; /** Locomotion-driven intensity config (optional) */ locomotion?: LocomotionConfig; } /** * Default activation config. */ export declare const DEFAULT_ACTIVATION_CONFIG: PhysicsActivationConfig; /** * Manages environment wind zones and computes aggregate wind force at a point. * Connects to WeatherBlackboard for global/ambient wind. */ export declare class WindZoneManager { private zones; private time; /** * Add or update a wind zone. */ addZone(zone: WindZone): void; /** * Remove a wind zone by ID. */ removeZone(id: string): boolean; /** * Get a wind zone by ID. */ getZone(id: string): WindZone | undefined; /** * Get all registered wind zones. */ getAllZones(): WindZone[]; /** * Advance internal time (for gust cycles). */ advanceTime(dt: number): void; /** * Get the current internal time. */ getTime(): number; /** * Compute the aggregate wind force at a world-space position, * combining all active wind zones + optional WeatherBlackboard ambient wind. * * @param worldPos - Sample point in world space * @param weather - Optional WeatherBlackboard state for ambient wind * @returns Aggregate wind force vector */ computeWindAt(worldPos: IVector3, weather?: WeatherBlackboardState): IVector3; /** * Compute a single zone's contribution at a point. */ private computeZoneContribution; } /** * Smooths a 3D velocity vector using Exponential Moving Average. * Prevents hair/cloth whip on instant direction changes (G.CHAR.006). * * EMA formula: smoothed = alpha * current + (1 - alpha) * previous * Lower alpha = smoother (more lag), higher alpha = more responsive. */ export declare class VelocitySmoother { private smoothedX; private smoothedY; private smoothedZ; private initialized; private readonly alpha; constructor(alpha?: number); /** * Update with a new raw velocity sample and return smoothed result. */ update(raw: IVector3): IVector3; /** * Get current smoothed velocity without updating. */ getCurrent(): IVector3; /** * Get the magnitude of the current smoothed velocity. */ getSpeed(): number; /** * Reset to uninitialized state. */ reset(): void; } /** * Evaluates the physics intensity (0-1) from a speed value using * the configured intensity curve (piecewise linear interpolation). */ export declare function evaluateIntensityCurve(speed: number, curve: IntensityCurvePoint[]): number; /** * Compute the self-wind vector from character velocity. * Self-wind opposes movement direction (headwind effect). * * @param smoothedVelocity - EMA-smoothed character velocity * @param scale - Self-wind scale factor (0-1) * @returns Opposing wind force vector */ export declare function computeSelfWind(smoothedVelocity: IVector3, scale: number): IVector3; /** * Per-frame input to the activation controller. */ export interface ActivationUpdateInput { /** Character root bone velocity (raw, will be EMA-smoothed internally) */ characterVelocity?: IVector3; /** External wind force at simulation position */ windForce?: IVector3; /** Current gravity vector (for gravity change detection) */ gravity?: IVector3; } /** * Per-simulation activation controller. * * One controller per cloth/hair simulation instance. Manages the 4-state * sleep/wake machine, evaluates triggers, computes locomotion intensity, * and provides blending parameters for the owning solver. * * The controller does NOT run physics itself. It tells the solver: * - Whether to simulate this frame (isSimulating()) * - What damping to use (getEffectiveDamping()) * - What wind force to apply (getEffectiveWind()) * - The current blend weight for WAKING transitions (getBlendWeight()) * - The current physics intensity from locomotion (getIntensity()) * * Usage: * ```typescript * const ctrl = new PhysicsActivationController(config); * // Each frame: * ctrl.update(dt, { characterVelocity, windForce, ... }); * if (ctrl.isSimulating()) { * solver.setDamping(ctrl.getEffectiveDamping(baseDamping)); * solver.setWind(ctrl.getEffectiveWind()); * solver.step(dt); * } * ``` */ export declare class PhysicsActivationController { private state; private config; private stateTime; private triggerStates; private velocitySmoother; private currentIntensity; private selfWindVector; private currentWindForce; private maxVertexVelocity; private previousGravity; private activeAnimationEvents; constructor(config?: Partial); /** * Get the current activation state. */ getState(): PhysicsActivationState; /** * Whether the physics solver should run this frame. * SLEEPING = false, all others = true. * If mode is 'always_on', always returns true. */ isSimulating(): boolean; /** * Get the current blend weight for WAKING transitions. * 0.0 = fully rest pose, 1.0 = fully simulated. * Returns 1.0 for ACTIVE and SETTLING states. */ getBlendWeight(): number; /** * Get the effective damping multiplier. * During SETTLING, damping is multiplied by settleDamping (3-5x). * Otherwise returns 1.0 (no modification to base damping). */ getEffectiveDampingMultiplier(): number; /** * Apply the activation damping multiplier to a base damping value. * Clamps result to [0, 1]. */ getEffectiveDamping(baseDamping: number): number; /** * Get current physics intensity from locomotion (0-1). * Controls stiffness reduction, wind multiplier, inertia response. */ getIntensity(): number; /** * Get the current self-wind vector from character locomotion. * Opposes movement direction (headwind effect). */ getSelfWind(): IVector3; /** * Get the last computed external wind force at the simulation's position. */ getWindForce(): IVector3; /** * Get the total effective wind = external wind + self-wind. */ getEffectiveWind(): IVector3; /** * Get how long the controller has been in the current state (seconds). */ getStateTime(): number; /** * Check if any trigger is currently active. */ hasActiveTrigger(): boolean; /** * Check if a specific trigger type is currently active. */ isTriggerActive(type: ActivationTriggerType): boolean; /** * Get the configuration. */ getConfig(): Readonly; /** * Notify the controller of an animation event (e.g., "jump", "attack"). * If the event matches a configured trigger, it activates. */ notifyAnimationEvent(eventName: string): void; /** * Notify the controller of a collision event. */ notifyCollision(): void; /** * Provide the maximum vertex velocity for SETTLING convergence check. * The solver should call this each frame with the max velocity across all particles. */ reportMaxVertexVelocity(velocity: number): void; /** * Force an immediate transition to a specific state. * Use with caution — typically the state machine manages transitions. */ forceState(state: PhysicsActivationState): void; /** * Main update. Call once per frame before running the physics solver. * * @param dt - Delta time in seconds * @param input - Frame input (velocity, wind, gravity) */ update(dt: number, input?: ActivationUpdateInput): void; private updateVelocityTrigger; private updateWindTrigger; private updateAnimationTrigger; private updateGravityTrigger; private updateCollisionTriggerDecay; private decayTriggerDelays; private updateLocomotion; private updateStateMachine; private transitionTo; } //# sourceMappingURL=PhysicsActivation.d.ts.map