@adatechnology/game-contracts 0.23.0 → 0.25.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
@@ -92,6 +92,8 @@ interface PartSpec {
92
92
  radius: number;
93
93
  /** Inside this radius (metres) the blast is lethal to a body; from here to `radius` it only hurts, less with distance. Without it a body takes the energy falling off over `radius`. */
94
94
  lethalRadius?: number;
95
+ /** Kilograms of TNT equivalent of the burst: the blast wave that pushes bodies is read from it (`knockOf`). */
96
+ tnt?: number;
95
97
  atC?: number;
96
98
  /**
97
99
  * Chain reaction: what sets the part off before it is destroyed and the seconds from that to the blast (a number, or `[min, max]` drawn
@@ -757,6 +759,7 @@ type DestructionEvent = (EventBase & {
757
759
  energy: number;
758
760
  radius: number;
759
761
  lethalRadius?: number;
762
+ tnt?: number;
760
763
  effect?: string;
761
764
  })
762
765
  /** An explosive part was set off by a bullet, fire or a nearby blast and will explode `delay` seconds from now (`explodes.chain`). */
@@ -832,6 +835,8 @@ type DestructionEvent = (EventBase & {
832
835
  * One unit of a wall of units (`PartSpec.units`: a brick, a block) came out under shots or blows: `center` in world space, `size` in the part's own
833
836
  * axes and `rotation` the part's world rotation (the box of the unit that left), `fragments` the unit itself. The part stays: draw the gap, do not hide it.
834
837
  * With `torn`, nothing left the part but what a blow tore off its face (splinters of wood, lumps of MDF): the hole and the marks around it stay drawn.
838
+ * With `units`, a blast knocked many units out at once: each one's box (`center` in world space, `size` in the part's axes, turned by `rotation`), and
839
+ * `center` and `size` are the box around them all.
835
840
  */
836
841
  | (EventBase & {
837
842
  type: 'chip';
@@ -841,6 +846,10 @@ type DestructionEvent = (EventBase & {
841
846
  rotation: Quat;
842
847
  fragments: readonly Fragment[];
843
848
  torn?: true;
849
+ units?: readonly {
850
+ center: Vec3;
851
+ size: Vec3;
852
+ }[];
844
853
  })
845
854
  /** Rebar of a reinforced part is bare: segments in world space. Empty `bars` means the cage is gone. */
846
855
  | (EventBase & {
@@ -969,6 +978,13 @@ interface BlastSource {
969
978
  readonly energy: number;
970
979
  readonly radius: number;
971
980
  readonly reach?: number;
981
+ /**
982
+ * Kilograms of TNT equivalent of the charge. With it a body is pushed by the blast wave itself (`blast-impulse`), and the shove of loose items keeps this module's rule; without it the body
983
+ * takes the charge from `energy` by `BlastImpulseOptions.tntPerJoule`.
984
+ */
985
+ readonly tnt?: number;
986
+ /** Inside this radius (metres) the blast is lethal: a body there that the blast reaches (not behind cover) goes down, wounded, wherever the wave pushed it. */
987
+ readonly lethalRadius?: number;
972
988
  }
973
989
  /** What a blast does to one body: how fast it leaves (m/s), the unit direction it leaves in, and how far from the centre it was. */
974
990
  interface BlastShove {
@@ -980,7 +996,7 @@ interface BlastShove {
980
996
  }
981
997
  /**
982
998
  * The shove a blast gives to a body of `mass` kg whose centre is at `position`: away from the centre, lifted a little, with the speed
983
- * `min(maxSpeed, sqrt(2 * energy * reach * coupling / mass)) * (1 - distance / radius) ^ 2`. Undefined when the body is outside the radius or the speed is under `minSpeed`.
999
+ * `sqrt(reach) * min(maxSpeed, sqrt(2 * energy * coupling / mass)) * (1 - distance / radius) ^ 2`. Undefined when the body is outside the radius or the speed is under `minSpeed`.
984
1000
  * The same rule moves the loose items and furniture of the test room and the people of the characters module, so a game that tunes the options tunes all of them.
985
1001
  */
986
1002
  declare function blastShove(blast: BlastSource, target: {
@@ -988,10 +1004,45 @@ declare function blastShove(blast: BlastSource, target: {
988
1004
  readonly mass: number;
989
1005
  }, options?: BlastShoveOptions): BlastShove | undefined;
990
1006
 
1007
+ /**
1008
+ * The push a blast gives a standing body, from the physics of the blast wave and not from a game coupling: the impulse of the wave per square metre at a distance from a charge of
1009
+ * `tnt` kg of TNT equivalent (Kinney and Graham's fit of the side-on impulse, scaled by the cube root of the charge), reflected on the body, over the area the body shows the wave,
1010
+ * divided by its mass. A hand grenade at 3 m gives a person a few tenths of a metre per second; what floors that person is the fragments, not the wave. A big charge close throws.
1011
+ */
1012
+ /** The numbers of the body a blast wave pushes. All have a default for a standing adult. */
1013
+ interface BlastImpulseOptions {
1014
+ /** Square metres of the body the wave meets: about 1.75 m tall by 0.4 m wide (default 0.7). */
1015
+ readonly bodyArea?: number;
1016
+ /** How much of the push goes up: the direction is `(away, lift)` normalised (default 0.15: the wave runs along the ground). */
1017
+ readonly lift?: number;
1018
+ /** Below this speed (m/s) the body does not move (default 0.3). */
1019
+ readonly minSpeed?: number;
1020
+ /**
1021
+ * Kilograms of TNT per joule of the blast `energy`, for a blast that does not say its charge (`BlastSource.tnt`). Default 0.24 / 200000: the arsenal's energy scale, where the M67 (180 g
1022
+ * of Composition B, 0.24 kg of TNT) is 200 kJ. It is a game equivalence for blasts without a charge, not a physical constant.
1023
+ */
1024
+ readonly tntPerJoule?: number;
1025
+ }
1026
+ declare const DEFAULT_BLAST_IMPULSE: Readonly<Required<BlastImpulseOptions>>;
1027
+ /**
1028
+ * Side-on (incident) specific impulse of the wave, in Pa·s, at `distance` metres from `tnt` kg of TNT in free air: Kinney and Graham, "Explosive Shocks in Air" (1985),
1029
+ * `I/W^(1/3) = 0.067 sqrt(1 + (Z/0.23)^4) / (Z^2 (1 + (Z/1.55)^3)^(1/3))` bar·ms per kg^(1/3), with the scaled distance `Z = R / W^(1/3)`.
1030
+ */
1031
+ declare function sideOnImpulse(tnt: number, distance: number): number;
1032
+ /**
1033
+ * The impulse per square metre a body facing the wave takes, Pa·s: the side-on impulse reflected. The reflection doubles a weak wave and grows up to about six times close to the charge,
1034
+ * where the wave is strong (the ratio of reflected to side-on impulse of the Kingery-Bulmash charts, fitted as `2 + 4 / (1 + Z^2)`).
1035
+ */
1036
+ declare function bodyImpulse(tnt: number, distance: number): number;
1037
+ /** The speed (m/s) a blast wave gives a free body of `mass` kg at `distance` metres from `tnt` kg of TNT, `reach` (0..1) of it getting through what stands in the way. */
1038
+ declare function blastBodySpeed(tnt: number, distance: number, mass: number, reach?: number, options?: BlastImpulseOptions): number;
1039
+
991
1040
  /** From this speed (m/s) a blast takes a standing body off its feet. Below it the body is only pushed. */
992
1041
  declare const KNOCK_DOWN_SPEED = 2.5;
993
1042
  /** The angle of a body lying where it fell (the same tilt a dead body takes). */
994
1043
  declare const KNOCKED_DOWN_ANGLE = 1.35;
1044
+ /** The share of the blast that has to get through for its fragments to reach a body: behind cover that stops half of it the fragments stop too. */
1045
+ declare const FRAGMENT_COVER = 0.5;
995
1046
  /** Height of the chest above the feet: where the blast takes the body. */
996
1047
  declare const KNOCK_CHEST = 1.1;
997
1048
  /** 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). */
@@ -1004,14 +1055,23 @@ interface Knock {
1004
1055
  lieLeft: number;
1005
1056
  /** Knocked off its feet by this blast. */
1006
1057
  down: boolean;
1058
+ /** Floored by its wounds: inside the lethal radius and not behind cover. It lies longer and falls where it stood. */
1059
+ wounded?: boolean;
1060
+ /** Seconds of stun the blast gave the body: dizziness and ringing ears. */
1061
+ stunSeconds?: number;
1062
+ /** The horizontal unit direction away from the blast, even when the push is too weak to move the body: the way it falls. */
1063
+ away?: Vec3;
1007
1064
  }
1008
1065
  /** The mass of a body in kg from its height (m) and its build (1 = average): 70 kg at 1.75 m. */
1009
1066
  declare const bodyMass: (height: number, build?: number) => number;
1010
1067
  /**
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.
1068
+ * What a blast does to a body standing at `feet`, of `mass` kg. Undefined when the blast does not reach it or does nothing to it.
1069
+ *
1070
+ * The body is pushed by the blast wave (`blastBodySpeed`, from the charge `blast.tnt`, or from `energy` by `tntPerJoule`), taken at the chest and cut by `reach`, the share that got through
1071
+ * cover. It is floored when that push is `KNOCK_DOWN_SPEED` or more, or when it stands inside `lethalRadius` with the blast reaching it (the fragments and the overpressure fell it where it
1072
+ * stands). It is stunned for a while inside the radius. `options` are the shove numbers of the host's loose items; only their `minSpeed` applies to a body.
1013
1073
  */
1014
- declare function knockOf(blast: BlastSource, feet: Vec3, mass: number, options?: BlastShoveOptions): Knock | undefined;
1074
+ declare function knockOf(blast: BlastSource, feet: Vec3, mass: number, options?: BlastShoveOptions, impulse?: BlastImpulseOptions): Knock | undefined;
1015
1075
  /**
1016
1076
  * 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
1077
  * body is on its feet and still, and the knock can be dropped.
@@ -1021,6 +1081,17 @@ declare function stepKnock(knock: Knock, position: {
1021
1081
  y: number;
1022
1082
  z: number;
1023
1083
  }, dt: number, groundAt: (x: number, z: number) => number, alive: boolean): boolean;
1084
+ /**
1085
+ * Throws a body at `velocity` (m/s, world): an occupant leaving a car through the glass, a pedestrian run over. The module that animates the body
1086
+ * takes it on its next frame (`takeLaunch`) and moves it as it moves a body a blast knocked down: flying, landing, sliding, lying, getting up.
1087
+ */
1088
+ declare function launchBody(root: {
1089
+ userData: Record<string, unknown>;
1090
+ }, velocity: Vec3): void;
1091
+ /** The throw `launchBody` left on the root, as a knock (knocked down, in the air), once; undefined when nothing threw it. */
1092
+ declare function takeLaunch(root: {
1093
+ userData: Record<string, unknown>;
1094
+ }): Knock | undefined;
1024
1095
 
1025
1096
  interface CostOptions {
1026
1097
  /** Reads of `state()` and of `describe()` to time. Default 10 000. */
@@ -1496,6 +1567,16 @@ interface HandTargets {
1496
1567
  }
1497
1568
  /** Read every frame by whoever draws the arms; answers null while nothing is held. */
1498
1569
  type HandTargetSource = () => HandTargets | null;
1570
+ /** The muzzle of the tool in the hands as it is drawn now (world space): where a shot's flash and tracer leave, and which way the barrel points. */
1571
+ interface MuzzlePose {
1572
+ readonly position: Vec3;
1573
+ /** Unit vector along the barrel, out of the muzzle. */
1574
+ readonly direction: Vec3;
1575
+ /** The module draws the muzzle flash itself: the host draws none of its own. */
1576
+ readonly drawsFlash: boolean;
1577
+ }
1578
+ /** Read by the host at each shot; null while the tool in the hands has no muzzle (a knife, a bare hand). */
1579
+ type MuzzleSource = () => MuzzlePose | null;
1499
1580
 
1500
1581
  /** How a hand turns something round in strokes (a screw cap, a tap handle, a knob): what `twistStroke` answers for one moment of the turning. */
1501
1582
  interface TwistStroke {
@@ -2143,6 +2224,8 @@ interface AreaPlacement {
2143
2224
  interface GroundZone {
2144
2225
  readonly rect: readonly [number, number, number, number];
2145
2226
  readonly surface: string;
2227
+ /** Sign a renderer lays on the front edge of the zone (the side of +z). */
2228
+ readonly label?: string;
2146
2229
  }
2147
2230
  /** What the floor is made of. `surface` is a ground profile id (`lawn`, `road`, `concrete_slab`, `tile_floor`...); a host that does not know it uses its default. */
2148
2231
  interface GroundLayout {
@@ -2290,6 +2373,10 @@ type HostEvent =
2290
2373
  radius: number;
2291
2374
  cause: string;
2292
2375
  explosive?: string;
2376
+ /** Kilograms of TNT equivalent of the charge, when the explosive says it: the blast wave that pushes bodies is read from it (`knockOf`). */
2377
+ tnt?: number;
2378
+ /** Inside this radius (metres) the blast is lethal to a body it reaches. */
2379
+ lethalRadius?: number;
2293
2380
  }
2294
2381
  /** The player crouched or stood up, or the camera mode changed; read the new state with `host.player()`. */
2295
2382
  | {
@@ -2499,6 +2586,11 @@ interface ModuleHost {
2499
2586
  * frame hooks have run, and puts the hands there. Pass null (or call the returned function) to let go. A host without arms leaves it out.
2500
2587
  */
2501
2588
  setHandTargets?(source: HandTargetSource | null): () => void;
2589
+ /**
2590
+ * Lends the host the muzzle of the tool the module draws in the hands: the host reads `source` at each shot and puts the tracer (and, unless the module draws one, the flash) there, at the
2591
+ * muzzle of the model and not at a guess in front of the eye. Pass null (or call the returned function) to let go. A host that draws no shot effects leaves it out.
2592
+ */
2593
+ setMuzzleSource?(source: MuzzleSource | null): () => void;
2502
2594
  /**
2503
2595
  * Where the player's body holds a tool now (world space), in every view (the weapon is the body's own): `position` is the pocket of the right shoulder a stock rests on, `quaternion` turns
2504
2596
  * to the aim (-z the aim, x right, y up, as a camera). A module draws the tool there at its real size. Null for a host with no body (the module puts the same pocket under the camera's eye);
@@ -2522,6 +2614,13 @@ interface ModuleHost {
2522
2614
  * that keeps its camera to itself leaves it out.
2523
2615
  */
2524
2616
  setViewCamera?(camera: unknown | null): void;
2617
+ /**
2618
+ * Holds the walking player where it stands (true) or lets it walk again (false): the movement keys then belong to the module (a vehicle the player drives). Mouse look,
2619
+ * the panels and the room's other keys keep working. A host without a walking player leaves it out.
2620
+ */
2621
+ holdPlayer?(held: boolean): void;
2622
+ /** True while a module holds the player (`holdPlayer(true)`): the keys then belong to that module, and another module's own keys (a weapon's accessories) stay out of its way. */
2623
+ playerHeld?(): boolean;
2525
2624
  /** Lent only to a module that declares controls, layers or metrics. */
2526
2625
  bench?: ModuleBench;
2527
2626
  /** The host's shared sound engine; a module that makes sound uses it when lent and stays silent (or brings its own) when not. */
@@ -2807,4 +2906,4 @@ declare function footprintBlocker(object: FootprintObject | (() => FootprintObje
2807
2906
  /** @deprecated Import it from `@adatechnology/game-contracts/testing`. */
2808
2907
  declare const resetWarnOnce: () => void;
2809
2908
 
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 };
2909
+ 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 BlastImpulseOptions, 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_IMPULSE, 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, FRAGMENT_COVER, 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 MuzzlePose, type MuzzleSource, 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, blastBodySpeed, blastShove, bodyImpulse, bodyMass, checkAnimation, checkBench, checkCategorized, checkDeferredQueue, checkDestructible, checkHit, checkSpec, checkToolClips, createEnvironmentBus, createToolClipPlayer, deepMerge, defineCategories, fillFactor, footprintBlocker, footstepSoundId, hitSoundId, ignitionChance, isSurfaceId, kitOriginOf, knockOf, landSoundId, launchBody, leadClipEvents, markKitOrigin, mergeById, removeIds, resetWarnOnce, resolveEntry, resolveIgnitionRules, sampleClip, scaleClip, sealBlock, sealOpener, shotExtras, shotToHit, sideOnImpulse, sparkTargetFactor, stepKnock, surfaceOfMaterial, swingSoundId, takeLaunch, toolAnimationStep, twistStroke, unmarkedDrawings, useToHit };
@@ -24,6 +24,7 @@ var GameContracts = (() => {
24
24
  BLOW_IDS: () => BLOW_IDS,
25
25
  BODY_FOLEY_IDS: () => BODY_FOLEY_IDS,
26
26
  BODY_SOUND_IDS: () => BODY_SOUND_IDS,
27
+ DEFAULT_BLAST_IMPULSE: () => DEFAULT_BLAST_IMPULSE,
27
28
  DEFAULT_BLAST_SHOVE: () => DEFAULT_BLAST_SHOVE,
28
29
  DEFAULT_ENVIRONMENT: () => DEFAULT_ENVIRONMENT,
29
30
  DEFAULT_IGNITION_RULES: () => DEFAULT_IGNITION_RULES,
@@ -31,6 +32,7 @@ var GameContracts = (() => {
31
32
  DEFERRED_PRIORITY: () => DEFERRED_PRIORITY,
32
33
  DEFERRED_QUEUE_ID: () => DEFERRED_QUEUE_ID,
33
34
  FOOTSTEP_SOUND_IDS: () => FOOTSTEP_SOUND_IDS,
35
+ FRAGMENT_COVER: () => FRAGMENT_COVER,
34
36
  FRAME_PHASES: () => FRAME_PHASES,
35
37
  GAIT_IDS: () => GAIT_IDS,
36
38
  HEAVY_LANDING_SPEED: () => HEAVY_LANDING_SPEED,
@@ -49,7 +51,9 @@ var GameContracts = (() => {
49
51
  TOOL_CLIP: () => TOOL_CLIP,
50
52
  TWIST_LIFT: () => TWIST_LIFT,
51
53
  applyPatches: () => applyPatches,
54
+ blastBodySpeed: () => blastBodySpeed,
52
55
  blastShove: () => blastShove,
56
+ bodyImpulse: () => bodyImpulse,
53
57
  bodyMass: () => bodyMass,
54
58
  checkAnimation: () => checkAnimation,
55
59
  checkBench: () => checkBench,
@@ -72,6 +76,7 @@ var GameContracts = (() => {
72
76
  kitOriginOf: () => kitOriginOf,
73
77
  knockOf: () => knockOf,
74
78
  landSoundId: () => landSoundId,
79
+ launchBody: () => launchBody,
75
80
  leadClipEvents: () => leadClipEvents,
76
81
  markKitOrigin: () => markKitOrigin,
77
82
  mergeById: () => mergeById,
@@ -85,10 +90,12 @@ var GameContracts = (() => {
85
90
  sealOpener: () => sealOpener,
86
91
  shotExtras: () => shotExtras,
87
92
  shotToHit: () => shotToHit,
93
+ sideOnImpulse: () => sideOnImpulse,
88
94
  sparkTargetFactor: () => sparkTargetFactor,
89
95
  stepKnock: () => stepKnock,
90
96
  surfaceOfMaterial: () => surfaceOfMaterial,
91
97
  swingSoundId: () => swingSoundId,
98
+ takeLaunch: () => takeLaunch,
92
99
  toolAnimationStep: () => toolAnimationStep,
93
100
  twistStroke: () => twistStroke,
94
101
  unmarkedDrawings: () => unmarkedDrawings,
@@ -98,6 +105,7 @@ var GameContracts = (() => {
98
105
 
99
106
  // src/blast-shove.ts
100
107
  var DEFAULT_BLAST_SHOVE = { coupling: 0.02, maxSpeed: 20, lift: 0.3, minSpeed: 0.3, falloffPower: 2 };
108
+ var clampShare = (share) => Math.min(1, Math.max(0, share));
101
109
  function blastShove(blast, target, options = {}) {
102
110
  const o = { ...DEFAULT_BLAST_SHOVE, ...options };
103
111
  const dx = target.position[0] - blast.point[0];
@@ -107,17 +115,41 @@ var GameContracts = (() => {
107
115
  if (!(blast.radius > 0) || distance > blast.radius) return void 0;
108
116
  const falloff = (1 - distance / blast.radius) ** o.falloffPower;
109
117
  const mass = Math.max(target.mass, 0.05);
110
- const speed = Math.min(o.maxSpeed, Math.sqrt(2 * blast.energy * (blast.reach ?? 1) * o.coupling / mass)) * falloff;
118
+ const reach = clampShare(blast.reach ?? 1);
119
+ const speed = Math.sqrt(reach) * Math.min(o.maxSpeed, Math.sqrt(2 * blast.energy * o.coupling / mass)) * falloff;
111
120
  if (!(speed >= o.minSpeed)) return void 0;
112
121
  const direction = distance > 1e-4 ? [dx / distance, dy / distance + o.lift, dz / distance] : [0, 1, 0];
113
122
  const length = Math.hypot(direction[0], direction[1], direction[2]);
114
123
  return { speed, direction: [direction[0] / length, direction[1] / length, direction[2] / length], distance, falloff };
115
124
  }
116
125
 
126
+ // src/blast-impulse.ts
127
+ var DEFAULT_BLAST_IMPULSE = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 2e5 };
128
+ var NEAREST = 0.3;
129
+ function sideOnImpulse(tnt, distance) {
130
+ if (!(tnt > 0)) return 0;
131
+ const w3 = Math.cbrt(tnt);
132
+ const z = Math.max(distance, NEAREST) / w3;
133
+ const scaled = 0.067 * Math.sqrt(1 + (z / 0.23) ** 4) / (z * z * Math.cbrt(1 + (z / 1.55) ** 3));
134
+ return scaled * w3 * 100;
135
+ }
136
+ function bodyImpulse(tnt, distance) {
137
+ if (!(tnt > 0)) return 0;
138
+ const z = Math.max(distance, NEAREST) / Math.cbrt(tnt);
139
+ return sideOnImpulse(tnt, distance) * (2 + 4 / (1 + z * z));
140
+ }
141
+ function blastBodySpeed(tnt, distance, mass, reach = 1, options = {}) {
142
+ const area = options.bodyArea ?? DEFAULT_BLAST_IMPULSE.bodyArea;
143
+ return bodyImpulse(tnt, distance) * area * Math.max(0, reach) / Math.max(mass, 1);
144
+ }
145
+
117
146
  // src/blast-knock.ts
118
147
  var KNOCK_DOWN_SPEED = 2.5;
119
148
  var KNOCKED_DOWN_ANGLE = 1.35;
120
149
  var LIE_SECONDS = 1.6;
150
+ var WOUNDED_LIE_SECONDS = 6;
151
+ var FRAGMENT_COVER = 0.5;
152
+ var STUN_SECONDS = 4;
121
153
  var STAND_UP_PER_SPEED = 0.25;
122
154
  var FALL_SECONDS = 0.25;
123
155
  var RISE_SECONDS = 0.6;
@@ -126,11 +158,38 @@ var GameContracts = (() => {
126
158
  var KNOCK_CHEST = 1.1;
127
159
  var CHEST = KNOCK_CHEST;
128
160
  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 };
161
+ function knockOf(blast, feet, mass, options, impulse = {}) {
162
+ const o = { ...DEFAULT_BLAST_IMPULSE, ...impulse };
163
+ const chest = [feet[0], feet[1] + CHEST, feet[2]];
164
+ const dx = chest[0] - blast.point[0];
165
+ const dy = chest[1] - blast.point[1];
166
+ const dz = chest[2] - blast.point[2];
167
+ const distance = Math.hypot(dx, dy, dz);
168
+ if (!(blast.radius > 0) || distance > blast.radius) return void 0;
169
+ const reach = Math.max(0, Math.min(1, blast.reach ?? 1));
170
+ const tnt = blast.tnt ?? blast.energy * o.tntPerJoule;
171
+ let speed = blastBodySpeed(tnt, distance, mass, reach, o);
172
+ const minSpeed = impulse.minSpeed ?? options?.minSpeed ?? o.minSpeed;
173
+ if (speed < minSpeed) speed = 0;
174
+ const struck = reach >= FRAGMENT_COVER;
175
+ const wounded = struck && blast.lethalRadius !== void 0 && distance <= blast.lethalRadius;
176
+ const edge = 1 - distance / blast.radius;
177
+ const stunSeconds = struck ? STUN_SECONDS * edge * edge * reach : 0;
178
+ if (speed === 0 && !wounded && stunSeconds < 0.25) return void 0;
179
+ const horizontal = Math.hypot(dx, dz);
180
+ const away = horizontal > 1e-4 ? [dx / horizontal, o.lift, dz / horizontal] : [0, 1, 0];
181
+ const length = Math.hypot(away[0], away[1], away[2]);
182
+ const velocity = [away[0] / length * speed, away[1] / length * speed, away[2] / length * speed];
183
+ return {
184
+ velocity,
185
+ airborne: velocity[1] > 0.5,
186
+ lying: 0,
187
+ lieLeft: wounded ? WOUNDED_LIE_SECONDS : 0,
188
+ away: horizontal > 1e-4 ? [dx / horizontal, 0, dz / horizontal] : [0, 0, 0],
189
+ down: speed >= KNOCK_DOWN_SPEED || wounded,
190
+ ...wounded ? { wounded } : {},
191
+ ...stunSeconds >= 0.25 ? { stunSeconds } : {}
192
+ };
134
193
  }
135
194
  function stepKnock(knock, position, dt, groundAt, alive) {
136
195
  const v = knock.velocity;
@@ -154,18 +213,25 @@ var GameContracts = (() => {
154
213
  }
155
214
  const moving = horizontal > 0.05 || knock.airborne;
156
215
  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
- }
216
+ if (moving || knock.lying < 1) knock.lying = Math.min(1, knock.lying + dt / FALL_SECONDS);
217
+ else if (alive) {
218
+ knock.lieLeft = knock.lieLeft > 0 ? knock.lieLeft : LIE_SECONDS + Math.hypot(v[0], v[2]) * STAND_UP_PER_SPEED;
219
+ knock.lieLeft -= dt;
220
+ if (knock.lieLeft <= 0) knock.down = false;
165
221
  }
166
222
  } else if (knock.lying > 0) knock.lying = Math.max(0, knock.lying - dt / RISE_SECONDS);
167
223
  return !moving && !knock.down && knock.lying === 0;
168
224
  }
225
+ var LAUNCHED = "launched";
226
+ function launchBody(root, velocity) {
227
+ root.userData[LAUNCHED] = [velocity[0], velocity[1], velocity[2]];
228
+ }
229
+ function takeLaunch(root) {
230
+ const velocity = root.userData[LAUNCHED];
231
+ if (!velocity) return void 0;
232
+ delete root.userData[LAUNCHED];
233
+ return { velocity: [velocity[0], velocity[1], velocity[2]], airborne: true, lying: 0, lieLeft: 0, down: true };
234
+ }
169
235
 
170
236
  // src/impact-cost.ts
171
237
  var DEFAULT_COST_READS = 1e4;
@@ -1190,6 +1256,8 @@ var GameContracts = (() => {
1190
1256
  mortar: "concrete",
1191
1257
  stone: "concrete",
1192
1258
  brick: "concrete",
1259
+ cobble: "concrete",
1260
+ paver: "concrete",
1193
1261
  plaster: "concrete",
1194
1262
  asphalt: "asphalt",
1195
1263
  wood_soft: "wood",
@@ -1200,6 +1268,8 @@ var GameContracts = (() => {
1200
1268
  ceramic: "tile",
1201
1269
  plastic: "tile",
1202
1270
  soil: "dirt",
1271
+ mud: "dirt",
1272
+ clay: "dirt",
1203
1273
  wax: "dirt",
1204
1274
  flesh_fruit: "dirt",
1205
1275
  turf: "grass",
@@ -1207,6 +1277,8 @@ var GameContracts = (() => {
1207
1277
  cactus: "grass",
1208
1278
  gravel: "gravel",
1209
1279
  sand: "sand",
1280
+ // no snow surface yet: packed snow crunches closest to sand underfoot
1281
+ snow: "sand",
1210
1282
  steel: "metal",
1211
1283
  cast_iron: "metal",
1212
1284
  aluminium: "metal",