@adatechnology/game-contracts 0.19.0 → 0.21.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
@@ -77,6 +77,13 @@ interface PartSpec {
77
77
  */
78
78
  outflow?: Vec3;
79
79
  };
80
+ /**
81
+ * Loose dry contents of a hollow part (the flakes of a cereal box, flour, sugar, coffee): a hole through it lets out a puff of them, in `color`
82
+ * (0xRRGGBB). The renderer draws the puff; nothing is lost from the part's mass.
83
+ */
84
+ powder?: {
85
+ color: number;
86
+ };
80
87
  /** The part explodes when destroyed, or when its temperature passes `atC` (gas bottle, fuel tank, aerosol). */
81
88
  explodes?: {
82
89
  energy: number;
@@ -658,12 +665,17 @@ type DestructionEvent = (EventBase & {
658
665
  part: string;
659
666
  dent: Dent;
660
667
  worldPoint: Vec3;
661
- }) | (EventBase & {
668
+ })
669
+ /** `powder`: the part's loose dry contents (`PartSpec.powder`), which puff out of the hole. */
670
+ | (EventBase & {
662
671
  type: 'hole';
663
672
  part: string;
664
673
  hole: Hole;
665
674
  worldPoint: Vec3;
666
675
  exitPoint?: Vec3;
676
+ powder?: {
677
+ color: number;
678
+ };
667
679
  }) | (EventBase & {
668
680
  type: 'crack';
669
681
  part: string;
@@ -812,6 +824,7 @@ type DestructionEvent = (EventBase & {
812
824
  /**
813
825
  * 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
814
826
  * 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.
827
+ * 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.
815
828
  */
816
829
  | (EventBase & {
817
830
  type: 'chip';
@@ -820,6 +833,7 @@ type DestructionEvent = (EventBase & {
820
833
  size: Vec3;
821
834
  rotation: Quat;
822
835
  fragments: readonly Fragment[];
836
+ torn?: true;
823
837
  })
824
838
  /** Rebar of a reinforced part is bare: segments in world space. Empty `bars` means the cage is gone. */
825
839
  | (EventBase & {
@@ -1321,18 +1335,50 @@ interface ItemUseDef {
1321
1335
  id: string;
1322
1336
  /** Seconds. */
1323
1337
  duration: number;
1324
- /** Keyframes: time 0..duration, the hand-relative position/rotation (Euler XYZ) of the item and the container action during that key. */
1338
+ /**
1339
+ * Keyframes: time 0..duration, the hand-relative position/rotation (Euler XYZ) of the item and the container action during that key. `eat` takes the contents
1340
+ * out as `drink` does, as food: the liquid packed with it (the brine of a tin, the oil of the sardines) goes with the food, without the effects of drinking it. `turn` is the
1341
+ * time the other hand turns a part of the item round (a screw cap, `twistStroke`); the container itself does nothing then.
1342
+ */
1325
1343
  keys: Array<{
1326
1344
  t: number;
1327
1345
  position: Vec3;
1328
1346
  rotation: Vec3;
1329
- action?: 'drink' | 'pour' | 'open' | 'close' | 'none';
1347
+ action?: 'drink' | 'eat' | 'pour' | 'open' | 'close' | 'turn' | 'none';
1330
1348
  rate?: number;
1331
1349
  }>;
1332
1350
  /** Only valid when the container holds one of these (empty = any). */
1333
1351
  liquids?: LiquidId[];
1334
1352
  }
1335
1353
 
1354
+ /**
1355
+ * The seal of a closed container (a food tin, a drink can, a jar, a bottle with a cap): what opens it and whether a breach spoils what is inside.
1356
+ * Plain data and one pure check, shared by the item rules (`resolveUse` of game-items) and any host that lets the player open and eat (the test room).
1357
+ */
1358
+ interface SealSpec {
1359
+ /** Ids of the tools that open it (items or weapons: `can_opener`, `knife`); any one of them is enough. */
1360
+ readonly openWith?: readonly string[];
1361
+ /** It opens by hand (a pull tab, a ring pull, a screw cap): no tool needed. */
1362
+ readonly byHand?: boolean;
1363
+ /** A hole through the container or a leak spoils what is inside: it can no longer be eaten or drunk. */
1364
+ readonly spoilsWhenBreached?: boolean;
1365
+ }
1366
+ /** The state of one container: opened, holed through (a shot, a blade) or leaking. */
1367
+ interface SealState {
1368
+ readonly opened?: boolean;
1369
+ readonly breached?: boolean;
1370
+ readonly leaked?: boolean;
1371
+ }
1372
+ /** Why a sealed container cannot be eaten or drunk now. */
1373
+ type SealBlock = 'sealed' | 'breached' | 'leaked';
1374
+ /** Whether it can be opened with the tools at hand, and with which one (undefined when it opens by hand). */
1375
+ declare const sealOpener: (seal: SealSpec, tools: Iterable<string>) => {
1376
+ readonly ok: boolean;
1377
+ readonly tool?: string;
1378
+ };
1379
+ /** What stops eating or drinking it now, or undefined when nothing does. A spoiled container (breached, leaked) is blocked even once open. */
1380
+ declare const sealBlock: (seal: SealSpec, state: SealState) => SealBlock | undefined;
1381
+
1336
1382
  /**
1337
1383
  * Where a hand should be, in world space. `position` is the middle of the palm; the orientation follows one convention for every hand: the hand-local +x runs from the wrist to the
1338
1384
  * knuckles (the way the fingers point when open), +y is the back of the hand and +z completes the frame (for a right hand the little finger side; the thumb is at -z). A left hand is the
@@ -1360,10 +1406,40 @@ interface HandTargets {
1360
1406
  fingers?: {
1361
1407
  index?: number;
1362
1408
  };
1409
+ /**
1410
+ * Where the elbows go: `view` puts them on screen beside the hand, out to the side, as a tool held out in front (default); `body` lets them hang down and back by the body, as an item
1411
+ * carried close (a can, a bottle), where an elbow out to the side would lift it above the shoulder.
1412
+ */
1413
+ elbows?: 'view' | 'body';
1363
1414
  }
1364
1415
  /** Read every frame by whoever draws the arms; answers null while nothing is held. */
1365
1416
  type HandTargetSource = () => HandTargets | null;
1366
1417
 
1418
+ /** 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. */
1419
+ interface TwistStroke {
1420
+ /** Radians the wrist is turned from the middle of its stroke, positive the way the part turns. */
1421
+ readonly angle: number;
1422
+ /** Radians the part has turned in all, from 0 to `strokes * reach`. */
1423
+ readonly turned: number;
1424
+ /** How closed the fingers are, 0 open to 1 closed: closed on the part while it turns, open while the wrist turns back. */
1425
+ readonly grip: number;
1426
+ /** 0..1, how far the hand rises off the part while the wrist turns back (1 is `TWIST_LIFT` metres). */
1427
+ readonly lift: number;
1428
+ }
1429
+ interface TwistOptions {
1430
+ /** Strokes of the wrist. Default 3. */
1431
+ readonly strokes?: number;
1432
+ /** Radians the part turns in one stroke (a wrist turns about a quarter round with its hold). Default 1.6. */
1433
+ readonly reach?: number;
1434
+ }
1435
+ /** Metres a hand rises off the part between two strokes. */
1436
+ declare const TWIST_LIFT = 0.012;
1437
+ /**
1438
+ * The hand turning a part round at `share` (0..1) of the whole turning: in each stroke the wrist turns with the part, then lets go, rises a little and turns back.
1439
+ * Pure and the same for every host, so first and third person turn the same hand the same way.
1440
+ */
1441
+ declare const twistStroke: (share: number, options?: TwistOptions) => TwistStroke;
1442
+
1367
1443
  /**
1368
1444
  * The bench of a module: controls, layers and counters it declares as data. The host keeps the values, draws them in a generic way and hands
1369
1445
  * every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.
@@ -1523,6 +1599,33 @@ interface EffectRequest {
1523
1599
  /** Named numbers the effect reads (a size, a length of time, a rate): which ones belong to the effect, and an effect that does not read one ignores it. */
1524
1600
  readonly values?: Readonly<Record<string, number>>;
1525
1601
  }
1602
+ /** A box in the frame of an {@link Enclosure}: `center` is the offset from the enclosure's centre along its own axes, `halfSize` half the size along each. */
1603
+ interface EnclosureBox {
1604
+ readonly center: Vec3;
1605
+ readonly halfSize: Vec3;
1606
+ /**
1607
+ * How much gets through, as the share per second of what presses on it (a bullet hole: a small number). Absent: it is open, everything passes
1608
+ * (an open door, a shattered window, a panel torn off).
1609
+ */
1610
+ readonly rate?: number;
1611
+ }
1612
+ /**
1613
+ * A closed space that holds out what the air carries (smoke, dust, steam, gas) and holds in what starts inside it: the cabin of a car, a closed room.
1614
+ * It is a box turned by `orientation`; what is carried crosses its walls only through `openings`, and through a closed wall only at `leak`
1615
+ * (the gaps of a shut door), like a real one. Nothing here is specific to a kind of thing or to a renderer.
1616
+ */
1617
+ interface Enclosure {
1618
+ /** Centre in world space, metres. */
1619
+ readonly center: Vec3;
1620
+ /** Half the size along the box's own axes, metres. */
1621
+ readonly halfSize: Vec3;
1622
+ /** Which way the box is turned, a unit quaternion `[x, y, z, w]` (default not turned). */
1623
+ readonly orientation?: readonly [number, number, number, number];
1624
+ /** Where the walls are open, in the box's own frame (an opening sticks out through the wall it is in). */
1625
+ readonly openings?: readonly EnclosureBox[];
1626
+ /** Share per second of what presses on a closed wall that seeps through it (a shut car: about 0.02). Default 0: sealed. */
1627
+ readonly leak?: number;
1628
+ }
1526
1629
  /** Identifies a running effect so it can be stopped. */
1527
1630
  type EffectHandle = number;
1528
1631
  /**
@@ -1536,6 +1639,11 @@ interface EffectSink {
1536
1639
  stop(handle: EffectHandle): void;
1537
1640
  /** Sets what the host lets a module tune (`quality`, `lod_band`...): the id of the setting and its value. A host applies the ids it knows. */
1538
1641
  configure?(settings: Readonly<Record<string, ControlValue>>): void;
1642
+ /**
1643
+ * Declares, moves or ends (`null`) a closed space by the module's own id: the host's smoke, dust and steam go round it and enter only through its
1644
+ * openings. A module calls it again whenever the space moves or opens (every frame is fine). A host without it lets smoke through everything.
1645
+ */
1646
+ enclose?(id: string, enclosure: Enclosure | null): void;
1539
1647
  /** Counters of what is alive (`puffs`, `sparks`, `lights`...), for a bench. */
1540
1648
  stats?(): Readonly<Record<string, number>>;
1541
1649
  /** The ids `play` accepts. */
@@ -1685,6 +1793,36 @@ interface ModuleItemFluid {
1685
1793
  uses?: string[];
1686
1794
  offset?: Vec3;
1687
1795
  }
1796
+ /** A closed container the player opens before eating or drinking it (a food tin, a drink can): the seal, and how the item looks open. */
1797
+ interface ModuleItemSeal extends SealSpec {
1798
+ /** Draws the item opened: gets the root `build` returned (the cut lid of a tin, the mouth of a can). */
1799
+ open?: (root: unknown) => void;
1800
+ /** Shows what is left in it once open: `share` of what it held (0 eaten up), after each bite or sip. */
1801
+ contents?: (root: unknown, share: number) => void;
1802
+ /**
1803
+ * The use (an `ItemUseDef` id, such as `unscrew`) a by-hand seal is opened with: it plays before the first bite or sip, the other hand turning the part
1804
+ * during its `turn` keys, and the item opens at its `open` key. Without it a by-hand seal opens at once on the way to the mouth (a pull tab).
1805
+ */
1806
+ openUse?: string;
1807
+ /** Draws the part being opened mid-way: `turned` radians round its axis, `lift` 0..1 of the way off (a screw cap leaving its thread). */
1808
+ turn?: (root: unknown, state: {
1809
+ readonly turned: number;
1810
+ readonly lift: number;
1811
+ }) => void;
1812
+ }
1813
+ /** A place on an item where a body sits or lies (a chair, a sofa cushion, a bed), in the item's own frame, the same shape as a `game-interactables` seat. */
1814
+ interface ItemSeat {
1815
+ id: string;
1816
+ pose: 'sit' | 'lie';
1817
+ /** Hips (sit) or pelvis (lie). */
1818
+ pos: Vec3;
1819
+ /** Facing of the body, relative to the item. */
1820
+ ry: number;
1821
+ /** Eye height above `pos`. Default 0.62 sitting, 0.18 lying. */
1822
+ eye?: number;
1823
+ /** Where the body stands when it gets up. */
1824
+ exit: Vec3;
1825
+ }
1688
1826
  /** A picture of an item for catalogues and inventories: a hand-made `svg`, or `render` to have the host draw the 3D model (camera angles in radians, `wear` 0 new, 1 worn, 2 ruined). */
1689
1827
  interface Thumbnail {
1690
1828
  svg?: string;
@@ -1711,12 +1849,18 @@ interface TestableItem {
1711
1849
  /** The module's own destruction: parts, hits and state are its business. The world hosts it next to its own objects. `id` is the object id the host gives it: `DestructibleImpl.id` must be that id. */
1712
1850
  impl?: (root: unknown, id: string) => DestructibleImpl;
1713
1851
  fluid?: ModuleItemFluid;
1852
+ /** The item is a sealed container: it is opened first (with a tool or by hand), and a breach may spoil it (`SealSpec`). */
1853
+ seal?: ModuleItemSeal;
1714
1854
  /** The item is also a weapon the player can equip: an id of the arsenal in use. */
1715
1855
  weapon?: string;
1716
1856
  /** Use animations this item brings (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */
1717
1857
  animations?: AnimationClipSpec[];
1718
1858
  /** Can be grabbed and thrown. */
1719
1859
  movable?: boolean;
1860
+ /** A big piece that stands on the floor (a sofa, a wardrobe, a fridge) and can be pushed by leaning on it. Default false: what goes in the hand is grabbed, never pushed. */
1861
+ pushable?: boolean;
1862
+ /** Where a body sits or lies on it: the host can seat the player there. */
1863
+ seats?: readonly ItemSeat[];
1720
1864
  /** 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. */
1721
1865
  slow?: boolean | string;
1722
1866
  /**
@@ -1981,6 +2125,21 @@ interface PlayerState {
1981
2125
  /** How the camera follows the player (`first`, `shoulder`...); the ids are the host's. */
1982
2126
  readonly cameraMode: string;
1983
2127
  }
2128
+ /** A leaf a module moves itself, for the host's hand (`ModuleHost.reachFor`). */
2129
+ interface HostReach {
2130
+ /** World point of the handle now. */
2131
+ handle(): Vec3;
2132
+ /** World point of the hinge of a turning leaf: the host stands the player beside the free edge, clear of the swing. Left out, the player stands in front of the handle. */
2133
+ hinge?(): Vec3;
2134
+ /** How long the hand stays on the handle after the grip, seconds (the module's swing). */
2135
+ readonly seconds: number;
2136
+ /** The leaf comes toward the body (true) or goes away (false). Default true. */
2137
+ readonly pull?: boolean;
2138
+ /** Locked or barred: the hand only shakes it. */
2139
+ readonly locked?: boolean;
2140
+ /** The hand is on the handle. */
2141
+ onGrip?(): void;
2142
+ }
1984
2143
  /**
1985
2144
  * What the host lends a module that brings its own presentation (a weapon in the hands, sounds, ejected casings). `THREE`, `scene` and `camera`
1986
2145
  * are the host's three.js namespace, scene and camera: `unknown` here because the contracts do not depend on three.js.
@@ -2004,6 +2163,11 @@ interface ModuleHost {
2004
2163
  * unsubscribe function. A host that never clears leaves it out.
2005
2164
  */
2006
2165
  onClear?(listener: () => void): () => void;
2166
+ /**
2167
+ * Follow the host going back to its seeded start (before each scenario): the module's own random streams, seed counters and clocks are to go back to where
2168
+ * they began, so a scenario does the same whatever ran before it. Returns the unsubscribe function. A host that never resets leaves it out.
2169
+ */
2170
+ onReset?(listener: () => void): () => void;
2007
2171
  /** Write a line to the host's behaviour log; `type` is the kind of entry (default `module_note`), so a module can log what a filter looks for (`sound`). */
2008
2172
  log(text: string, data?: Record<string, unknown>, type?: string): void;
2009
2173
  /**
@@ -2034,6 +2198,18 @@ interface ModuleHost {
2034
2198
  * 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.
2035
2199
  */
2036
2200
  setHandTargets?(source: HandTargetSource | null): () => void;
2201
+ /**
2202
+ * 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
2203
+ * 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);
2204
+ * a host without a body leaves it out.
2205
+ */
2206
+ holdFrame?(): HandPose | null;
2207
+ /**
2208
+ * Puts the player's hand on a leaf the module moves itself (a door of a house): the host plays the reach to `handle()`, calls `onGrip` when the hand is on it (the module then turns
2209
+ * the handle and swings the leaf), keeps the hand on `handle()` for `seconds` as it moves, and takes it back; the body pulls or pushes (`pull`) or shakes a locked one (`locked`).
2210
+ * `handle` answers the world point of the handle every frame. A host without a body leaves it out (the module then moves the leaf at once).
2211
+ */
2212
+ reachFor?(reach: HostReach): void;
2037
2213
  /** Asks the tool in hand to do something (see `ToolCommand`). A host without accessories leaves it out. */
2038
2214
  command?(command: ToolCommand): ToolCommandResult;
2039
2215
  /** Lent only to a module that declares controls, layers or metrics. */
@@ -2294,4 +2470,4 @@ interface FootprintObject {
2294
2470
  */
2295
2471
  declare function footprintBlocker(object: FootprintObject | (() => FootprintObject | null | undefined), footprint: Footprint | (() => Footprint | null | undefined)): (x: number, z: number, radius: number) => boolean;
2296
2472
 
2297
- export { type ActionControl, type AnimationClipSpec, type AnimationEventSpec, type AnimationKey, type AnimationPose, type AreaPlacement, type AudioListener, type AudioSink, type BallisticExtras, type BenchChange, type BenchDeclaration, type BenchReport, type BenchSheet, type BlastOptions, type BlastReport, type BlastShove, type BlastShoveOptions, type BlastSource, type BodyRegion, type BodySlot, 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 Crack, DEFAULT_BLAST_SHOVE, DEFAULT_ENVIRONMENT, DEFAULT_IGNITION_RULES, type DamageStage, type DamageType, type DebrisKind, type DeepPartial, type Dent, type DestructibleImpl, type DestructibleSpec, type DestructionEvent, type DestructionEventType, type EffectHandle, type EffectRequest, type EffectSink, type EnvironmentBus, type EnvironmentKey, type EnvironmentState, type ExplosionChain, type FluidKind, type Footprint, type FootprintObject, type FractureStyle, type Fragment, type FragmentHitOptions, type FragmentMark, type GroundLayout, type GroundZone, type HandPose, type HandTargetSource, type HandTargets, type Hit, type HitIgnition, type HitResult, type Hole, type HostEvent, type HostTool, type HostToolAttachment, IMPACT_CONTRACT, type IgnitionRules, type ImpactContract, type ItemAnimationContract, type ItemUseDef, type JointKind, type JointSpec, type JointState, KIT_ORIGIN_KEY, type KitOrigin, type LiquidId, type LoopHandle, MISSING_BITS, type MaterialId, type ModuleArea, type ModuleBench, type ModuleControl, type ModuleHost, type ModuleItemFluid, 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, type SegmentKind, type ShelterOptions, type Shot, type SoilLayer, type SoilProfile, type SoilState, type SoundHandle, type SoundRequest, type SpecReport, type StructureRole, type SurfaceLayer, TOOL_CLIP, 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 UnmarkedDrawing, type Use, type Vec3, type WarmUpProbes, type WindExposure, type WindState, applyPatches, blastShove, checkAnimation, checkBench, checkCategorized, checkDestructible, checkHit, checkSpec, checkToolClips, createEnvironmentBus, createToolClipPlayer, deepMerge, defineCategories, fillFactor, footprintBlocker, ignitionChance, kitOriginOf, markKitOrigin, mergeById, removeIds, resolveEntry, resolveIgnitionRules, sampleClip, scaleClip, shotExtras, shotToHit, sparkTargetFactor, toolAnimationStep, unmarkedDrawings, useToHit };
2473
+ export { type ActionControl, type AnimationClipSpec, type AnimationEventSpec, type AnimationKey, type AnimationPose, type AreaPlacement, type AudioListener, type AudioSink, type BallisticExtras, type BenchChange, type BenchDeclaration, type BenchReport, type BenchSheet, type BlastOptions, type BlastReport, type BlastShove, type BlastShoveOptions, type BlastSource, type BodyRegion, type BodySlot, 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 Crack, DEFAULT_BLAST_SHOVE, DEFAULT_ENVIRONMENT, DEFAULT_IGNITION_RULES, type DamageStage, type DamageType, type DebrisKind, type DeepPartial, 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, type FluidKind, type Footprint, type FootprintObject, type FractureStyle, type Fragment, type FragmentHitOptions, type FragmentMark, type GroundLayout, type GroundZone, type HandPose, type HandTargetSource, type HandTargets, type Hit, type HitIgnition, type HitResult, type Hole, type HostEvent, 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, type LiquidId, type LoopHandle, 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, 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 SurfaceLayer, 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, checkDestructible, checkHit, checkSpec, checkToolClips, createEnvironmentBus, createToolClipPlayer, deepMerge, defineCategories, fillFactor, footprintBlocker, ignitionChance, kitOriginOf, markKitOrigin, mergeById, removeIds, resolveEntry, resolveIgnitionRules, sampleClip, scaleClip, sealBlock, sealOpener, shotExtras, shotToHit, sparkTargetFactor, toolAnimationStep, twistStroke, unmarkedDrawings, useToHit };
@@ -29,6 +29,7 @@ var GameContracts = (() => {
29
29
  MISSING_BITS: () => MISSING_BITS,
30
30
  STAGE_EVENT: () => STAGE_EVENT,
31
31
  TOOL_CLIP: () => TOOL_CLIP,
32
+ TWIST_LIFT: () => TWIST_LIFT,
32
33
  applyPatches: () => applyPatches,
33
34
  blastShove: () => blastShove,
34
35
  checkAnimation: () => checkAnimation,
@@ -53,10 +54,13 @@ var GameContracts = (() => {
53
54
  resolveIgnitionRules: () => resolveIgnitionRules,
54
55
  sampleClip: () => sampleClip,
55
56
  scaleClip: () => scaleClip,
57
+ sealBlock: () => sealBlock,
58
+ sealOpener: () => sealOpener,
56
59
  shotExtras: () => shotExtras,
57
60
  shotToHit: () => shotToHit,
58
61
  sparkTargetFactor: () => sparkTargetFactor,
59
62
  toolAnimationStep: () => toolAnimationStep,
63
+ twistStroke: () => twistStroke,
60
64
  unmarkedDrawings: () => unmarkedDrawings,
61
65
  useToHit: () => useToHit
62
66
  });
@@ -407,6 +411,38 @@ var GameContracts = (() => {
407
411
  return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
408
412
  };
409
413
 
414
+ // src/seal.ts
415
+ var sealOpener = (seal, tools) => {
416
+ if (seal.byHand || !seal.openWith || seal.openWith.length === 0) return { ok: true };
417
+ const have = new Set(tools);
418
+ const tool = seal.openWith.find((id) => have.has(id));
419
+ return tool === void 0 ? { ok: false } : { ok: true, tool };
420
+ };
421
+ var sealBlock = (seal, state) => {
422
+ if (seal.spoilsWhenBreached && state.breached) return "breached";
423
+ if (seal.spoilsWhenBreached && state.leaked) return "leaked";
424
+ if (!state.opened) return "sealed";
425
+ return void 0;
426
+ };
427
+
428
+ // src/twist.ts
429
+ var TWIST_LIFT = 0.012;
430
+ var TURNING = 0.7;
431
+ var smooth = (k) => k * k * (3 - 2 * k);
432
+ var twistStroke = (share, options = {}) => {
433
+ const strokes = Math.max(1, Math.round(options.strokes ?? 3));
434
+ const reach = options.reach ?? 1.6;
435
+ const along = Math.min(1, Math.max(0, share)) * strokes;
436
+ const index = Math.min(strokes - 1, Math.floor(along));
437
+ const phase = along - index;
438
+ if (phase <= TURNING) {
439
+ const k2 = smooth(phase / TURNING);
440
+ return { angle: reach * (k2 - 0.5), turned: reach * (index + k2), grip: 1, lift: 0 };
441
+ }
442
+ const k = (phase - TURNING) / (1 - TURNING);
443
+ return { angle: reach * (0.5 - smooth(k)), turned: reach * (index + 1), grip: 0.35, lift: Math.sin(k * Math.PI) };
444
+ };
445
+
410
446
  // src/animation.ts
411
447
  var finite3 = (v) => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === "number" && Number.isFinite(n));
412
448
  var checkAnimation = (clip, partIds) => {