@adatechnology/game-contracts 0.18.0 → 0.20.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;
@@ -675,7 +687,9 @@ type DestructionEvent = (EventBase & {
675
687
  point: Vec3;
676
688
  direction: Vec3;
677
689
  energy: number;
678
- }) | (EventBase & {
690
+ })
691
+ /** Bits thrown off a part; `material` is what they are made of when it is not the part's own (the plaster coat over a brick wall). */
692
+ | (EventBase & {
679
693
  type: 'debris';
680
694
  part: string;
681
695
  kind: DebrisKind;
@@ -683,6 +697,7 @@ type DestructionEvent = (EventBase & {
683
697
  normal: Vec3;
684
698
  count: number;
685
699
  speed: number;
700
+ material?: MaterialId;
686
701
  }) | (EventBase & {
687
702
  type: 'fracture';
688
703
  part: string;
@@ -809,6 +824,7 @@ type DestructionEvent = (EventBase & {
809
824
  /**
810
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
811
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.
812
828
  */
813
829
  | (EventBase & {
814
830
  type: 'chip';
@@ -817,6 +833,7 @@ type DestructionEvent = (EventBase & {
817
833
  size: Vec3;
818
834
  rotation: Quat;
819
835
  fragments: readonly Fragment[];
836
+ torn?: true;
820
837
  })
821
838
  /** Rebar of a reinforced part is bare: segments in world space. Empty `bars` means the cage is gone. */
822
839
  | (EventBase & {
@@ -1318,18 +1335,50 @@ interface ItemUseDef {
1318
1335
  id: string;
1319
1336
  /** Seconds. */
1320
1337
  duration: number;
1321
- /** 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
+ */
1322
1343
  keys: Array<{
1323
1344
  t: number;
1324
1345
  position: Vec3;
1325
1346
  rotation: Vec3;
1326
- action?: 'drink' | 'pour' | 'open' | 'close' | 'none';
1347
+ action?: 'drink' | 'eat' | 'pour' | 'open' | 'close' | 'turn' | 'none';
1327
1348
  rate?: number;
1328
1349
  }>;
1329
1350
  /** Only valid when the container holds one of these (empty = any). */
1330
1351
  liquids?: LiquidId[];
1331
1352
  }
1332
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
+
1333
1382
  /**
1334
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
1335
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
@@ -1357,10 +1406,40 @@ interface HandTargets {
1357
1406
  fingers?: {
1358
1407
  index?: number;
1359
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';
1360
1414
  }
1361
1415
  /** Read every frame by whoever draws the arms; answers null while nothing is held. */
1362
1416
  type HandTargetSource = () => HandTargets | null;
1363
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
+
1364
1443
  /**
1365
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
1366
1445
  * every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.
@@ -1682,6 +1761,36 @@ interface ModuleItemFluid {
1682
1761
  uses?: string[];
1683
1762
  offset?: Vec3;
1684
1763
  }
1764
+ /** 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. */
1765
+ interface ModuleItemSeal extends SealSpec {
1766
+ /** Draws the item opened: gets the root `build` returned (the cut lid of a tin, the mouth of a can). */
1767
+ open?: (root: unknown) => void;
1768
+ /** Shows what is left in it once open: `share` of what it held (0 eaten up), after each bite or sip. */
1769
+ contents?: (root: unknown, share: number) => void;
1770
+ /**
1771
+ * 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
1772
+ * 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).
1773
+ */
1774
+ openUse?: string;
1775
+ /** 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). */
1776
+ turn?: (root: unknown, state: {
1777
+ readonly turned: number;
1778
+ readonly lift: number;
1779
+ }) => void;
1780
+ }
1781
+ /** 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. */
1782
+ interface ItemSeat {
1783
+ id: string;
1784
+ pose: 'sit' | 'lie';
1785
+ /** Hips (sit) or pelvis (lie). */
1786
+ pos: Vec3;
1787
+ /** Facing of the body, relative to the item. */
1788
+ ry: number;
1789
+ /** Eye height above `pos`. Default 0.62 sitting, 0.18 lying. */
1790
+ eye?: number;
1791
+ /** Where the body stands when it gets up. */
1792
+ exit: Vec3;
1793
+ }
1685
1794
  /** 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). */
1686
1795
  interface Thumbnail {
1687
1796
  svg?: string;
@@ -1708,12 +1817,18 @@ interface TestableItem {
1708
1817
  /** 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. */
1709
1818
  impl?: (root: unknown, id: string) => DestructibleImpl;
1710
1819
  fluid?: ModuleItemFluid;
1820
+ /** The item is a sealed container: it is opened first (with a tool or by hand), and a breach may spoil it (`SealSpec`). */
1821
+ seal?: ModuleItemSeal;
1711
1822
  /** The item is also a weapon the player can equip: an id of the arsenal in use. */
1712
1823
  weapon?: string;
1713
1824
  /** Use animations this item brings (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */
1714
1825
  animations?: AnimationClipSpec[];
1715
1826
  /** Can be grabbed and thrown. */
1716
1827
  movable?: boolean;
1828
+ /** 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. */
1829
+ pushable?: boolean;
1830
+ /** Where a body sits or lies on it: the host can seat the player there. */
1831
+ seats?: readonly ItemSeat[];
1717
1832
  /** 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. */
1718
1833
  slow?: boolean | string;
1719
1834
  /**
@@ -1978,6 +2093,21 @@ interface PlayerState {
1978
2093
  /** How the camera follows the player (`first`, `shoulder`...); the ids are the host's. */
1979
2094
  readonly cameraMode: string;
1980
2095
  }
2096
+ /** A leaf a module moves itself, for the host's hand (`ModuleHost.reachFor`). */
2097
+ interface HostReach {
2098
+ /** World point of the handle now. */
2099
+ handle(): Vec3;
2100
+ /** 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. */
2101
+ hinge?(): Vec3;
2102
+ /** How long the hand stays on the handle after the grip, seconds (the module's swing). */
2103
+ readonly seconds: number;
2104
+ /** The leaf comes toward the body (true) or goes away (false). Default true. */
2105
+ readonly pull?: boolean;
2106
+ /** Locked or barred: the hand only shakes it. */
2107
+ readonly locked?: boolean;
2108
+ /** The hand is on the handle. */
2109
+ onGrip?(): void;
2110
+ }
1981
2111
  /**
1982
2112
  * What the host lends a module that brings its own presentation (a weapon in the hands, sounds, ejected casings). `THREE`, `scene` and `camera`
1983
2113
  * are the host's three.js namespace, scene and camera: `unknown` here because the contracts do not depend on three.js.
@@ -2001,6 +2131,11 @@ interface ModuleHost {
2001
2131
  * unsubscribe function. A host that never clears leaves it out.
2002
2132
  */
2003
2133
  onClear?(listener: () => void): () => void;
2134
+ /**
2135
+ * 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
2136
+ * they began, so a scenario does the same whatever ran before it. Returns the unsubscribe function. A host that never resets leaves it out.
2137
+ */
2138
+ onReset?(listener: () => void): () => void;
2004
2139
  /** 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`). */
2005
2140
  log(text: string, data?: Record<string, unknown>, type?: string): void;
2006
2141
  /**
@@ -2031,6 +2166,18 @@ interface ModuleHost {
2031
2166
  * 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.
2032
2167
  */
2033
2168
  setHandTargets?(source: HandTargetSource | null): () => void;
2169
+ /**
2170
+ * 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
2171
+ * 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);
2172
+ * a host without a body leaves it out.
2173
+ */
2174
+ holdFrame?(): HandPose | null;
2175
+ /**
2176
+ * 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
2177
+ * 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`).
2178
+ * `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).
2179
+ */
2180
+ reachFor?(reach: HostReach): void;
2034
2181
  /** Asks the tool in hand to do something (see `ToolCommand`). A host without accessories leaves it out. */
2035
2182
  command?(command: ToolCommand): ToolCommandResult;
2036
2183
  /** Lent only to a module that declares controls, layers or metrics. */
@@ -2291,4 +2438,4 @@ interface FootprintObject {
2291
2438
  */
2292
2439
  declare function footprintBlocker(object: FootprintObject | (() => FootprintObject | null | undefined), footprint: Footprint | (() => Footprint | null | undefined)): (x: number, z: number, radius: number) => boolean;
2293
2440
 
2294
- 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 };
2441
+ 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 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) => {