@adatechnology/game-contracts 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -656,6 +656,8 @@ interface EventBase {
656
656
  /** Seconds since the world was created. */
657
657
  time: number;
658
658
  source?: string;
659
+ /** The kind of hit the event came out of (a shot, a blow, a cut, a blast...), when it came out of a hit; the sound of an impact is chosen by it. */
660
+ blow?: DamageType;
659
661
  }
660
662
  /** Points in events are in world space. */
661
663
  type DestructionEvent = (EventBase & {
@@ -679,7 +681,8 @@ type DestructionEvent = (EventBase & {
679
681
  exitPoint?: Vec3;
680
682
  powder?: {
681
683
  color: number;
682
- };
684
+ }; /** Joules the round still carries out of the part, when the part knows. */
685
+ energyLeft?: number;
683
686
  }) | (EventBase & {
684
687
  type: 'crack';
685
688
  part: string;
@@ -985,6 +988,40 @@ declare function blastShove(blast: BlastSource, target: {
985
988
  readonly mass: number;
986
989
  }, options?: BlastShoveOptions): BlastShove | undefined;
987
990
 
991
+ /** From this speed (m/s) a blast takes a standing body off its feet. Below it the body is only pushed. */
992
+ declare const KNOCK_DOWN_SPEED = 2.5;
993
+ /** The angle of a body lying where it fell (the same tilt a dead body takes). */
994
+ declare const KNOCKED_DOWN_ANGLE = 1.35;
995
+ /** Height of the chest above the feet: where the blast takes the body. */
996
+ declare const KNOCK_CHEST = 1.1;
997
+ /** What a blast is doing to one body: its velocity (m/s), whether it is in the air, and how far down it is (0 on its feet, 1 lying). */
998
+ interface Knock {
999
+ velocity: [number, number, number];
1000
+ airborne: boolean;
1001
+ /** 0..1 */
1002
+ lying: number;
1003
+ /** Seconds left to lie on the ground after it stopped. */
1004
+ lieLeft: number;
1005
+ /** Knocked off its feet by this blast. */
1006
+ down: boolean;
1007
+ }
1008
+ /** The mass of a body in kg from its height (m) and its build (1 = average): 70 kg at 1.75 m. */
1009
+ declare const bodyMass: (height: number, build?: number) => number;
1010
+ /**
1011
+ * What a blast does to a body standing at `feet`: the shove of `game-contracts` taken at the chest, a hop when it goes up, and a fall when it is strong enough. Undefined when the blast does not
1012
+ * reach it. The numbers are the host's (`ModuleHost.blastShove`), the same ones that throw its loose items.
1013
+ */
1014
+ declare function knockOf(blast: BlastSource, feet: Vec3, mass: number, options?: BlastShoveOptions): Knock | undefined;
1015
+ /**
1016
+ * Moves a body one step: it slides on the ground and slows, falls when it was knocked down, lies a while and gets up (a dead body stays down). Moves `position` in place; returns true when the
1017
+ * body is on its feet and still, and the knock can be dropped.
1018
+ */
1019
+ declare function stepKnock(knock: Knock, position: {
1020
+ x: number;
1021
+ y: number;
1022
+ z: number;
1023
+ }, dt: number, groundAt: (x: number, z: number) => number, alive: boolean): boolean;
1024
+
988
1025
  interface CostOptions {
989
1026
  /** Reads of `state()` and of `describe()` to time. Default 10 000. */
990
1027
  reads?: number;
@@ -1725,6 +1762,11 @@ interface AnimationEventSpec {
1725
1762
  /** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */
1726
1763
  type: string;
1727
1764
  data?: Record<string, unknown>;
1765
+ /**
1766
+ * Seconds of real time the event fires before `t`, not stretched with the clip: a recorded sound whose hit comes that long after its start
1767
+ * (the click of a magazine seating) starts early so the hit lands on `t`. The player passes the lead it could apply as `data.lead`.
1768
+ */
1769
+ lead?: number;
1728
1770
  }
1729
1771
  /** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */
1730
1772
  interface AnimationClipSpec {
@@ -1762,6 +1804,10 @@ interface SoundRequest {
1762
1804
  readonly seed?: number;
1763
1805
  /** Seconds from now (the hit of a blow after its swing); the host schedules it on the audio clock, not on a timer. */
1764
1806
  readonly delay?: number;
1807
+ /** Built now instead of waiting in the engine's deferred start queue: for a sound that has to land on the frame the player sees it (a blow's
1808
+ * contact, a heel strike, a landing). The queue builds a voice on the next frame budget and drops it after its latency, which at a low frame
1809
+ * rate is late or silent. Default false. */
1810
+ readonly urgent?: boolean;
1765
1811
  }
1766
1812
  /** What `AudioSink.play` hands back; opaque to the module, given back to `stop`. */
1767
1813
  interface SoundHandle {
@@ -2017,6 +2063,20 @@ interface Thumbnail {
2017
2063
  wear?: 0 | 1 | 2;
2018
2064
  };
2019
2065
  }
2066
+ /**
2067
+ * What an item sounds like when the hand works it, as names of sound sets the host knows (the kit's are `LEAF_FOLEY_SETS` and `PICKUP_CLASSES` of game-audio).
2068
+ * Everything is optional: without it the host picks by the material of the part.
2069
+ */
2070
+ interface ItemSounds {
2071
+ /** How it sounds picked up: `metal`, `plastic`, `glass`, `paper`, `fabric`, `wood`, `food`. Default: by its heaviest material. */
2072
+ handling?: string;
2073
+ /** The sound set of each leaf worked by hand, by part id, `*` for every other leaf: `car_door`, `car_trunk`, `fridge`, `microwave`, `drawer`, `wood_door`, `metal_door`. */
2074
+ leaves?: Readonly<Record<string, string>>;
2075
+ /** The sound id of opening its seal (`can_open`, `bottle_open`). Default: a metal seal is a can, any other a screw cap. */
2076
+ open?: string;
2077
+ /** The sound id of each use while it is held (`drink`: `drink_gulp`, `eat`: `eat_chew`), played over and over while the use lasts. */
2078
+ uses?: Readonly<Record<string, string>>;
2079
+ }
2020
2080
  /** One thing a module can put in the test room (or any viewer): a toy car, a chair, a pistol on a table. */
2021
2081
  interface TestableItem {
2022
2082
  /** snake_case id, unique inside the module. */
@@ -2045,6 +2105,8 @@ interface TestableItem {
2045
2105
  pushable?: boolean;
2046
2106
  /** Where a body sits or lies on it: the host can seat the player there. */
2047
2107
  seats?: readonly ItemSeat[];
2108
+ /** What it sounds like in the hand (picked up, opened, used) and the sound set of its doors and lids. */
2109
+ sounds?: ItemSounds;
2048
2110
  /** Building it takes a noticeable time (a vehicle, a district): the host's catalogue shows its loading indicator while it spawns one. `true`, or the text to show. */
2049
2111
  slow?: boolean | string;
2050
2112
  /**
@@ -2342,14 +2404,37 @@ interface FrameOptions {
2342
2404
  readonly order?: number;
2343
2405
  }
2344
2406
  /**
2345
- * What the host lends a module that brings its own presentation (a weapon in the hands, sounds, ejected casings). `THREE`, `scene` and `camera`
2346
- * are the host's three.js namespace, scene and camera: `unknown` here because the contracts do not depend on three.js.
2407
+ * An extra view the host draws over its own, for a module that needs to see from somewhere else at the same time: a rear-view mirror, a side mirror, a reversing camera, a security
2408
+ * monitor. The host renders the scene from `camera` into `rect` after the main view, every frame, until the returned function is called.
2347
2409
  */
2410
+ interface ModuleView {
2411
+ /** A three.js camera the module moves itself every frame. Its aspect is set by the host to the rect's. */
2412
+ camera: unknown;
2413
+ /** Where on the screen, as fractions of the canvas from its top-left corner (0..1). */
2414
+ rect: {
2415
+ readonly x: number;
2416
+ readonly y: number;
2417
+ readonly width: number;
2418
+ readonly height: number;
2419
+ };
2420
+ /** Draws it left-right flipped, as a mirror shows the world. */
2421
+ mirror?: boolean;
2422
+ /** Rounds the corners of the frame (a fraction of the shorter side, 0..0.5); 0 or absent is a plain rectangle. */
2423
+ rounded?: number;
2424
+ /** Short label the host may print on the frame. */
2425
+ label?: string;
2426
+ /** Renders it at this fraction of the canvas resolution (default 0.5): a mirror does not need full detail. */
2427
+ resolution?: number;
2428
+ }
2348
2429
  interface HostGraphics {
2349
2430
  hordeDetail(): 'auto' | number;
2350
2431
  onChange(listener: () => void): () => void;
2351
2432
  report(key: string, value: number): void;
2352
2433
  }
2434
+ /**
2435
+ * What the host lends a module that brings its own presentation (a weapon in the hands, sounds, ejected casings). `THREE`, `scene` and `camera`
2436
+ * are the host's three.js namespace, scene and camera: `unknown` here because the contracts do not depend on three.js.
2437
+ */
2353
2438
  interface ModuleHost {
2354
2439
  THREE: unknown;
2355
2440
  scene: unknown;
@@ -2428,6 +2513,15 @@ interface ModuleHost {
2428
2513
  reachFor?(reach: HostReach): void;
2429
2514
  /** Asks the tool in hand to do something (see `ToolCommand`). A host without accessories leaves it out. */
2430
2515
  command?(command: ToolCommand): ToolCommandResult;
2516
+ /**
2517
+ * Draws an extra view over the host's own (mirrors, a reversing camera). Returns the function that takes it away. A host that cannot draw more than one view leaves it out.
2518
+ */
2519
+ addView?(view: ModuleView): () => void;
2520
+ /**
2521
+ * Takes over the main view with the module's camera (a vehicle's chase, cockpit or hood camera) until it is called again with null. The player's own camera comes back then. A host
2522
+ * that keeps its camera to itself leaves it out.
2523
+ */
2524
+ setViewCamera?(camera: unknown | null): void;
2431
2525
  /** Lent only to a module that declares controls, layers or metrics. */
2432
2526
  bench?: ModuleBench;
2433
2527
  /** The host's shared sound engine; a module that makes sound uses it when lent and stays silent (or brings its own) when not. */
@@ -2447,6 +2541,12 @@ interface ModuleHost {
2447
2541
  * so that it throws things the way the host throws its loose items. Absent when the host keeps the defaults.
2448
2542
  */
2449
2543
  blastShove?: BlastShoveOptions;
2544
+ /**
2545
+ * The share (0..1) of a blast at `point` that reaches `target` through the cover between them (walls, cars, furniture of the host's world): what a module passes as
2546
+ * `BlastSource.reach` to `blastShove`, so a body behind a wall is not thrown like one in the open. `ignore` lists world objects that never block (the explosive).
2547
+ * Absent when the host has no world: the whole blast reaches.
2548
+ */
2549
+ blastReach?(point: Vec3, target: Vec3, ignore?: readonly string[]): number;
2450
2550
  /** The host's loading indicator: a module with a slow job of its own (building a house, laying a district) shows it through this instead of freezing the page. Absent when the host has none. */
2451
2551
  loading?: BusySink;
2452
2552
  }
@@ -2561,6 +2661,12 @@ interface ToolClipPlayer {
2561
2661
  readonly clip: AnimationClipSpec;
2562
2662
  }>;
2563
2663
  }
2664
+ /**
2665
+ * The clip with every event that has a `lead` moved that many seconds earlier, the lead it got in `data.lead`. A led event never goes before 0
2666
+ * nor before the event written ahead of it (a fast clip keeps its order: the lead shrinks). Run after `scaleClip`: the lead is real time, the
2667
+ * clip's stretch does not touch it.
2668
+ */
2669
+ declare const leadClipEvents: (clip: AnimationClipSpec) => AnimationClipSpec;
2564
2670
  declare const createToolClipPlayer: (options: ToolClipPlayerOptions) => ToolClipPlayer;
2565
2671
 
2566
2672
  type Entry = {
@@ -2701,4 +2807,4 @@ declare function footprintBlocker(object: FootprintObject | (() => FootprintObje
2701
2807
  /** @deprecated Import it from `@adatechnology/game-contracts/testing`. */
2702
2808
  declare const resetWarnOnce: () => void;
2703
2809
 
2704
- export { type ActionControl, type AnimationClipSpec, type AnimationEventSpec, type AnimationKey, type AnimationPose, type AreaPlacement, type AudioListener, type AudioSink, BLOW_IDS, BODY_FOLEY_IDS, BODY_SOUND_IDS, type BallisticExtras, type BenchChange, type BenchDeclaration, type BenchReport, type BenchSheet, type BlastOptions, type BlastReport, type BlastShove, type BlastShoveOptions, type BlastSource, type BlowId, type BlowTarget, type BodyFoleyId, type BodyRegion, type BodySlot, type BodySoundId, type BooleanControl, type BusyOptions, type BusySink, type BusyTask, type CategorizedOptions, type CategorizedReport, type CategoryDef, type CategoryOptions, type CategoryRegistry, type ChainDelay, type CheckOptions, type CheckSpecOptions, type ChoiceControl, type ChoiceOption, type ConfinementOptions, type ContainerSpec, type ContractChecker, type ContractContext, type ControlPatch, type ControlValue, type CostOptions, type Crack, DEFAULT_BLAST_SHOVE, DEFAULT_ENVIRONMENT, DEFAULT_IGNITION_RULES, DEFAULT_SURFACE, DEFERRED_PRIORITY, DEFERRED_QUEUE_ID, type DamageStage, type DamageType, type DebrisKind, type DeepPartial, type DeferredQueue, type DeferredQueueOptions, type DeferredQueueReport, type Dent, type DestructibleImpl, type DestructibleSpec, type DestructionEvent, type DestructionEventType, type EffectHandle, type EffectRequest, type EffectSink, type Enclosure, type EnclosureBox, type EnvironmentBus, type EnvironmentKey, type EnvironmentState, type ExplosionChain, FOOTSTEP_SOUND_IDS, FRAME_PHASES, type FluidKind, type Footprint, type FootprintObject, type FootstepSoundId, type FractureStyle, type Fragment, type FragmentHitOptions, type FragmentMark, type FrameOptions, type FramePhase, GAIT_IDS, type GaitId, type GroundLayout, type GroundZone, HEAVY_LANDING_SPEED, type HandPose, type HandTargetSource, type HandTargets, type Hit, type HitIgnition, type HitResult, type Hole, type HostEvent, type HostGraphics, type HostReach, type HostTool, type HostToolAttachment, IMPACT_CONTRACT, type IgnitionRules, type ImpactContract, type ItemAnimationContract, type ItemSeat, type ItemUseDef, type JointKind, type JointSpec, type JointState, KIT_ORIGIN_KEY, type KitOrigin, LANDING_IDS, LAND_SOUND_IDS, type LandSoundId, type LandingId, type LiquidId, type LoopHandle, MATERIAL_SURFACES, MISSING_BITS, type MaterialId, type ModuleArea, type ModuleBench, type ModuleControl, type ModuleHost, type ModuleItemFluid, type ModuleItemSeal, type ModuleLayer, type ModuleMetric, type ModuleScenario, type NumberControl, type ObjectState, type PartBreach, type PartShape, type PartSpec, type PartState, type PartStatus, type PartUnits, type PileSpec, type PlayerState, type Quat, type RayHit, type Reinforcement, STAGE_EVENT, SURFACE_IDS, SWING_LEAD_SECONDS, type SealBlock, type SealSpec, type SealState, type SegmentKind, type ShelterOptions, type Shot, type SoilLayer, type SoilProfile, type SoilState, type SoundHandle, type SoundRequest, type SpecReport, type StructureRole, type SurfaceId, type SurfaceLayer, type SurfaceRay, type SurfaceRayHit, TOOL_CLIP, TWIST_LIFT, type TensionRebar, type TestableItem, type TestableModule, type Thumbnail, type ToolAnimationStep, type ToolAnimator, type ToolClipId, type ToolClipPlayer, type ToolClipPlayerOptions, type ToolClipSet, type ToolCommand, type ToolCommandResult, type ToolKind, type ToolRig, type TunedMassDamper, type TwistOptions, type TwistStroke, type UnmarkedDrawing, type Use, type Vec3, type WarmUpProbes, type WindExposure, type WindState, applyPatches, blastShove, checkAnimation, checkBench, checkCategorized, checkDeferredQueue, checkDestructible, checkHit, checkSpec, checkToolClips, createEnvironmentBus, createToolClipPlayer, deepMerge, defineCategories, fillFactor, footprintBlocker, footstepSoundId, hitSoundId, ignitionChance, isSurfaceId, kitOriginOf, landSoundId, markKitOrigin, mergeById, removeIds, resetWarnOnce, resolveEntry, resolveIgnitionRules, sampleClip, scaleClip, sealBlock, sealOpener, shotExtras, shotToHit, sparkTargetFactor, surfaceOfMaterial, swingSoundId, toolAnimationStep, twistStroke, unmarkedDrawings, useToHit };
2810
+ export { type ActionControl, type AnimationClipSpec, type AnimationEventSpec, type AnimationKey, type AnimationPose, type AreaPlacement, type AudioListener, type AudioSink, BLOW_IDS, BODY_FOLEY_IDS, BODY_SOUND_IDS, type BallisticExtras, type BenchChange, type BenchDeclaration, type BenchReport, type BenchSheet, type BlastOptions, type BlastReport, type BlastShove, type BlastShoveOptions, type BlastSource, type BlowId, type BlowTarget, type BodyFoleyId, type BodyRegion, type BodySlot, type BodySoundId, type BooleanControl, type BusyOptions, type BusySink, type BusyTask, type CategorizedOptions, type CategorizedReport, type CategoryDef, type CategoryOptions, type CategoryRegistry, type ChainDelay, type CheckOptions, type CheckSpecOptions, type ChoiceControl, type ChoiceOption, type ConfinementOptions, type ContainerSpec, type ContractChecker, type ContractContext, type ControlPatch, type ControlValue, type CostOptions, type Crack, DEFAULT_BLAST_SHOVE, DEFAULT_ENVIRONMENT, DEFAULT_IGNITION_RULES, DEFAULT_SURFACE, DEFERRED_PRIORITY, DEFERRED_QUEUE_ID, type DamageStage, type DamageType, type DebrisKind, type DeepPartial, type DeferredQueue, type DeferredQueueOptions, type DeferredQueueReport, type Dent, type DestructibleImpl, type DestructibleSpec, type DestructionEvent, type DestructionEventType, type EffectHandle, type EffectRequest, type EffectSink, type Enclosure, type EnclosureBox, type EnvironmentBus, type EnvironmentKey, type EnvironmentState, type ExplosionChain, FOOTSTEP_SOUND_IDS, FRAME_PHASES, type FluidKind, type Footprint, type FootprintObject, type FootstepSoundId, type FractureStyle, type Fragment, type FragmentHitOptions, type FragmentMark, type FrameOptions, type FramePhase, GAIT_IDS, type GaitId, type GroundLayout, type GroundZone, HEAVY_LANDING_SPEED, type HandPose, type HandTargetSource, type HandTargets, type Hit, type HitIgnition, type HitResult, type Hole, type HostEvent, type HostGraphics, type HostReach, type HostTool, type HostToolAttachment, IMPACT_CONTRACT, type IgnitionRules, type ImpactContract, type ItemAnimationContract, type ItemSeat, type ItemSounds, type ItemUseDef, type JointKind, type JointSpec, type JointState, KIT_ORIGIN_KEY, KNOCKED_DOWN_ANGLE, KNOCK_CHEST, KNOCK_DOWN_SPEED, type KitOrigin, type Knock, LANDING_IDS, LAND_SOUND_IDS, type LandSoundId, type LandingId, type LiquidId, type LoopHandle, MATERIAL_SURFACES, MISSING_BITS, type MaterialId, type ModuleArea, type ModuleBench, type ModuleControl, type ModuleHost, type ModuleItemFluid, type ModuleItemSeal, type ModuleLayer, type ModuleMetric, type ModuleScenario, type ModuleView, type NumberControl, type ObjectState, type PartBreach, type PartShape, type PartSpec, type PartState, type PartStatus, type PartUnits, type PileSpec, type PlayerState, type Quat, type RayHit, type Reinforcement, STAGE_EVENT, SURFACE_IDS, SWING_LEAD_SECONDS, type SealBlock, type SealSpec, type SealState, type SegmentKind, type ShelterOptions, type Shot, type SoilLayer, type SoilProfile, type SoilState, type SoundHandle, type SoundRequest, type SpecReport, type StructureRole, type SurfaceId, type SurfaceLayer, type SurfaceRay, type SurfaceRayHit, TOOL_CLIP, TWIST_LIFT, type TensionRebar, type TestableItem, type TestableModule, type Thumbnail, type ToolAnimationStep, type ToolAnimator, type ToolClipId, type ToolClipPlayer, type ToolClipPlayerOptions, type ToolClipSet, type ToolCommand, type ToolCommandResult, type ToolKind, type ToolRig, type TunedMassDamper, type TwistOptions, type TwistStroke, type UnmarkedDrawing, type Use, type Vec3, type WarmUpProbes, type WindExposure, type WindState, applyPatches, blastShove, bodyMass, checkAnimation, checkBench, checkCategorized, checkDeferredQueue, checkDestructible, checkHit, checkSpec, checkToolClips, createEnvironmentBus, createToolClipPlayer, deepMerge, defineCategories, fillFactor, footprintBlocker, footstepSoundId, hitSoundId, ignitionChance, isSurfaceId, kitOriginOf, knockOf, landSoundId, leadClipEvents, markKitOrigin, mergeById, removeIds, resetWarnOnce, resolveEntry, resolveIgnitionRules, sampleClip, scaleClip, sealBlock, sealOpener, shotExtras, shotToHit, sparkTargetFactor, stepKnock, surfaceOfMaterial, swingSoundId, toolAnimationStep, twistStroke, unmarkedDrawings, useToHit };
@@ -36,6 +36,9 @@ var GameContracts = (() => {
36
36
  HEAVY_LANDING_SPEED: () => HEAVY_LANDING_SPEED,
37
37
  IMPACT_CONTRACT: () => IMPACT_CONTRACT,
38
38
  KIT_ORIGIN_KEY: () => KIT_ORIGIN_KEY,
39
+ KNOCKED_DOWN_ANGLE: () => KNOCKED_DOWN_ANGLE,
40
+ KNOCK_CHEST: () => KNOCK_CHEST,
41
+ KNOCK_DOWN_SPEED: () => KNOCK_DOWN_SPEED,
39
42
  LANDING_IDS: () => LANDING_IDS,
40
43
  LAND_SOUND_IDS: () => LAND_SOUND_IDS,
41
44
  MATERIAL_SURFACES: () => MATERIAL_SURFACES,
@@ -47,6 +50,7 @@ var GameContracts = (() => {
47
50
  TWIST_LIFT: () => TWIST_LIFT,
48
51
  applyPatches: () => applyPatches,
49
52
  blastShove: () => blastShove,
53
+ bodyMass: () => bodyMass,
50
54
  checkAnimation: () => checkAnimation,
51
55
  checkBench: () => checkBench,
52
56
  checkCategorized: () => checkCategorized,
@@ -66,7 +70,9 @@ var GameContracts = (() => {
66
70
  ignitionChance: () => ignitionChance,
67
71
  isSurfaceId: () => isSurfaceId,
68
72
  kitOriginOf: () => kitOriginOf,
73
+ knockOf: () => knockOf,
69
74
  landSoundId: () => landSoundId,
75
+ leadClipEvents: () => leadClipEvents,
70
76
  markKitOrigin: () => markKitOrigin,
71
77
  mergeById: () => mergeById,
72
78
  removeIds: () => removeIds,
@@ -80,6 +86,7 @@ var GameContracts = (() => {
80
86
  shotExtras: () => shotExtras,
81
87
  shotToHit: () => shotToHit,
82
88
  sparkTargetFactor: () => sparkTargetFactor,
89
+ stepKnock: () => stepKnock,
83
90
  surfaceOfMaterial: () => surfaceOfMaterial,
84
91
  swingSoundId: () => swingSoundId,
85
92
  toolAnimationStep: () => toolAnimationStep,
@@ -107,6 +114,59 @@ var GameContracts = (() => {
107
114
  return { speed, direction: [direction[0] / length, direction[1] / length, direction[2] / length], distance, falloff };
108
115
  }
109
116
 
117
+ // src/blast-knock.ts
118
+ var KNOCK_DOWN_SPEED = 2.5;
119
+ var KNOCKED_DOWN_ANGLE = 1.35;
120
+ var LIE_SECONDS = 1.6;
121
+ var STAND_UP_PER_SPEED = 0.25;
122
+ var FALL_SECONDS = 0.25;
123
+ var RISE_SECONDS = 0.6;
124
+ var GROUND_FRICTION = 7;
125
+ var GRAVITY = 9.81;
126
+ var KNOCK_CHEST = 1.1;
127
+ var CHEST = KNOCK_CHEST;
128
+ var bodyMass = (height, build = 1) => 70 * (height / 1.75) ** 2 * build;
129
+ function knockOf(blast, feet, mass, options) {
130
+ const hit = blastShove(blast, { position: [feet[0], feet[1] + CHEST, feet[2]], mass }, options);
131
+ if (!hit) return void 0;
132
+ const [dx, dy, dz] = hit.direction;
133
+ return { velocity: [dx * hit.speed, Math.max(0, dy * hit.speed), dz * hit.speed], airborne: dy > 0, lying: 0, lieLeft: 0, down: hit.speed >= KNOCK_DOWN_SPEED };
134
+ }
135
+ function stepKnock(knock, position, dt, groundAt, alive) {
136
+ const v = knock.velocity;
137
+ position.x += v[0] * dt;
138
+ position.z += v[2] * dt;
139
+ const ground = groundAt(position.x, position.z);
140
+ if (knock.airborne || position.y > ground + 1e-4) {
141
+ v[1] -= GRAVITY * dt;
142
+ position.y += v[1] * dt;
143
+ if (position.y <= ground) {
144
+ position.y = ground;
145
+ v[1] = 0;
146
+ knock.airborne = false;
147
+ }
148
+ } else position.y = ground;
149
+ const horizontal = Math.hypot(v[0], v[2]);
150
+ if (!knock.airborne && horizontal > 0) {
151
+ const slowed = Math.max(0, horizontal - GROUND_FRICTION * dt);
152
+ v[0] *= slowed / horizontal;
153
+ v[2] *= slowed / horizontal;
154
+ }
155
+ const moving = horizontal > 0.05 || knock.airborne;
156
+ if (knock.down) {
157
+ if (moving) knock.lying = Math.min(1, knock.lying + dt / FALL_SECONDS);
158
+ else {
159
+ knock.lying = 1;
160
+ if (alive) {
161
+ knock.lieLeft = knock.lieLeft > 0 ? knock.lieLeft : LIE_SECONDS + Math.hypot(v[0], v[2]) * STAND_UP_PER_SPEED;
162
+ knock.lieLeft -= dt;
163
+ if (knock.lieLeft <= 0) knock.down = false;
164
+ }
165
+ }
166
+ } else if (knock.lying > 0) knock.lying = Math.max(0, knock.lying - dt / RISE_SECONDS);
167
+ return !moving && !knock.down && knock.lying === 0;
168
+ }
169
+
110
170
  // src/impact-cost.ts
111
171
  var DEFAULT_COST_READS = 1e4;
112
172
  var DEFAULT_COST_CEILING_MS = 50;
@@ -650,6 +710,21 @@ var GameContracts = (() => {
650
710
  }
651
711
  return { ok: problems.length === 0, problems };
652
712
  };
713
+ var leadClipEvents = (clip) => {
714
+ const events = clip.events ?? [];
715
+ if (!events.some((event) => (event.lead ?? 0) > 0)) return clip;
716
+ let floor = 0;
717
+ const led = events.map((event) => {
718
+ const lead = Math.max(0, Math.min(event.t - floor, event.lead ?? 0));
719
+ const t = event.t - lead;
720
+ floor = Math.max(floor, t);
721
+ if (event.lead === void 0) return event;
722
+ const moved = { ...event, t, data: lead > 0 ? { ...event.data, lead } : { ...event.data } };
723
+ delete moved.lead;
724
+ return moved;
725
+ });
726
+ return { ...clip, events: led };
727
+ };
653
728
  var createToolClipPlayer = (options) => {
654
729
  const { clips, rig, onEvent } = options;
655
730
  let instances = [];
@@ -692,7 +767,7 @@ var GameContracts = (() => {
692
767
  }
693
768
  const found = find(step.clip, step.variant);
694
769
  if (!found) return;
695
- const clip = step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip;
770
+ const clip = leadClipEvents(step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip);
696
771
  const interval = options.repeatInterval?.() ?? 0.1;
697
772
  instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0);
698
773
  for (let round = 0; round < (step.repeat ?? 1); round++) {