@adatechnology/game-contracts 0.8.0 → 0.10.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/README.md +17 -0
- package/dist/index.d.ts +219 -47
- package/dist/index.global.js +41 -0
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,6 +107,15 @@ A module can also declare a bench as data: `controls` (number, boolean, choice,
|
|
|
107
107
|
`ModuleBench` in `attach` (`host.bench`): `value(id)`, `layer(id)`, `onChange(listener)` (right after `attach` it gets one change per control and
|
|
108
108
|
layer, in order), `patch(id, { hidden, disabled, options, value })` (no echo) and `look(eye, target)`. `checkBench({ controls, layers, metrics })`
|
|
109
109
|
checks the declaration (snake_case ids unique across the three, `min < max`, `step > 0`, defaults in range and among the options, `max > 0`).
|
|
110
|
+
A choice's options may carry a `group` (the host lists them under that heading; the options of a group sit next to each other, and `checkBench` says
|
|
111
|
+
so when they do not). `ModuleBench.sheet(sheet)` puts a card on the host's panel (`BenchSheet`: kicker, title, body, facts, steps, colour swatches, note and a
|
|
112
|
+
timeline of segments whose playhead follows a module metric); a host with no sheet area leaves it out.
|
|
113
|
+
|
|
114
|
+
### Effects the host lends
|
|
115
|
+
|
|
116
|
+
`ModuleHost.effects` is an `EffectSink`: `play({ effect, point, direction?, normal?, seed?, param?, scale? })` starts an effect of the host's catalogue by id
|
|
117
|
+
and answers a handle (or `null` when the host does not know it), `stop(handle)` ends a looping one, and `configure`, `stats` and `catalog` are optional. The
|
|
118
|
+
`explosion` host event carries `explosive` (the id of the explosive), `PartSpec.explodes.effect` and the `explode` event name the effect of a part's blast.
|
|
110
119
|
|
|
111
120
|
```ts
|
|
112
121
|
controls: () => [
|
|
@@ -121,6 +130,13 @@ Tool events are named after a tool's cycle, not a firearm: `mode`, `fault`, `cle
|
|
|
121
130
|
(cartridges spent) next to `shots` (projectiles, always 1 or more) and the `mode`; a dud round is its own `misfire` event, not a `shot` of zero. `HostTool` may report `mode`, `fault`, `safety` and `heat` (0..1).
|
|
122
131
|
`host.configureTool(settings)` takes a `Record<string, string>` keyed by the id of the module's own control; the test room applies `condition` and `ammo_quality` and ignores other ids.
|
|
123
132
|
|
|
133
|
+
### Sound and weather the host lends
|
|
134
|
+
|
|
135
|
+
`ModuleHost.audio?` is an `AudioSink` (`play(SoundRequest)`, `stop(handle)`, and optionally `loop`, `setListener`, `register`): one shared sound engine, so every module is mixed,
|
|
136
|
+
limited and panned the same way. `ModuleHost.environment?` is an `EnvironmentBus` (`get`, `set(patch)`, `on`) holding an `EnvironmentState` (`rain` mm/h, wind, `fogFar`, `visibility`,
|
|
137
|
+
`nightFactor`, `temperatureC`, `wetness`): the weather module writes it, the others read it. Both are optional, additive and generic; `createEnvironmentBus(initial?)` is a plain
|
|
138
|
+
store that implements the bus, and `DEFAULT_ENVIRONMENT` is the calm dry day.
|
|
139
|
+
|
|
124
140
|
## API
|
|
125
141
|
|
|
126
142
|
Everything is exported from the package root; see `src/*.ts` for the doc comments. Units: metres, seconds, joules, millimetres (calibre), degrees Celsius.
|
|
@@ -137,6 +153,7 @@ antes definiam, então `import { Hit } from '@adatechnology/game-destruction'` c
|
|
|
137
153
|
- **Impacto**: `IMPACT_CONTRACT` e `checkHit(hit)` dizem quais campos cada tipo de dano exige.
|
|
138
154
|
- **Armas**: `Shot`, `Use` e `shotToHit(shot, ponto)`.
|
|
139
155
|
- **Fluidos**: `ContainerSpec`, `LiquidId` e `ItemUseDef`.
|
|
156
|
+
- **Som e clima**: `ModuleHost.audio?` (`AudioSink`) e `ModuleHost.environment?` (`EnvironmentBus` com `EnvironmentState`), emprestados pela sala; `createEnvironmentBus()` e `DEFAULT_ENVIRONMENT` (o dia seco e calmo).
|
|
140
157
|
- **Animações**: `AnimationClipSpec` e `checkAnimation(clipe, idsDasPeças)` (uma porta `open`, uma pistola `reload`).
|
|
141
158
|
- **Módulo**: `TestableModule` entrega itens, animações e cenários à sala de testes; `room.loadModule(modulo)` roda as verificações e escreve linhas
|
|
142
159
|
✓ / ✗ no log. Assim o módulo de um carro de aluguel já nasce com as suas animações e o seu jeito de quebrar.
|
package/dist/index.d.ts
CHANGED
|
@@ -83,6 +83,8 @@ interface PartSpec {
|
|
|
83
83
|
* Each part goes off once. Without `chain`, it explodes only when destroyed (or at `atC`).
|
|
84
84
|
*/
|
|
85
85
|
chain?: ExplosionChain;
|
|
86
|
+
/** Id of the effect the host plays for the blast (`explosion_fuel_barrel`); without it the host picks a generic one by `energy` and `radius`. */
|
|
87
|
+
effect?: string;
|
|
86
88
|
};
|
|
87
89
|
/** Fixed to the world (a wall-mounted cabinet, a tree trunk in the ground). Groups that keep an anchored part stay in place. */
|
|
88
90
|
anchored?: boolean;
|
|
@@ -453,6 +455,7 @@ type DestructionEvent = (EventBase & {
|
|
|
453
455
|
point: Vec3;
|
|
454
456
|
energy: number;
|
|
455
457
|
radius: number;
|
|
458
|
+
effect?: string;
|
|
456
459
|
})
|
|
457
460
|
/** An explosive part was set off by a bullet, fire or a nearby blast and will explode `delay` seconds from now (`explodes.chain`). */
|
|
458
461
|
| (EventBase & {
|
|
@@ -820,50 +823,6 @@ interface ItemUseDef {
|
|
|
820
823
|
liquids?: LiquidId[];
|
|
821
824
|
}
|
|
822
825
|
|
|
823
|
-
/** Pose of one part (or of the whole item) at a key: position offset in metres and Euler XYZ rotation in radians, both relative to the rest pose. */
|
|
824
|
-
interface AnimationPose {
|
|
825
|
-
position?: Vec3;
|
|
826
|
-
rotation?: Vec3;
|
|
827
|
-
}
|
|
828
|
-
interface AnimationKey {
|
|
829
|
-
/** Seconds from the start of the clip. */
|
|
830
|
-
t: number;
|
|
831
|
-
/** Pose of the item's root. */
|
|
832
|
-
position?: Vec3;
|
|
833
|
-
rotation?: Vec3;
|
|
834
|
-
/** Poses of named parts: the key is a destruction part id (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */
|
|
835
|
-
parts?: Record<string, AnimationPose>;
|
|
836
|
-
}
|
|
837
|
-
interface AnimationEventSpec {
|
|
838
|
-
/** Seconds from the start of the clip. */
|
|
839
|
-
t: number;
|
|
840
|
-
/** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */
|
|
841
|
-
type: string;
|
|
842
|
-
data?: Record<string, unknown>;
|
|
843
|
-
}
|
|
844
|
-
/** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */
|
|
845
|
-
interface AnimationClipSpec {
|
|
846
|
-
/** snake_case: `reload`, `open`, `drink`... Unique within the item. */
|
|
847
|
-
id: string;
|
|
848
|
-
/** Seconds. */
|
|
849
|
-
duration: number;
|
|
850
|
-
keys: AnimationKey[];
|
|
851
|
-
events?: AnimationEventSpec[];
|
|
852
|
-
}
|
|
853
|
-
/** What an item exposes when it brings its own animations. */
|
|
854
|
-
interface ItemAnimationContract {
|
|
855
|
-
animations: AnimationClipSpec[];
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Problems with a clip (empty when it is valid): a snake_case id, a positive finite duration, at least one key, key and event times inside
|
|
859
|
-
* `[0, duration]` and not going backwards, finite vectors, every named part one of `partIds`, event types that are non-empty strings and
|
|
860
|
-
* JSON-serializable event data.
|
|
861
|
-
*/
|
|
862
|
-
declare const checkAnimation: (clip: AnimationClipSpec, partIds: Iterable<string>) => {
|
|
863
|
-
ok: boolean;
|
|
864
|
-
problems: string[];
|
|
865
|
-
};
|
|
866
|
-
|
|
867
826
|
/**
|
|
868
827
|
* 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
|
|
869
828
|
* every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.
|
|
@@ -872,6 +831,8 @@ type ControlValue = number | boolean | string;
|
|
|
872
831
|
interface ChoiceOption {
|
|
873
832
|
readonly id: string;
|
|
874
833
|
readonly name: string;
|
|
834
|
+
/** Heading the host lists the option under (`optgroup`, a grouped row of buttons). Options of a group must sit next to each other. */
|
|
835
|
+
readonly group?: string;
|
|
875
836
|
}
|
|
876
837
|
interface ControlBase {
|
|
877
838
|
/** snake_case, unique across the module's controls, layers and metrics. */
|
|
@@ -948,6 +909,41 @@ interface ModuleBench {
|
|
|
948
909
|
patch(id: string, patch: ControlPatch): void;
|
|
949
910
|
/** Move the host camera (a near / middle / far choice). */
|
|
950
911
|
look(eye: Vec3, target: Vec3): void;
|
|
912
|
+
/** Shows a sheet on the host's panel, or takes it down with `null`. A host without a sheet area ignores it. */
|
|
913
|
+
sheet?(sheet: BenchSheet | null): void;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* A card of text the module puts on the host's panel (the sheet of an effect, of a weapon): what it is, its numbers, its steps, its colours and,
|
|
917
|
+
* optionally, a strip of segments over time. Names and texts are already translated for the host's locale.
|
|
918
|
+
*/
|
|
919
|
+
interface BenchSheet {
|
|
920
|
+
readonly kicker?: string;
|
|
921
|
+
readonly title: string;
|
|
922
|
+
readonly body?: string;
|
|
923
|
+
readonly facts?: readonly {
|
|
924
|
+
readonly label: string;
|
|
925
|
+
readonly value: string;
|
|
926
|
+
}[];
|
|
927
|
+
readonly steps?: readonly {
|
|
928
|
+
readonly at: string;
|
|
929
|
+
readonly text: string;
|
|
930
|
+
}[];
|
|
931
|
+
readonly swatches?: readonly {
|
|
932
|
+
readonly color: string;
|
|
933
|
+
readonly name: string;
|
|
934
|
+
}[];
|
|
935
|
+
readonly note?: string;
|
|
936
|
+
/** A strip of named segments (seconds), with tick marks and a playhead that follows the module metric `metric`. */
|
|
937
|
+
readonly timeline?: {
|
|
938
|
+
readonly segments: readonly {
|
|
939
|
+
readonly name: string;
|
|
940
|
+
readonly start: number;
|
|
941
|
+
readonly end: number;
|
|
942
|
+
readonly color: string;
|
|
943
|
+
}[];
|
|
944
|
+
readonly ticks: readonly number[];
|
|
945
|
+
readonly metric: string;
|
|
946
|
+
};
|
|
951
947
|
}
|
|
952
948
|
interface BenchReport {
|
|
953
949
|
readonly ok: boolean;
|
|
@@ -958,9 +954,169 @@ interface BenchDeclaration {
|
|
|
958
954
|
readonly layers?: readonly ModuleLayer[];
|
|
959
955
|
readonly metrics?: readonly ModuleMetric[];
|
|
960
956
|
}
|
|
961
|
-
/** snake_case ids unique across controls, layers and metrics; min < max; step > 0; default in range; options non-empty, unique, default among them; metric max > 0. */
|
|
957
|
+
/** snake_case ids unique across controls, layers and metrics; min < max; step > 0; default in range; options non-empty, unique, default among them, each group in one run; metric max > 0. */
|
|
962
958
|
declare function checkBench(declared: BenchDeclaration): BenchReport;
|
|
963
959
|
|
|
960
|
+
/** What a module asks the host to show: an effect by id (`explosion_m67`, `impact_concrete`, `muzzle_ak`), where, and how. */
|
|
961
|
+
interface EffectRequest {
|
|
962
|
+
/** An id of the host's effect catalogue (`EffectSink.catalog`). A host that does not know it plays nothing. */
|
|
963
|
+
readonly effect: string;
|
|
964
|
+
readonly point: Vec3;
|
|
965
|
+
/** Where the effect is aimed (a cone, a jet). */
|
|
966
|
+
readonly direction?: Vec3;
|
|
967
|
+
/** The surface normal at `point` (an impact, a decal). */
|
|
968
|
+
readonly normal?: Vec3;
|
|
969
|
+
/** Makes the visual repeatable: the same seed draws the same effect, so a network event of a few bytes is enough on every client. */
|
|
970
|
+
readonly seed?: number;
|
|
971
|
+
/** The variation of the effect (an age, a material, a condition): an option index or an option id of the effect. */
|
|
972
|
+
readonly param?: number | string;
|
|
973
|
+
/** Multiplies the effect's size and amount. */
|
|
974
|
+
readonly scale?: number;
|
|
975
|
+
}
|
|
976
|
+
/** Identifies a running effect so it can be stopped. */
|
|
977
|
+
type EffectHandle = number;
|
|
978
|
+
/**
|
|
979
|
+
* What the host lends a module to show effects (smoke, sparks, blast, blood mist) without bringing its own particles. Nothing here is specific
|
|
980
|
+
* to a kind of effect or to a renderer.
|
|
981
|
+
*/
|
|
982
|
+
interface EffectSink {
|
|
983
|
+
/** Starts the effect; `null` when the host does not know it, the layer is off or the budget is spent. */
|
|
984
|
+
play(request: EffectRequest): EffectHandle | null;
|
|
985
|
+
/** Ends a looping effect now (a fire, a trail). A handle that already ended is ignored. */
|
|
986
|
+
stop(handle: EffectHandle): void;
|
|
987
|
+
/** 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. */
|
|
988
|
+
configure?(settings: Readonly<Record<string, ControlValue>>): void;
|
|
989
|
+
/** Counters of what is alive (`puffs`, `sparks`, `lights`...), for a bench. */
|
|
990
|
+
stats?(): Readonly<Record<string, number>>;
|
|
991
|
+
/** The ids `play` accepts. */
|
|
992
|
+
catalog?(): readonly string[];
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/** Pose of one part (or of the whole item) at a key: position offset in metres and Euler XYZ rotation in radians, both relative to the rest pose. */
|
|
996
|
+
interface AnimationPose {
|
|
997
|
+
position?: Vec3;
|
|
998
|
+
rotation?: Vec3;
|
|
999
|
+
}
|
|
1000
|
+
interface AnimationKey {
|
|
1001
|
+
/** Seconds from the start of the clip. */
|
|
1002
|
+
t: number;
|
|
1003
|
+
/** Pose of the item's root. */
|
|
1004
|
+
position?: Vec3;
|
|
1005
|
+
rotation?: Vec3;
|
|
1006
|
+
/** Poses of named parts: the key is a destruction part id (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */
|
|
1007
|
+
parts?: Record<string, AnimationPose>;
|
|
1008
|
+
}
|
|
1009
|
+
interface AnimationEventSpec {
|
|
1010
|
+
/** Seconds from the start of the clip. */
|
|
1011
|
+
t: number;
|
|
1012
|
+
/** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */
|
|
1013
|
+
type: string;
|
|
1014
|
+
data?: Record<string, unknown>;
|
|
1015
|
+
}
|
|
1016
|
+
/** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */
|
|
1017
|
+
interface AnimationClipSpec {
|
|
1018
|
+
/** snake_case: `reload`, `open`, `drink`... Unique within the item. */
|
|
1019
|
+
id: string;
|
|
1020
|
+
/** Seconds. */
|
|
1021
|
+
duration: number;
|
|
1022
|
+
keys: AnimationKey[];
|
|
1023
|
+
events?: AnimationEventSpec[];
|
|
1024
|
+
}
|
|
1025
|
+
/** What an item exposes when it brings its own animations. */
|
|
1026
|
+
interface ItemAnimationContract {
|
|
1027
|
+
animations: AnimationClipSpec[];
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Problems with a clip (empty when it is valid): a snake_case id, a positive finite duration, at least one key, key and event times inside
|
|
1031
|
+
* `[0, duration]` and not going backwards, finite vectors, every named part one of `partIds`, event types that are non-empty strings and
|
|
1032
|
+
* JSON-serializable event data.
|
|
1033
|
+
*/
|
|
1034
|
+
declare const checkAnimation: (clip: AnimationClipSpec, partIds: Iterable<string>) => {
|
|
1035
|
+
ok: boolean;
|
|
1036
|
+
problems: string[];
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
/** One sound a module asks the host to play. `sound` is a key in a sound bank the host knows (`weapon_shot`, `glass_break`...); a host that does not know it plays nothing. */
|
|
1040
|
+
interface SoundRequest {
|
|
1041
|
+
readonly sound: string;
|
|
1042
|
+
/** World position; without it the sound is heard at the listener (no distance, no pan). */
|
|
1043
|
+
readonly position?: Vec3;
|
|
1044
|
+
/** 0..1 level before distance falloff. Default 1. */
|
|
1045
|
+
readonly volume?: number;
|
|
1046
|
+
/** Named numbers the sound's recipe reads (a caliber's energy, a zombie's pitch...). */
|
|
1047
|
+
readonly params?: Readonly<Record<string, number>>;
|
|
1048
|
+
/** Seed for the sound's own random stream: same seed, same sound. */
|
|
1049
|
+
readonly seed?: number;
|
|
1050
|
+
}
|
|
1051
|
+
/** What `AudioSink.play` hands back; opaque to the module, given back to `stop`. */
|
|
1052
|
+
interface SoundHandle {
|
|
1053
|
+
readonly id: number;
|
|
1054
|
+
}
|
|
1055
|
+
/** A continuous sound (rain, an engine, a crowd): created once, then driven by `set`. */
|
|
1056
|
+
interface LoopHandle {
|
|
1057
|
+
set(state: {
|
|
1058
|
+
readonly volume?: number;
|
|
1059
|
+
readonly frequency?: number;
|
|
1060
|
+
readonly params?: Readonly<Record<string, number>>;
|
|
1061
|
+
readonly position?: Vec3;
|
|
1062
|
+
}): void;
|
|
1063
|
+
stop(): void;
|
|
1064
|
+
}
|
|
1065
|
+
interface AudioListener {
|
|
1066
|
+
readonly position: Vec3;
|
|
1067
|
+
/** Heading in radians around the y axis. */
|
|
1068
|
+
readonly yaw: number;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* The sound the host lends a module (`ModuleHost.audio`): one shared engine, so every module is mixed, limited and panned the same way.
|
|
1072
|
+
* Every method may do nothing (no AudioContext, muted, unknown sound), and a module must work without a sink.
|
|
1073
|
+
*/
|
|
1074
|
+
interface AudioSink {
|
|
1075
|
+
play(request: SoundRequest): SoundHandle | null;
|
|
1076
|
+
stop(handle: SoundHandle): void;
|
|
1077
|
+
/** Starts a looping sound; a host without loops leaves it out. */
|
|
1078
|
+
loop?(sound: string, options?: {
|
|
1079
|
+
readonly position?: Vec3;
|
|
1080
|
+
}): LoopHandle;
|
|
1081
|
+
setListener?(listener: AudioListener): void;
|
|
1082
|
+
/** Adds a sound bank to the host's engine. `bank` is the engine's own type, `unknown` here because the contracts do not know it. */
|
|
1083
|
+
register?(bank: unknown): void;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/** The weather, light and ground as the world is right now. Written by whoever drives the weather, read by whoever reacts to it. */
|
|
1087
|
+
interface EnvironmentState {
|
|
1088
|
+
/** Rain in mm/h; 0 is dry. */
|
|
1089
|
+
readonly rain: number;
|
|
1090
|
+
/** Wind in m/s along x and z, and its strength (the length of that vector). */
|
|
1091
|
+
readonly windX: number;
|
|
1092
|
+
readonly windZ: number;
|
|
1093
|
+
readonly windSpeed: number;
|
|
1094
|
+
/** Strongest gust in m/s. */
|
|
1095
|
+
readonly gust: number;
|
|
1096
|
+
/** Render fog far distance in metres. */
|
|
1097
|
+
readonly fogFar: number;
|
|
1098
|
+
/** How far one can see for gameplay, in metres (not the render fog). */
|
|
1099
|
+
readonly visibility: number;
|
|
1100
|
+
/** 0 is full daylight, 1 is the darkest night. */
|
|
1101
|
+
readonly nightFactor: number;
|
|
1102
|
+
readonly temperatureC: number;
|
|
1103
|
+
/** Wet ground, 0..1: rises with rain and dries after it stops. */
|
|
1104
|
+
readonly wetness: number;
|
|
1105
|
+
}
|
|
1106
|
+
type EnvironmentKey = keyof EnvironmentState;
|
|
1107
|
+
/** A calm dry day (the normal world, before anything went wrong). */
|
|
1108
|
+
declare const DEFAULT_ENVIRONMENT: EnvironmentState;
|
|
1109
|
+
/** The host's environment (`ModuleHost.environment`): a store the weather module writes to and the others read. */
|
|
1110
|
+
interface EnvironmentBus {
|
|
1111
|
+
get(): EnvironmentState;
|
|
1112
|
+
/** Merges `patch` into the state; listeners hear it only when a value really changed. */
|
|
1113
|
+
set(patch: Partial<EnvironmentState>): void;
|
|
1114
|
+
/** Hears every change with the keys that changed. Returns the unsubscribe function. */
|
|
1115
|
+
on(listener: (state: EnvironmentState, changed: readonly EnvironmentKey[]) => void): () => void;
|
|
1116
|
+
}
|
|
1117
|
+
/** A plain store that implements the bus; the test room and tests use it, a game may bring its own. */
|
|
1118
|
+
declare function createEnvironmentBus(initial?: Partial<EnvironmentState>): EnvironmentBus;
|
|
1119
|
+
|
|
964
1120
|
/** An item that holds a liquid (a cup, a bottle, a fuel tank). `part` is the destruction part that is the container; a hole in it makes it leak. */
|
|
965
1121
|
interface ModuleItemFluid {
|
|
966
1122
|
container: ContainerSpec;
|
|
@@ -1059,6 +1215,12 @@ type HostEvent =
|
|
|
1059
1215
|
rounds?: number;
|
|
1060
1216
|
mode?: string;
|
|
1061
1217
|
suppressed?: boolean;
|
|
1218
|
+
/**
|
|
1219
|
+
* Where the muzzle is in the world and where it points, for a shot the viewer sees from outside (another player, the third-person camera). A module
|
|
1220
|
+
* then shows the shooter's flash and casing through `host.effects`. Absent for the player's own shot, which the weapon in the hands already draws.
|
|
1221
|
+
*/
|
|
1222
|
+
origin?: Vec3;
|
|
1223
|
+
direction?: Vec3;
|
|
1062
1224
|
}
|
|
1063
1225
|
/** The trigger was pulled, the round was spent and nothing left the tool (a dud cartridge): no projectile, no recoil. */
|
|
1064
1226
|
| {
|
|
@@ -1153,7 +1315,10 @@ type HostEvent =
|
|
|
1153
1315
|
on: boolean;
|
|
1154
1316
|
duration?: number;
|
|
1155
1317
|
}
|
|
1156
|
-
/**
|
|
1318
|
+
/**
|
|
1319
|
+
* Something blew up: a thrown or launched explosive, a charge, or a part of the world (a fuel barrel); `source` is the weapon or the object, `cause` what set it off.
|
|
1320
|
+
* `explosive` is the id of the explosive when there is one (`m67`, `c4`, `fuel_barrel`): the host picks the blast by it.
|
|
1321
|
+
*/
|
|
1157
1322
|
| {
|
|
1158
1323
|
type: 'explosion';
|
|
1159
1324
|
source: string;
|
|
@@ -1161,6 +1326,7 @@ type HostEvent =
|
|
|
1161
1326
|
energy: number;
|
|
1162
1327
|
radius: number;
|
|
1163
1328
|
cause: string;
|
|
1329
|
+
explosive?: string;
|
|
1164
1330
|
};
|
|
1165
1331
|
/** The tool in hand as the host sees it. */
|
|
1166
1332
|
interface HostTool {
|
|
@@ -1250,6 +1416,12 @@ interface ModuleHost {
|
|
|
1250
1416
|
command?(command: ToolCommand): ToolCommandResult;
|
|
1251
1417
|
/** Lent only to a module that declares controls, layers or metrics. */
|
|
1252
1418
|
bench?: ModuleBench;
|
|
1419
|
+
/** The host's shared sound engine; a module that makes sound uses it when lent and stays silent (or brings its own) when not. */
|
|
1420
|
+
audio?: AudioSink;
|
|
1421
|
+
/** The host's weather, light and ground; a module that reacts to rain, wind or night reads it. */
|
|
1422
|
+
environment?: EnvironmentBus;
|
|
1423
|
+
/** The host's effects (smoke, sparks, blast, impacts): a module asks for them by id instead of drawing its own. Absent when the host has none. */
|
|
1424
|
+
effects?: EffectSink;
|
|
1253
1425
|
}
|
|
1254
1426
|
/** What a module hands the test room: its items and, optionally, scenarios that exercise them, its area and its own presentation. */
|
|
1255
1427
|
interface TestableModule<Context = unknown> {
|
|
@@ -1371,4 +1543,4 @@ declare const mergeById: <TEntry extends Entry>(shipped: readonly TEntry[], extr
|
|
|
1371
1543
|
declare const applyPatches: <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, DeepPartial<TEntry> | Partial<TEntry>>> | undefined) => void;
|
|
1372
1544
|
declare const removeIds: (entries: Map<string, unknown>, ids: readonly string[] | undefined) => void;
|
|
1373
1545
|
|
|
1374
|
-
export { type ActionControl, type AnimationClipSpec, type AnimationEventSpec, type AnimationKey, type AnimationPose, type AreaPlacement, type BenchChange, type BenchDeclaration, type BenchReport, type BodyRegion, type BodySlot, type BooleanControl, type ChainDelay, type CheckOptions, type CheckSpecOptions, type ChoiceControl, type ChoiceOption, type ContainerSpec, type ControlPatch, type ControlValue, type Crack, type DamageStage, type DamageType, type DebrisKind, type DeepPartial, type Dent, type DestructibleImpl, type DestructibleSpec, type DestructionEvent, type DestructionEventType, type ExplosionChain, type FluidKind, type FractureStyle, type Fragment, type FragmentMark, type Hit, type HitResult, type Hole, type HostEvent, type HostTool, type HostToolAttachment, IMPACT_CONTRACT, type ImpactContract, type ItemAnimationContract, type ItemUseDef, type JointKind, type JointSpec, type JointState, type LiquidId, 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 Quat, type RayHit, type Reinforcement, STAGE_EVENT, type SegmentKind, type Shot, type SpecReport, type StructureRole, type SurfaceLayer, TOOL_CLIP, 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 Use, type Vec3, applyPatches, checkAnimation, checkBench, checkDestructible, checkHit, checkSpec, checkToolClips, createToolClipPlayer, deepMerge, mergeById, removeIds, sampleClip, scaleClip, shotToHit, toolAnimationStep };
|
|
1546
|
+
export { type ActionControl, type AnimationClipSpec, type AnimationEventSpec, type AnimationKey, type AnimationPose, type AreaPlacement, type AudioListener, type AudioSink, type BenchChange, type BenchDeclaration, type BenchReport, type BenchSheet, type BodyRegion, type BodySlot, type BooleanControl, type ChainDelay, type CheckOptions, type CheckSpecOptions, type ChoiceControl, type ChoiceOption, type ContainerSpec, type ControlPatch, type ControlValue, type Crack, DEFAULT_ENVIRONMENT, 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 FractureStyle, type Fragment, type FragmentMark, type Hit, type HitResult, type Hole, type HostEvent, type HostTool, type HostToolAttachment, IMPACT_CONTRACT, type ImpactContract, type ItemAnimationContract, type ItemUseDef, type JointKind, type JointSpec, type JointState, 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 Quat, type RayHit, type Reinforcement, STAGE_EVENT, type SegmentKind, type Shot, type SoundHandle, type SoundRequest, type SpecReport, type StructureRole, type SurfaceLayer, TOOL_CLIP, 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 Use, type Vec3, applyPatches, checkAnimation, checkBench, checkDestructible, checkHit, checkSpec, checkToolClips, createEnvironmentBus, createToolClipPlayer, deepMerge, mergeById, removeIds, sampleClip, scaleClip, shotToHit, toolAnimationStep };
|
package/dist/index.global.js
CHANGED
|
@@ -21,6 +21,7 @@ var GameContracts = (() => {
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
+
DEFAULT_ENVIRONMENT: () => DEFAULT_ENVIRONMENT,
|
|
24
25
|
IMPACT_CONTRACT: () => IMPACT_CONTRACT,
|
|
25
26
|
MISSING_BITS: () => MISSING_BITS,
|
|
26
27
|
STAGE_EVENT: () => STAGE_EVENT,
|
|
@@ -32,6 +33,7 @@ var GameContracts = (() => {
|
|
|
32
33
|
checkHit: () => checkHit,
|
|
33
34
|
checkSpec: () => checkSpec,
|
|
34
35
|
checkToolClips: () => checkToolClips,
|
|
36
|
+
createEnvironmentBus: () => createEnvironmentBus,
|
|
35
37
|
createToolClipPlayer: () => createToolClipPlayer,
|
|
36
38
|
deepMerge: () => deepMerge,
|
|
37
39
|
mergeById: () => mergeById,
|
|
@@ -550,9 +552,15 @@ var GameContracts = (() => {
|
|
|
550
552
|
return;
|
|
551
553
|
}
|
|
552
554
|
const seen = /* @__PURE__ */ new Set();
|
|
555
|
+
const groups = /* @__PURE__ */ new Set();
|
|
556
|
+
let previousGroup;
|
|
553
557
|
for (const option of control.options) {
|
|
554
558
|
if (seen.has(option.id)) problems.push(`${label} repeats option "${option.id}"`);
|
|
555
559
|
seen.add(option.id);
|
|
560
|
+
if (option.group !== void 0 && (typeof option.group !== "string" || option.group === "")) problems.push(`${label} option "${option.id}" has an empty group`);
|
|
561
|
+
else if (option.group !== void 0 && option.group !== previousGroup && groups.has(option.group)) problems.push(`${label} group "${option.group}" is split: its options must be next to each other`);
|
|
562
|
+
if (typeof option.group === "string" && option.group !== "") groups.add(option.group);
|
|
563
|
+
previousGroup = option.group;
|
|
556
564
|
}
|
|
557
565
|
if (!seen.has(control.default)) problems.push(`${label} default "${control.default}" is not among its options`);
|
|
558
566
|
}
|
|
@@ -575,6 +583,39 @@ var GameContracts = (() => {
|
|
|
575
583
|
}
|
|
576
584
|
return { ok: problems.length === 0, problems };
|
|
577
585
|
}
|
|
586
|
+
|
|
587
|
+
// src/environment.ts
|
|
588
|
+
var DEFAULT_ENVIRONMENT = {
|
|
589
|
+
rain: 0,
|
|
590
|
+
windX: 0,
|
|
591
|
+
windZ: 0,
|
|
592
|
+
windSpeed: 0,
|
|
593
|
+
gust: 0,
|
|
594
|
+
fogFar: 220,
|
|
595
|
+
visibility: 220,
|
|
596
|
+
nightFactor: 0,
|
|
597
|
+
temperatureC: 20,
|
|
598
|
+
wetness: 0
|
|
599
|
+
};
|
|
600
|
+
function createEnvironmentBus(initial = {}) {
|
|
601
|
+
let state = { ...DEFAULT_ENVIRONMENT, ...initial };
|
|
602
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
603
|
+
return {
|
|
604
|
+
get: () => state,
|
|
605
|
+
set(patch) {
|
|
606
|
+
const changed = Object.keys(patch).filter((key) => patch[key] !== void 0 && patch[key] !== state[key]);
|
|
607
|
+
if (changed.length === 0) return;
|
|
608
|
+
const next = { ...state };
|
|
609
|
+
for (const key of changed) next[key] = patch[key];
|
|
610
|
+
state = next;
|
|
611
|
+
for (const listener of [...listeners]) listener(state, changed);
|
|
612
|
+
},
|
|
613
|
+
on(listener) {
|
|
614
|
+
listeners.add(listener);
|
|
615
|
+
return () => void listeners.delete(listener);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
}
|
|
578
619
|
return __toCommonJS(index_exports);
|
|
579
620
|
})();
|
|
580
621
|
//# sourceMappingURL=index.global.js.map
|
package/dist/index.global.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/impact.ts","../src/body.ts","../src/spec.ts","../src/weapons.ts","../src/animation.ts","../src/tool-animation.ts","../src/registry.ts","../src/bench.ts"],"sourcesContent":["/**\n * @adatechnology/game-contracts: the contracts every game module (vehicles, furniture, plants, houses, weapons) depends on, and nothing else.\n * Types plus tiny pure checkers, no runtime dependencies. The default destruction world, the renderers and the test room are optional.\n */\nexport type {\n ChainDelay,\n Crack,\n DamageStage,\n DamageType,\n DebrisKind,\n Dent,\n DestructibleSpec,\n ExplosionChain,\n DestructionEvent,\n DestructionEventType,\n FluidKind,\n Fragment,\n FragmentMark,\n FractureStyle,\n Hit,\n HitResult,\n Hole,\n JointKind,\n JointSpec,\n JointState,\n MaterialId,\n ObjectState,\n PartShape,\n PartSpec,\n PartBreach,\n PartState,\n PartStatus,\n PartUnits,\n Quat,\n RayHit,\n Reinforcement,\n StructureRole,\n SurfaceLayer,\n ToolKind,\n Vec3,\n} from './destruction'\nexport { IMPACT_CONTRACT, checkDestructible, checkHit } from './impact'\nexport type { CheckOptions, DestructibleImpl, ImpactContract } from './impact'\nexport { MISSING_BITS } from './body'\nexport type { BodyRegion, BodySlot, SegmentKind } from './body'\nexport { checkSpec } from './spec'\nexport type { CheckSpecOptions, SpecReport } from './spec'\nexport { shotToHit } from './weapons'\nexport type { Shot, Use } from './weapons'\nexport type { ContainerSpec, ItemUseDef, LiquidId } from './fluids'\nexport { checkAnimation } from './animation'\nexport type { AnimationClipSpec, AnimationEventSpec, AnimationKey, AnimationPose, ItemAnimationContract } from './animation'\nexport { STAGE_EVENT, TOOL_CLIP, checkToolClips, createToolClipPlayer, sampleClip, scaleClip, toolAnimationStep } from './tool-animation'\nexport type { ToolAnimationStep, ToolAnimator, ToolClipId, ToolClipPlayer, ToolClipPlayerOptions, ToolClipSet, ToolRig } from './tool-animation'\nexport type { AreaPlacement, HostEvent, HostTool, HostToolAttachment, ModuleArea, ModuleHost, ModuleItemFluid, ModuleScenario, TestableItem, TestableModule, Thumbnail, ToolCommand, ToolCommandResult } from './module'\nexport { applyPatches, deepMerge, mergeById, removeIds } from './registry'\nexport type { DeepPartial } from './registry'\nexport { checkBench } from './bench'\nexport type {\n ActionControl,\n BenchChange,\n BenchDeclaration,\n BenchReport,\n BooleanControl,\n ChoiceControl,\n ChoiceOption,\n ControlPatch,\n ControlValue,\n ModuleBench,\n ModuleControl,\n ModuleLayer,\n ModuleMetric,\n NumberControl,\n} from './bench'\n","import type { DamageType, DestructibleSpec, DestructionEvent, Hit, JointSpec, ObjectState, ToolKind, Vec3 } from './destruction'\n\n/**\n * What a game module implements for an object whose behaviour it owns (a vehicle with its own panel deformation, a creature, a machine). The\n * world (`DestructionWorld.register`) hosts it next to its own objects: raycast, shoot, explode, hit, disassemble, extinguish, update, state,\n * reset and `on` all route to it.\n *\n * Space: the impl receives hits in WORLD space and returns events in WORLD space (it owns its transform; the transform given to `register` or\n * `setTransform` is only used by the world for raycasts and blast range). Every event must carry `object === id` and name parts listed by\n * `describe()`.\n */\nexport interface DestructibleImpl {\n readonly id: string\n readonly name?: string\n /** Parts, joints and structure in the impl's local space, for raycast, inspector and log. Called often: keep it cheap. */\n describe(): DestructibleSpec\n /** Apply one hit (see `IMPACT_CONTRACT` for the fields per type) and return the events it caused. */\n hit(hit: Hit): DestructionEvent[]\n /** Advance fire, melting, collapse and other timed behaviour. */\n update?(dt: number): DestructionEvent[]\n state(): ObjectState\n reset?(): void\n /** Same contract as `DestructionWorld.disassemble`: progress 0..1, or -1 when the tool cannot undo the joint. */\n disassemble?(jointId: string, tool: ToolKind, dt: number): number\n extinguish?(partId?: string): void\n /** Optional: an animation moved a part; `position` / `rotation` (Euler XYZ, radians) are object-local. Keep raycast and hit shapes in sync. */\n setPartPose?(partId: string, position: Vec3, rotation?: Vec3): void\n /** Plain JSON of the impl's own state, stored in `DestructionSnapshot.impls`. */\n serialize?(): unknown\n load?(data: unknown): void\n}\n\n/** The fields a weapon or hazard must put in a `Hit`, per damage type. THE impact contract. */\nexport interface ImpactContract {\n required: string[]\n optional: string[]\n note: string\n}\n\nexport const IMPACT_CONTRACT: Record<DamageType, ImpactContract> = {\n ballistic: { required: ['type', 'point', 'direction', 'energy', 'caliber'], optional: ['space', 'source'], note: 'bullets and pellets: joules and diameter in millimetres' },\n blunt: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'contact radius in metres (default 3 cm)' },\n slash: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'axes, knives, machetes' },\n explosive: { required: ['type', 'point', 'energy', 'radius'], optional: ['space', 'source'], note: 'point is the centre of the blast, radius the range in metres' },\n fire: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'flame temperature in °C; duration in seconds (call every frame with `dt`)' },\n heat: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'heat without flame (cutting torch, hot plate)' },\n}\n\n/** Problems with a hit according to the impact contract (empty when it is valid). */\nexport const checkHit = (hit: Hit): string[] => {\n const c = IMPACT_CONTRACT[hit.type]\n if (!c) return [`unknown damage type \"${String(hit.type)}\"`]\n const out: string[] = []\n for (const f of c.required) {\n const v = (hit as unknown as Record<string, unknown>)[f]\n if (v === undefined || (Array.isArray(v) ? v.some((n) => !Number.isFinite(n)) : typeof v === 'number' && !Number.isFinite(v))) out.push(`${hit.type} hit needs \"${f}\"`)\n }\n return out\n}\n\nexport interface CheckOptions {\n /**\n * Ids of every material the impl may use (the contracts package does not know the materials of your world: pass the ids of\n * `world.materials`, or the built-in plus your own). When omitted, the material check is skipped.\n */\n materials?: Iterable<string>\n}\n\nconst finiteJson = (v: unknown, path: string, out: string[]) => {\n if (typeof v === 'number') {\n if (!Number.isFinite(v)) out.push(`${path} is not a finite number`)\n } else if (Array.isArray(v)) v.forEach((x, i) => finiteJson(x, `${path}[${i}]`, out))\n else if (v && typeof v === 'object') for (const [k, x] of Object.entries(v)) finiteJson(x, `${path}.${k}`, out)\n else if (typeof v === 'function' || typeof v === 'symbol' || typeof v === 'bigint') out.push(`${path} is not JSON`)\n}\n\n/**\n * Contract checker for the test room (it needs no world): `describe()` is valid, a 500 J ballistic hit at a part centre returns well-formed events of this impl, `state()`\n * matches `describe()`, hit points never go negative, `update(0.016)` returns an array and every event is JSON-serializable. It hits the impl\n * (and calls `reset()` afterwards when there is one).\n */\nexport const checkDestructible = (impl: DestructibleImpl, options: CheckOptions = {}): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const known = options.materials ? new Set(options.materials) : null\n let spec: DestructibleSpec | undefined\n try {\n spec = impl.describe()\n } catch (e) {\n return { ok: false, problems: [`describe() threw: ${String(e)}`] }\n }\n if (!spec || !Array.isArray(spec.parts) || spec.parts.length === 0) return { ok: false, problems: ['describe() must return at least one part'] }\n const ids = new Set<string>()\n for (const p of spec.parts) {\n if (ids.has(p.id)) problems.push(`duplicate part id \"${p.id}\"`)\n ids.add(p.id)\n if (known && !known.has(p.material)) problems.push(`part \"${p.id}\" has unknown material \"${p.material}\"`)\n if (!p.shape || !['box', 'cylinder', 'sphere'].includes(p.shape.kind)) problems.push(`part \"${p.id}\" has no valid shape`)\n if (!Array.isArray(p.position) || p.position.length !== 3) problems.push(`part \"${p.id}\" has no position`)\n }\n const jointIds = new Set<string>()\n for (const j of (spec.joints ?? []) as JointSpec[]) {\n if (!ids.has(j.a) || !ids.has(j.b)) problems.push(`joint \"${j.id ?? `${j.a}~${j.b}`}\" references an unknown part`)\n const jid = j.id ?? `${j.a}~${j.b}`\n if (jointIds.has(jid)) problems.push(`duplicate joint id \"${jid}\"`)\n jointIds.add(jid)\n }\n const checkState = (label: string) => {\n let st: ObjectState\n try {\n st = impl.state()\n } catch (e) {\n problems.push(`state() threw ${label}: ${String(e)}`)\n return\n }\n const sids = st.parts.map((p) => p.id).sort().join(',')\n if (sids !== [...ids].sort().join(',')) problems.push(`state() part ids do not match describe() ${label}`)\n for (const p of st.parts) if (!(p.hp >= 0)) problems.push(`part \"${p.id}\" has negative or invalid hp ${label}`)\n }\n const checkEvents = (events: unknown, label: string) => {\n if (!Array.isArray(events)) {\n problems.push(`${label} must return an array`)\n return\n }\n const evs = events as DestructionEvent[]\n evs.forEach((e, i) => {\n if (e.object !== impl.id) problems.push(`${label} event ${i} (${e.type}) has object \"${String(e.object)}\", expected \"${impl.id}\"`)\n const part = (e as { part?: string }).part\n if (part !== undefined && !ids.has(part)) problems.push(`${label} event ${i} (${e.type}) names unknown part \"${part}\"`)\n })\n const bad: string[] = []\n finiteJson(evs, 'events', bad)\n problems.push(...bad.map((b) => `${label}: ${b}`))\n try {\n const s = JSON.stringify(evs)\n if (JSON.stringify(JSON.parse(s)) !== s) problems.push(`${label} events do not survive a JSON round trip`)\n } catch (e) {\n problems.push(`${label} events are not JSON-serializable: ${String(e)}`)\n }\n }\n checkState('before the hit')\n const target = spec.parts[0]\n try {\n const hit: Hit = { type: 'ballistic', point: [target.position[0], target.position[1], target.position[2]], direction: [0, 0, -1], energy: 500, caliber: 9 }\n checkEvents(impl.hit(hit), 'hit()')\n } catch (e) {\n problems.push(`hit() threw: ${String(e)}`)\n }\n checkState('after the hit')\n if (impl.update) {\n try {\n checkEvents(impl.update(0.016), 'update()')\n } catch (e) {\n problems.push(`update() threw: ${String(e)}`)\n }\n checkState('after update')\n }\n try {\n impl.reset?.()\n } catch (e) {\n problems.push(`reset() threw: ${String(e)}`)\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The slots of a body an item can cover (the item slot vocabulary: game-items imports it from here). */\nexport type BodySlot = 'head' | 'eyes' | 'torso' | 'arms' | 'hands' | 'legs' | 'feet' | 'over'\n\n/** The 11 segments of the detailed body model (the Arsenal ids). */\nexport type SegmentKind = 'head' | 'jaw' | 'neck' | 'torso' | 'pelvis' | 'upperArm' | 'forearm' | 'hand' | 'thigh' | 'shin' | 'foot'\n\n/** The 6 regions of the crowd model (the game regions 0..5). */\nexport type BodyRegion = 'head' | 'torso' | 'armL' | 'armR' | 'legL' | 'legR'\n\n/** Bits of the `missing` mask of a body. The instanced body shader reads them, so the values are part of the contract. */\nexport const MISSING_BITS = {\n head: 1,\n armL: 2,\n armR: 4,\n legL: 8,\n legR: 16,\n armLBroken: 32,\n armRBroken: 64,\n legLBroken: 128,\n legRBroken: 256,\n jaw: 512,\n} as const\n","import type { DestructibleSpec, PartSpec } from './destruction'\n\nexport interface CheckSpecOptions {\n /** Known material ids. When given, every part's material must be one of them. */\n materials?: Iterable<string>\n}\n\nexport interface SpecReport {\n /** False when there is at least one problem (warnings do not count). */\n ok: boolean\n problems: string[]\n /** Things that are legal but probably wrong, e.g. structural parts that cannot reach an anchored part. */\n warnings: string[]\n}\n\nconst STRUCTURE_ROLES = ['foundation', 'column', 'beam', 'slab', 'wall', 'partition', 'roof', 'stair', 'none']\nconst BEARS = [true, false, 'a_on_b', 'b_on_a']\n\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\nconst positive = (n: unknown): boolean => finite(n) && n > 0\nconst vec3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every(finite)\nconst nonNegative = (n: unknown): boolean => n === undefined || (finite(n) && n >= 0)\n\nconst FACES = ['both', '+', '-']\nconst BONDS = ['running', 'stack']\n\n/** Layers, reinforcement, units and breach: known materials, finite positive sizes. */\nconst checkStagedFields = (p: PartSpec | undefined, label: string, materials: Set<string> | null, problems: string[]): void => {\n const known = (m: unknown, what: string): void => {\n if (materials && !materials.has(m as string)) problems.push(`${label} ${what} has unknown material \"${String(m)}\"`)\n }\n ;(p?.layers ?? []).forEach((l, i) => {\n known(l?.material, `layer ${i}`)\n if (!positive(l?.thickness)) problems.push(`${label} layer ${i} thickness must be finite and positive`)\n if (!FACES.includes(l?.faces)) problems.push(`${label} layer ${i} has an invalid faces \"${String(l?.faces)}\"`)\n })\n const r = p?.reinforcement\n if (r) {\n known(r.material, 'reinforcement')\n if (!positive(r.spacing) || !positive(r.diameter)) problems.push(`${label} reinforcement spacing and diameter must be finite and positive`)\n if (r.residualCapacity !== undefined && !(finite(r.residualCapacity) && r.residualCapacity >= 0 && r.residualCapacity <= 1)) problems.push(`${label} reinforcement residualCapacity must be between 0 and 1`)\n }\n const u = p?.units\n if (u) {\n known(u.mortar, 'units mortar')\n if (!vec3(u.size) || !u.size.every(positive)) problems.push(`${label} units size must be three finite positive numbers`)\n if (!BONDS.includes(u.bond)) problems.push(`${label} units has an invalid bond \"${String(u.bond)}\"`)\n }\n if (p?.breach && p.breach.area !== undefined && !positive(p.breach.area)) problems.push(`${label} breach area must be finite and positive`)\n}\n\n/**\n * Static checks on a `DestructibleSpec` (no world needed), for items that give a `spec` instead of an `impl`: unique part ids, known materials\n * (when `options.materials` is given), joints that reference existing, different parts, finite positive sizes, non-negative mass / hp / capacity,\n * valid structure roles and `bears` directions. A structural part with no path of joints to an anchored part is a warning, not a problem: it\n * is allowed, it just falls.\n */\nexport const checkSpec = (spec: DestructibleSpec, options: CheckSpecOptions = {}): SpecReport => {\n const problems: string[] = []\n const warnings: string[] = []\n const materials = options.materials ? new Set(options.materials) : null\n const parts = Array.isArray(spec?.parts) ? spec.parts : []\n if (!Array.isArray(spec?.parts) || parts.length === 0) problems.push('the spec needs at least one part')\n const ids = new Set<string>()\n for (const p of parts) {\n const id = String(p?.id ?? '')\n const label = `part \"${id}\"`\n if (id === '') problems.push('a part has no id')\n else if (ids.has(id)) problems.push(`duplicate part id \"${id}\"`)\n ids.add(id)\n if (materials && !materials.has(p?.material)) problems.push(`${label} has unknown material \"${String(p?.material)}\"`)\n else if (typeof p?.material !== 'string' || p.material === '') problems.push(`${label} needs a material`)\n const s = p?.shape\n if (s?.kind === 'box') {\n if (!Array.isArray(s.size) || s.size.length !== 3 || !s.size.every(positive)) problems.push(`${label} box size must be three finite positive numbers`)\n } else if (s?.kind === 'cylinder') {\n if (!positive(s.radius) || !positive(s.height)) problems.push(`${label} cylinder radius and height must be finite and positive`)\n } else if (s?.kind === 'sphere') {\n if (!positive(s.radius)) problems.push(`${label} sphere radius must be finite and positive`)\n } else problems.push(`${label} has an unknown shape`)\n if (!vec3(p?.position)) problems.push(`${label} needs a finite position [x, y, z]`)\n if (p?.rotation !== undefined && !vec3(p.rotation)) problems.push(`${label} has an invalid rotation`)\n for (const f of ['mass', 'hp', 'thickness'] as const) if (!nonNegative(p?.[f])) problems.push(`${label} ${f} must be a finite number >= 0`)\n if (p?.fill !== undefined && !(finite(p.fill) && p.fill >= 0 && p.fill <= 1)) problems.push(`${label} fill must be between 0 and 1`)\n if (p?.structure) {\n if (!STRUCTURE_ROLES.includes(p.structure.role)) problems.push(`${label} has an invalid structure role \"${String(p.structure.role)}\"`)\n if (!nonNegative(p.structure.capacity)) problems.push(`${label} structure capacity must be a finite number >= 0`)\n }\n if (p?.contains && !nonNegative(p.contains.liters)) problems.push(`${label} contains.liters must be a finite number >= 0`)\n checkStagedFields(p, label, materials, problems)\n }\n\n const jointIds = new Set<string>()\n const edges: Array<[string, string]> = []\n for (const j of spec?.joints ?? []) {\n const id = j.id ?? `${j.a}~${j.b}`\n const label = `joint \"${id}\"`\n if (jointIds.has(id)) problems.push(`duplicate joint id \"${id}\"`)\n jointIds.add(id)\n if (!ids.has(j.a)) problems.push(`${label} references unknown part \"${String(j.a)}\"`)\n if (!ids.has(j.b)) problems.push(`${label} references unknown part \"${String(j.b)}\"`)\n if (j.a === j.b) problems.push(`${label} joins part \"${String(j.a)}\" to itself`)\n if (j.bears !== undefined && !BEARS.includes(j.bears)) problems.push(`${label} has an invalid bears \"${String(j.bears)}\"`)\n if (!nonNegative(j.strength)) problems.push(`${label} strength must be a finite number >= 0`)\n if (!nonNegative(j.seconds)) problems.push(`${label} seconds must be a finite number >= 0`)\n if (j.bears !== false && ids.has(j.a) && ids.has(j.b) && j.a !== j.b) edges.push([j.a, j.b])\n }\n\n // structural parts must reach an anchored part over the joints (only when the spec lists its joints: otherwise they are inferred later)\n const structural = parts.filter((p) => p?.structure && p.structure.role !== 'none')\n if (structural.length && spec.joints) {\n const anchored = parts.filter((p) => p.anchored || p.structure?.role === 'foundation').map((p) => p.id)\n const adj = new Map<string, string[]>()\n for (const [a, b] of edges) {\n adj.set(a, [...(adj.get(a) ?? []), b])\n adj.set(b, [...(adj.get(b) ?? []), a])\n }\n const seen = new Set(anchored)\n const queue = [...anchored]\n while (queue.length) {\n for (const n of adj.get(queue.pop()!) ?? []) {\n if (seen.has(n)) continue\n seen.add(n)\n queue.push(n)\n }\n }\n const lost = structural.filter((p) => !seen.has(p.id)).map((p) => p.id)\n if (lost.length) warnings.push(`structural part(s) ${lost.map((i) => `\"${i}\"`).join(', ')} cannot reach an anchored part, so they will fall`)\n }\n return { ok: problems.length === 0, problems, warnings }\n}\n","import type { Hit, Vec3 } from './destruction'\n\n/**\n * A ballistic shot (one per pellet) as a weapon module produces it. Plain data: any damage model can consume it (`shotToHit` makes the `Hit`).\n */\nexport interface Shot {\n origin: Vec3\n /** Unit vector. */\n direction: Vec3\n /** Muzzle energy in joules. */\n energy: number\n /** Bullet diameter in millimetres. */\n caliber: number\n /** snake_case calibre id, e.g. `9x19`. */\n caliberId: string\n /** Muzzle speed in m/s. */\n speed: number\n /** Projectile mass in grams (one pellet for a shotgun load). */\n mass?: number\n /** Energy at a distance in metres, with the calibre's drag. */\n energyAt(distance: number): number\n pellet: number\n /** snake_case weapon id. */\n weapon: string\n source?: string\n}\n\n/** One use of a non-firearm (melee swing, flame tick, heat tick, throw, tool tick, extinguisher tick). */\nexport type Use =\n | { kind: 'melee'; damage: 'blunt' | 'slash'; energy: number; radius: number; reach: number; origin: Vec3; direction: Vec3 }\n | { kind: 'flame'; temperature: number; radius: number; range: number; cone: boolean; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'heat'; temperature: number; radius: number; range: number; duration: number; origin: Vec3; direction: Vec3 }\n | {\n kind: 'throw'\n energy: number\n radius: number\n fuse: number\n velocity: Vec3\n origin: Vec3\n fire?: { temperature: number; radius: number; seconds: number }\n /** Explosive id (`m67`, `molotov`, `pg7v`...): the host looks the numbers up in the arsenal. Absent on a plain thrown object. */\n explosive?: string\n /** What sets it off: `timer` after `fuse` seconds, `impact` on the first hit once armed. */\n trigger?: 'timer' | 'impact'\n /** Metres the projectile must fly before an impact sets it off; closer, it hits without exploding (rocket launchers). */\n armDistance?: number\n /** Radius in metres of the wounding area (`radius` is the lethal one). */\n injuryRadius?: number\n /** Seconds the blast stuns whoever is inside the injury radius (flashbang). */\n stunSeconds?: number\n /** Seconds the thrower held it after pulling the pin: `fuse` is what was left of the full fuse when it left the hand. */\n cooked?: number\n /** Went off in the thrower's hand (held past the fuse): no velocity, `fuse` is 0 and the blast is at `origin`. */\n inHand?: boolean\n }\n /** An explosive set down where the trigger is pulled (mine, C4, charge): it waits for its trigger instead of flying. */\n | {\n kind: 'place'\n explosive: string\n /** `wire`: something trips it; `remote`: the player fires it; `timer`: after `fuse` seconds. */\n triggers: readonly ('timer' | 'wire' | 'remote')[]\n energy: number\n /** Lethal radius in metres. */\n radius: number\n injuryRadius: number\n /** Cone the blast goes through, in front of `direction` (mine); absent for a blast in every direction. */\n cone?: { halfAngleDeg: number; length: number }\n /** The wire a `wire` charge stretches from where it is set down: metres out, degrees from its facing (counter-clockwise from above) and metres above it. */\n tripwire?: { length: number; angleDeg: number; height: number }\n fuse?: number\n origin: Vec3\n direction: Vec3\n }\n /** The blast behind a rocket launcher: dangerous inside `radius` metres, in the cone around `direction` (pointing away from the target). */\n | { kind: 'backblast'; radius: number; halfAngleDeg: number; origin: Vec3; direction: Vec3 }\n | { kind: 'tool'; tool: string; reach: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'extinguish'; range: number; coolC: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'hand'; reach: number; origin: Vec3; direction: Vec3 }\n\n/**\n * The `Hit` a ballistic shot makes at `point` (world space): the energy is what is left of the shot at that distance from its origin\n * (`shot.energyAt`), the direction and calibre come from the shot. `normal` (the surface normal at the point, when the caller knows it) does not\n * change the hit; it is accepted so that callers can pass what their raycast returned and so that a stricter implementation could use it.\n */\nexport const shotToHit = (shot: Shot, point: Vec3, normal?: Vec3): Hit => {\n void normal\n const dx = point[0] - shot.origin[0]\n const dy = point[1] - shot.origin[1]\n const dz = point[2] - shot.origin[2]\n const distance = Math.hypot(dx, dy, dz)\n return {\n type: 'ballistic',\n point: [point[0], point[1], point[2]],\n direction: [shot.direction[0], shot.direction[1], shot.direction[2]],\n energy: shot.energyAt(distance),\n caliber: shot.caliber,\n ...(shot.mass === undefined ? {} : { mass: shot.mass }),\n source: shot.source ?? shot.weapon,\n }\n}\n","import type { Vec3 } from './destruction'\n\n/** Pose of one part (or of the whole item) at a key: position offset in metres and Euler XYZ rotation in radians, both relative to the rest pose. */\nexport interface AnimationPose {\n position?: Vec3\n rotation?: Vec3\n}\n\nexport interface AnimationKey {\n /** Seconds from the start of the clip. */\n t: number\n /** Pose of the item's root. */\n position?: Vec3\n rotation?: Vec3\n /** Poses of named parts: the key is a destruction part id (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */\n parts?: Record<string, AnimationPose>\n}\n\nexport interface AnimationEventSpec {\n /** Seconds from the start of the clip. */\n t: number\n /** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */\n type: string\n data?: Record<string, unknown>\n}\n\n/** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */\nexport interface AnimationClipSpec {\n /** snake_case: `reload`, `open`, `drink`... Unique within the item. */\n id: string\n /** Seconds. */\n duration: number\n keys: AnimationKey[]\n events?: AnimationEventSpec[]\n}\n\n/** What an item exposes when it brings its own animations. */\nexport interface ItemAnimationContract {\n animations: AnimationClipSpec[]\n}\n\nconst finite3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === 'number' && Number.isFinite(n))\n\n/**\n * Problems with a clip (empty when it is valid): a snake_case id, a positive finite duration, at least one key, key and event times inside\n * `[0, duration]` and not going backwards, finite vectors, every named part one of `partIds`, event types that are non-empty strings and\n * JSON-serializable event data.\n */\nexport const checkAnimation = (clip: AnimationClipSpec, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const parts = new Set(partIds)\n const id = String(clip?.id ?? '')\n if (!/^[a-z][a-z0-9_]*$/.test(id)) problems.push(`clip id \"${id}\" must be snake_case`)\n const label = `clip \"${id}\"`\n if (!(typeof clip?.duration === 'number' && Number.isFinite(clip.duration) && clip.duration > 0)) problems.push(`${label} needs a positive finite duration`)\n const dur = typeof clip?.duration === 'number' && clip.duration > 0 ? clip.duration : Infinity\n if (!Array.isArray(clip?.keys) || clip.keys.length === 0) {\n problems.push(`${label} needs at least one key`)\n return { ok: false, problems }\n }\n let prev = -Infinity\n clip.keys.forEach((k, i) => {\n if (!(typeof k.t === 'number' && Number.isFinite(k.t)) || k.t < 0 || k.t > dur + 1e-9) problems.push(`${label} key ${i} has t=${String(k.t)} outside 0..${dur}`)\n else if (k.t < prev) problems.push(`${label} key ${i} goes back in time (${k.t} < ${prev})`)\n else prev = k.t\n for (const f of ['position', 'rotation'] as const) if (k[f] !== undefined && !finite3(k[f])) problems.push(`${label} key ${i} has an invalid ${f}`)\n for (const [pid, pose] of Object.entries(k.parts ?? {})) {\n if (!parts.has(pid)) problems.push(`${label} key ${i} moves unknown part \"${pid}\"`)\n for (const f of ['position', 'rotation'] as const) if (pose?.[f] !== undefined && !finite3(pose[f])) problems.push(`${label} key ${i} part \"${pid}\" has an invalid ${f}`)\n }\n })\n let prevEv = -Infinity\n ;(clip.events ?? []).forEach((e, i) => {\n if (!(typeof e.t === 'number' && Number.isFinite(e.t)) || e.t < 0 || e.t > dur + 1e-9) problems.push(`${label} event ${i} has t=${String(e.t)} outside 0..${dur}`)\n else if (e.t < prevEv) problems.push(`${label} event ${i} goes back in time`)\n else prevEv = e.t\n if (typeof e.type !== 'string' || e.type === '') problems.push(`${label} event ${i} needs a type`)\n try {\n JSON.stringify(e.data ?? {})\n } catch {\n problems.push(`${label} event ${i} data is not JSON`)\n }\n })\n return { ok: problems.length === 0, problems }\n}\n","import type { AnimationClipSpec, AnimationEventSpec, AnimationPose } from './animation'\nimport { checkAnimation } from './animation'\nimport type { HostEvent } from './module'\n\n/** Clips a tool can bring, one per move of its cycle. A tool brings the ones it has; a missing clip is simply not played. */\nexport const TOOL_CLIP = {\n DRAW: 'draw',\n SHOT: 'shot',\n MISFIRE: 'misfire',\n EMPTY: 'empty',\n CYCLE: 'cycle',\n RELOAD: 'reload',\n FEED: 'feed',\n FAULT: 'fault',\n CLEAR: 'clear',\n SAFETY_ON: 'safety_on',\n SAFETY_OFF: 'safety_off',\n MODE: 'mode',\n /** A throwable with the pin out, held until it is thrown. */\n COOK: 'cook',\n /** The stock folds, unfolds, slides in or out (variants `fold`, `unfold`, `collapse`, `extend`). */\n STOCK: 'stock',\n /** A light or a laser is switched (variants `on`, `off`). */\n TOGGLE: 'toggle',\n} as const\nexport type ToolClipId = (typeof TOOL_CLIP)[keyof typeof TOOL_CLIP]\n\n/** What a renderer does when a rule event arrives. `variant` picks `<clip>_<variant>` when the tool has it, else `<clip>`. */\nexport type ToolAnimationStep =\n | { readonly type: 'play'; readonly clip: ToolClipId; readonly variant?: string; readonly duration?: number; readonly repeat?: number }\n /** Move the clip that is playing to the marker (a clip event `stage` with `data.stage`) the rule just reached. */\n | { readonly type: 'sync'; readonly stage: string }\n /** The rule says the phase is over: the clip jumps to its end if it is still running. */\n | { readonly type: 'finish'; readonly clip: ToolClipId }\n\nconst stockVariant = (event: Extract<HostEvent, { type: 'stock' }>): string => {\n const isFolding = event.kind === undefined ? false : event.kind.includes('fold')\n if (event.state === 'collapsed') return isFolding ? 'fold' : 'collapse'\n return isFolding ? 'unfold' : 'extend'\n}\n\n/** The move a host event asks of the tool in hand, or null when it has no motion. */\nexport const toolAnimationStep = (event: HostEvent): ToolAnimationStep | null => {\n switch (event.type) {\n case 'tool':\n return { type: 'play', clip: TOOL_CLIP.DRAW, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'shot':\n return { type: 'play', clip: TOOL_CLIP.SHOT, repeat: Math.max(1, event.rounds ?? 1) }\n case 'misfire':\n return { type: 'play', clip: TOOL_CLIP.MISFIRE }\n case 'empty':\n return { type: 'play', clip: TOOL_CLIP.EMPTY }\n case 'mode':\n return { type: 'play', clip: TOOL_CLIP.MODE, variant: event.mode }\n case 'fault':\n return { type: 'play', clip: TOOL_CLIP.FAULT, variant: event.fault }\n case 'clear':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CLEAR }\n return { type: 'play', clip: TOOL_CLIP.CLEAR, variant: event.fault, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'safety':\n return { type: 'play', clip: event.on ? TOOL_CLIP.SAFETY_ON : TOOL_CLIP.SAFETY_OFF }\n case 'cycle':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CYCLE }\n return { type: 'play', clip: TOOL_CLIP.CYCLE, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'reload':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.RELOAD }\n return { type: 'play', clip: TOOL_CLIP.RELOAD, ...(event.kind === undefined ? {} : { variant: event.kind }), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'stage':\n return { type: 'sync', stage: event.stage }\n case 'feed':\n return { type: 'play', clip: TOOL_CLIP.FEED }\n case 'cook':\n if (event.phase === 'start') return { type: 'play', clip: TOOL_CLIP.COOK, duration: event.fuse }\n return { type: 'finish', clip: TOOL_CLIP.COOK }\n case 'stock':\n return { type: 'play', clip: TOOL_CLIP.STOCK, variant: stockVariant(event), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'toggle':\n return { type: 'play', clip: TOOL_CLIP.TOGGLE, variant: event.on ? 'on' : 'off' }\n default:\n return null\n }\n}\n\n/** Where a renderer puts the poses: every part of the tool by id (the ids the clips move), and the whole tool. Poses are offsets from rest. */\nexport interface ToolRig {\n setPartPose(partId: string, pose: AnimationPose): void\n setRootPose?(pose: AnimationPose): void\n}\n\n/** Clips by id, with the variants as `<clip>_<variant>`. */\nexport type ToolClipSet = Readonly<Record<string, AnimationClipSpec>>\n\n/** A renderer's tool animator: feed it the host events and the frame time. */\nexport interface ToolAnimator {\n handle(event: HostEvent): void\n update(dt: number): void\n /** Ids of the clips playing now. */\n readonly playing: readonly string[]\n dispose(): void\n}\n\nexport const STAGE_EVENT = 'stage'\n\n/** The clip with its times (keys and events) scaled so that it lasts `duration` seconds. */\nexport const scaleClip = (clip: AnimationClipSpec, duration: number): AnimationClipSpec => {\n const factor = clip.duration > 0 ? duration / clip.duration : 1\n return {\n ...clip,\n duration,\n keys: clip.keys.map((key) => ({ ...key, t: key.t * factor })),\n ...(clip.events ? { events: clip.events.map((event) => ({ ...event, t: event.t * factor })) } : {}),\n }\n}\n\nconst ZERO: [number, number, number] = [0, 0, 0]\nconst mix = (from: readonly number[], to: readonly number[], fraction: number): [number, number, number] => [\n (from[0] ?? 0) + ((to[0] ?? 0) - (from[0] ?? 0)) * fraction,\n (from[1] ?? 0) + ((to[1] ?? 0) - (from[1] ?? 0)) * fraction,\n (from[2] ?? 0) + ((to[2] ?? 0) - (from[2] ?? 0)) * fraction,\n]\n\n/** Pose of the whole tool (`partId` undefined) or of one part at `time`, linear between keys; a key that does not name it means rest. */\nexport const sampleClip = (clip: AnimationClipSpec, partId: string | undefined, time: number): { position: [number, number, number]; rotation: [number, number, number] } => {\n const points = clip.keys.map((key) => {\n const pose = partId === undefined ? key : key.parts?.[partId]\n return { t: key.t, position: pose?.position ?? ZERO, rotation: pose?.rotation ?? ZERO }\n })\n const first = points[0]\n if (!first || first.t > 0) points.unshift({ t: 0, position: ZERO, rotation: ZERO })\n const last = points[points.length - 1]\n if (!last) return { position: ZERO, rotation: ZERO }\n if (time >= last.t) return { position: mix(last.position, last.position, 0), rotation: mix(last.rotation, last.rotation, 0) }\n const index = Math.max(0, points.findIndex((point, i) => time >= point.t && time < (points[i + 1]?.t ?? Infinity)))\n const from = points[index]\n const to = points[index + 1]\n if (!from || !to) return { position: ZERO, rotation: ZERO }\n const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0\n return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) }\n}\n\n/** Problems with a clip set (empty when valid): each key is the clip id (or `<clip>_<variant>`), every clip passes `checkAnimation`, and a `stage` event names its stage. */\nexport const checkToolClips = (clips: ToolClipSet, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const parts = [...partIds]\n const problems: string[] = []\n for (const [key, clip] of Object.entries(clips)) {\n if (clip.id !== key) problems.push(`clip \"${key}\" has the id \"${clip.id}\"`)\n if (!(clip.duration > 0)) problems.push(`clip \"${key}\" has no duration`)\n problems.push(...checkAnimation(clip, parts).problems)\n for (const event of clip.events ?? []) if (event.type === STAGE_EVENT && typeof event.data?.stage !== 'string') problems.push(`clip \"${key}\" has a stage event without data.stage`)\n }\n return { ok: problems.length === 0, problems }\n}\n\ninterface Instance {\n readonly name: string\n readonly clip: AnimationClipSpec\n time: number\n delay: number\n fired: number\n}\n\nexport interface ToolClipPlayerOptions {\n clips: ToolClipSet\n rig: ToolRig\n /** Called for each clip event as its time passes (`eject_casing`, `drop_magazine`, `stage`...). */\n onEvent?(event: AnimationEventSpec, clip: string): void\n /** Seconds between the rounds of a `repeat` (a burst): default 0.1. */\n repeatInterval?(): number\n}\n\n/** The default player of a tool's clips: runs the steps of `toolAnimationStep` against a rig, scaled to the duration the rule gives. */\nexport interface ToolClipPlayer {\n apply(step: ToolAnimationStep): void\n update(dt: number): void\n readonly playing: readonly string[]\n}\n\nexport const createToolClipPlayer = (options: ToolClipPlayerOptions): ToolClipPlayer => {\n const { clips, rig, onEvent } = options\n let instances: Instance[] = []\n\n const find = (clip: string, variant?: string): { name: string; clip: AnimationClipSpec } | undefined => {\n const name = variant !== undefined && clips[`${clip}_${variant}`] ? `${clip}_${variant}` : clip\n const found = clips[name]\n return found ? { name, clip: found } : undefined\n }\n\n const render = (instance: Instance): void => {\n const time = Math.min(instance.time, instance.clip.duration)\n const root = sampleClip(instance.clip, undefined, time)\n if (instance.clip.keys.some((key) => key.position || key.rotation)) rig.setRootPose?.(root)\n const touched = new Set(instance.clip.keys.flatMap((key) => Object.keys(key.parts ?? {})))\n for (const id of touched) rig.setPartPose(id, sampleClip(instance.clip, id, time))\n }\n\n const fireEvents = (instance: Instance, upTo: number): void => {\n const events = instance.clip.events ?? []\n while (instance.fired < events.length && (events[instance.fired]?.t ?? Infinity) <= upTo + 1e-9) {\n const event = events[instance.fired]\n instance.fired += 1\n if (event) onEvent?.(event, instance.name)\n }\n }\n\n return {\n apply(step) {\n if (step.type === 'sync') {\n for (const instance of instances) {\n const marker = (instance.clip.events ?? []).find((event) => event.type === STAGE_EVENT && event.data?.stage === step.stage)\n if (marker && instance.time < marker.t) instance.time = marker.t\n }\n return\n }\n if (step.type === 'finish') {\n for (const instance of instances) {\n if (instance.name !== step.clip && !instance.name.startsWith(`${step.clip}_`)) continue\n fireEvents(instance, instance.clip.duration)\n instance.time = instance.clip.duration\n }\n return\n }\n const found = find(step.clip, step.variant)\n if (!found) return\n const clip = step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip\n const interval = options.repeatInterval?.() ?? 0.1\n instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0)\n for (let round = 0; round < (step.repeat ?? 1); round++) {\n instances.push({ name: found.name, clip, time: 0, delay: round * interval, fired: 0 })\n }\n },\n update(dt) {\n for (const instance of instances) {\n const carried = instance.delay > 0 ? dt - instance.delay : dt\n if (instance.delay > 0) {\n instance.delay = Math.max(0, instance.delay - dt)\n if (instance.delay > 0) continue\n }\n instance.time += carried\n fireEvents(instance, instance.time)\n render(instance)\n }\n instances = instances.filter((instance) => instance.delay > 0 || instance.time < instance.clip.duration)\n },\n get playing() {\n return instances.map((instance) => instance.name)\n },\n }\n}\n","type PlainObject = Record<string, unknown>\ntype Entry = { readonly id: string }\n\n/** Keys that would reach the prototype of the result when a patch comes from parsed JSON. */\nconst UNSAFE_KEYS: ReadonlySet<string> = new Set(['__proto__', 'constructor', 'prototype'])\n\nconst isPlainObject = (value: unknown): value is PlainObject =>\n typeof value === 'object' && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype\n\n/** Recursively optional; arrays and functions are replaced as a whole, objects are merged. */\nexport type DeepPartial<TValue> = TValue extends (...args: never[]) => unknown\n ? TValue\n : TValue extends readonly unknown[]\n ? TValue\n : TValue extends object\n ? { [TKey in keyof TValue]?: DeepPartial<TValue[TKey]> }\n : TValue\n\n/** Merges `patch` over `base`: plain objects merge field by field, arrays, functions and primitives replace. `undefined` in the patch is ignored. */\nexport const deepMerge = <TBase extends object>(base: TBase, patch: object | undefined): TBase => {\n if (!patch) return base\n const result: PlainObject = { ...(base as PlainObject) }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined || UNSAFE_KEYS.has(key)) continue\n const current = result[key]\n result[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value\n }\n return result as TBase\n}\n\n/** The shipped entries with the ones from `extra` added or, when the id exists, replaced. */\nexport const mergeById = <TEntry extends Entry>(shipped: readonly TEntry[], extra: readonly TEntry[] | undefined): Map<string, TEntry> => {\n const entries = new Map<string, TEntry>()\n for (const entry of [...shipped, ...(extra ?? [])]) entries.set(entry.id, entry)\n return entries\n}\n\n/** Applies `patch` by id; a patch for an id that does not exist is ignored. */\nexport const applyPatches = <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, DeepPartial<TEntry> | Partial<TEntry>>> | undefined): void => {\n for (const [id, patch] of Object.entries(patches ?? {})) {\n const entry = entries.get(id)\n if (entry) entries.set(id, deepMerge(entry, patch))\n }\n}\n\nexport const removeIds = (entries: Map<string, unknown>, ids: readonly string[] | undefined): void => {\n for (const id of ids ?? []) entries.delete(id)\n}\n","import type { Vec3 } from './destruction'\n\n/**\n * 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\n * every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.\n */\nexport type ControlValue = number | boolean | string\n\nexport interface ChoiceOption {\n readonly id: string\n readonly name: string\n}\n\ninterface ControlBase {\n /** snake_case, unique across the module's controls, layers and metrics. */\n readonly id: string\n /** Display name, already translated for the locale the host asked for. */\n readonly name: string\n readonly hint?: string\n readonly hidden?: boolean\n}\n\nexport interface NumberControl extends ControlBase {\n readonly kind: 'number'\n readonly min: number\n readonly max: number\n readonly step?: number\n readonly unit?: string\n readonly default: number\n}\n\nexport interface BooleanControl extends ControlBase {\n readonly kind: 'boolean'\n readonly default: boolean\n}\n\nexport interface ChoiceControl extends ControlBase {\n readonly kind: 'choice'\n readonly options: readonly ChoiceOption[]\n readonly default: string\n}\n\n/** A button: no value, only the press. */\nexport interface ActionControl extends ControlBase {\n readonly kind: 'action'\n}\n\nexport type ModuleControl = NumberControl | BooleanControl | ChoiceControl | ActionControl\n\n/** Something of the module the viewer turns on or off (smoke, flash, casings). */\nexport interface ModuleLayer {\n readonly id: string\n readonly name: string\n /** On unless `false`. */\n readonly default?: boolean\n}\n\n/** Pulled: the room calls read only when it samples (~4 Hz, or on TestRoom.metrics()). */\nexport interface ModuleMetric {\n readonly id: string\n readonly name: string\n readonly unit?: string\n /** Budget: the host shows a bar and warns past it. */\n readonly max?: number\n readonly digits?: number\n read(): number | string\n}\n\nexport type BenchChange =\n | { readonly kind: 'control'; readonly id: string; readonly value: ControlValue }\n | { readonly kind: 'layer'; readonly id: string; readonly on: boolean }\n | { readonly kind: 'action'; readonly id: string }\n\nexport interface ControlPatch {\n readonly hidden?: boolean\n readonly disabled?: boolean\n readonly options?: readonly ChoiceOption[]\n readonly value?: ControlValue\n}\n\n/** What the host lends a module that declares a bench: the current values and the changes. */\nexport interface ModuleBench {\n value(id: string): ControlValue | undefined\n layer(id: string): boolean\n /** Right after attach returns, the host sends one change per control (not actions) and per layer, in declaration order. */\n onChange(listener: (change: BenchChange) => void): () => void\n /** `value` sets without emitting (no echo loops); `options` falls back to the first option if the value is no longer offered. */\n patch(id: string, patch: ControlPatch): void\n /** Move the host camera (a near / middle / far choice). */\n look(eye: Vec3, target: Vec3): void\n}\n\nexport interface BenchReport {\n readonly ok: boolean\n readonly problems: readonly string[]\n}\n\nexport interface BenchDeclaration {\n readonly controls?: readonly ModuleControl[]\n readonly layers?: readonly ModuleLayer[]\n readonly metrics?: readonly ModuleMetric[]\n}\n\nconst SNAKE_CASE = /^[a-z][a-z0-9_]*$/\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\n\nfunction checkControl(control: ModuleControl, problems: string[]): void {\n const label = `control \"${control.id}\"`\n if (control.kind === 'number') {\n if (!finite(control.min) || !finite(control.max) || control.min >= control.max) problems.push(`${label} needs min < max`)\n if (control.step !== undefined && !(finite(control.step) && control.step > 0)) problems.push(`${label} needs a step > 0`)\n if (!finite(control.default) || control.default < control.min || control.default > control.max) problems.push(`${label} default ${String(control.default)} is outside ${control.min}..${control.max}`)\n return\n }\n if (control.kind !== 'choice') return\n if (control.options.length === 0) {\n problems.push(`${label} needs at least one option`)\n return\n }\n const seen = new Set<string>()\n for (const option of control.options) {\n if (seen.has(option.id)) problems.push(`${label} repeats option \"${option.id}\"`)\n seen.add(option.id)\n }\n if (!seen.has(control.default)) problems.push(`${label} default \"${control.default}\" is not among its options`)\n}\n\n/** snake_case ids unique across controls, layers and metrics; min < max; step > 0; default in range; options non-empty, unique, default among them; metric max > 0. */\nexport function checkBench(declared: BenchDeclaration): BenchReport {\n const problems: string[] = []\n const ids = new Set<string>()\n const claim = (kind: string, id: string): void => {\n if (!SNAKE_CASE.test(id)) problems.push(`${kind} id \"${id}\" must be snake_case`)\n if (ids.has(id)) problems.push(`duplicate id \"${id}\" across controls, layers and metrics`)\n ids.add(id)\n }\n for (const control of declared.controls ?? []) {\n claim('control', control.id)\n checkControl(control, problems)\n }\n for (const layer of declared.layers ?? []) claim('layer', layer.id)\n for (const metric of declared.metrics ?? []) {\n claim('metric', metric.id)\n if (metric.max !== undefined && !(finite(metric.max) && metric.max > 0)) problems.push(`metric \"${metric.id}\" needs a max > 0`)\n }\n return { ok: problems.length === 0, problems }\n}\n"],"mappings":"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","names":["finite"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/impact.ts","../src/body.ts","../src/spec.ts","../src/weapons.ts","../src/animation.ts","../src/tool-animation.ts","../src/registry.ts","../src/bench.ts","../src/environment.ts"],"sourcesContent":["/**\n * @adatechnology/game-contracts: the contracts every game module (vehicles, furniture, plants, houses, weapons) depends on, and nothing else.\n * Types plus tiny pure checkers, no runtime dependencies. The default destruction world, the renderers and the test room are optional.\n */\nexport type {\n ChainDelay,\n Crack,\n DamageStage,\n DamageType,\n DebrisKind,\n Dent,\n DestructibleSpec,\n ExplosionChain,\n DestructionEvent,\n DestructionEventType,\n FluidKind,\n Fragment,\n FragmentMark,\n FractureStyle,\n Hit,\n HitResult,\n Hole,\n JointKind,\n JointSpec,\n JointState,\n MaterialId,\n ObjectState,\n PartShape,\n PartSpec,\n PartBreach,\n PartState,\n PartStatus,\n PartUnits,\n Quat,\n RayHit,\n Reinforcement,\n StructureRole,\n SurfaceLayer,\n ToolKind,\n Vec3,\n} from './destruction'\nexport { IMPACT_CONTRACT, checkDestructible, checkHit } from './impact'\nexport type { CheckOptions, DestructibleImpl, ImpactContract } from './impact'\nexport { MISSING_BITS } from './body'\nexport type { BodyRegion, BodySlot, SegmentKind } from './body'\nexport { checkSpec } from './spec'\nexport type { CheckSpecOptions, SpecReport } from './spec'\nexport { shotToHit } from './weapons'\nexport type { Shot, Use } from './weapons'\nexport type { ContainerSpec, ItemUseDef, LiquidId } from './fluids'\nexport type { EffectHandle, EffectRequest, EffectSink } from './effects'\nexport { checkAnimation } from './animation'\nexport type { AnimationClipSpec, AnimationEventSpec, AnimationKey, AnimationPose, ItemAnimationContract } from './animation'\nexport { STAGE_EVENT, TOOL_CLIP, checkToolClips, createToolClipPlayer, sampleClip, scaleClip, toolAnimationStep } from './tool-animation'\nexport type { ToolAnimationStep, ToolAnimator, ToolClipId, ToolClipPlayer, ToolClipPlayerOptions, ToolClipSet, ToolRig } from './tool-animation'\nexport type { AreaPlacement, HostEvent, HostTool, HostToolAttachment, ModuleArea, ModuleHost, ModuleItemFluid, ModuleScenario, TestableItem, TestableModule, Thumbnail, ToolCommand, ToolCommandResult } from './module'\nexport { applyPatches, deepMerge, mergeById, removeIds } from './registry'\nexport type { DeepPartial } from './registry'\nexport { checkBench } from './bench'\nexport type {\n ActionControl,\n BenchChange,\n BenchDeclaration,\n BenchReport,\n BenchSheet,\n BooleanControl,\n ChoiceControl,\n ChoiceOption,\n ControlPatch,\n ControlValue,\n ModuleBench,\n ModuleControl,\n ModuleLayer,\n ModuleMetric,\n NumberControl,\n} from './bench'\nexport type { AudioListener, AudioSink, LoopHandle, SoundHandle, SoundRequest } from './audio'\nexport { DEFAULT_ENVIRONMENT, createEnvironmentBus } from './environment'\nexport type { EnvironmentBus, EnvironmentKey, EnvironmentState } from './environment'\n","import type { DamageType, DestructibleSpec, DestructionEvent, Hit, JointSpec, ObjectState, ToolKind, Vec3 } from './destruction'\n\n/**\n * What a game module implements for an object whose behaviour it owns (a vehicle with its own panel deformation, a creature, a machine). The\n * world (`DestructionWorld.register`) hosts it next to its own objects: raycast, shoot, explode, hit, disassemble, extinguish, update, state,\n * reset and `on` all route to it.\n *\n * Space: the impl receives hits in WORLD space and returns events in WORLD space (it owns its transform; the transform given to `register` or\n * `setTransform` is only used by the world for raycasts and blast range). Every event must carry `object === id` and name parts listed by\n * `describe()`.\n */\nexport interface DestructibleImpl {\n readonly id: string\n readonly name?: string\n /** Parts, joints and structure in the impl's local space, for raycast, inspector and log. Called often: keep it cheap. */\n describe(): DestructibleSpec\n /** Apply one hit (see `IMPACT_CONTRACT` for the fields per type) and return the events it caused. */\n hit(hit: Hit): DestructionEvent[]\n /** Advance fire, melting, collapse and other timed behaviour. */\n update?(dt: number): DestructionEvent[]\n state(): ObjectState\n reset?(): void\n /** Same contract as `DestructionWorld.disassemble`: progress 0..1, or -1 when the tool cannot undo the joint. */\n disassemble?(jointId: string, tool: ToolKind, dt: number): number\n extinguish?(partId?: string): void\n /** Optional: an animation moved a part; `position` / `rotation` (Euler XYZ, radians) are object-local. Keep raycast and hit shapes in sync. */\n setPartPose?(partId: string, position: Vec3, rotation?: Vec3): void\n /** Plain JSON of the impl's own state, stored in `DestructionSnapshot.impls`. */\n serialize?(): unknown\n load?(data: unknown): void\n}\n\n/** The fields a weapon or hazard must put in a `Hit`, per damage type. THE impact contract. */\nexport interface ImpactContract {\n required: string[]\n optional: string[]\n note: string\n}\n\nexport const IMPACT_CONTRACT: Record<DamageType, ImpactContract> = {\n ballistic: { required: ['type', 'point', 'direction', 'energy', 'caliber'], optional: ['space', 'source'], note: 'bullets and pellets: joules and diameter in millimetres' },\n blunt: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'contact radius in metres (default 3 cm)' },\n slash: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'axes, knives, machetes' },\n explosive: { required: ['type', 'point', 'energy', 'radius'], optional: ['space', 'source'], note: 'point is the centre of the blast, radius the range in metres' },\n fire: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'flame temperature in °C; duration in seconds (call every frame with `dt`)' },\n heat: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'heat without flame (cutting torch, hot plate)' },\n}\n\n/** Problems with a hit according to the impact contract (empty when it is valid). */\nexport const checkHit = (hit: Hit): string[] => {\n const c = IMPACT_CONTRACT[hit.type]\n if (!c) return [`unknown damage type \"${String(hit.type)}\"`]\n const out: string[] = []\n for (const f of c.required) {\n const v = (hit as unknown as Record<string, unknown>)[f]\n if (v === undefined || (Array.isArray(v) ? v.some((n) => !Number.isFinite(n)) : typeof v === 'number' && !Number.isFinite(v))) out.push(`${hit.type} hit needs \"${f}\"`)\n }\n return out\n}\n\nexport interface CheckOptions {\n /**\n * Ids of every material the impl may use (the contracts package does not know the materials of your world: pass the ids of\n * `world.materials`, or the built-in plus your own). When omitted, the material check is skipped.\n */\n materials?: Iterable<string>\n}\n\nconst finiteJson = (v: unknown, path: string, out: string[]) => {\n if (typeof v === 'number') {\n if (!Number.isFinite(v)) out.push(`${path} is not a finite number`)\n } else if (Array.isArray(v)) v.forEach((x, i) => finiteJson(x, `${path}[${i}]`, out))\n else if (v && typeof v === 'object') for (const [k, x] of Object.entries(v)) finiteJson(x, `${path}.${k}`, out)\n else if (typeof v === 'function' || typeof v === 'symbol' || typeof v === 'bigint') out.push(`${path} is not JSON`)\n}\n\n/**\n * Contract checker for the test room (it needs no world): `describe()` is valid, a 500 J ballistic hit at a part centre returns well-formed events of this impl, `state()`\n * matches `describe()`, hit points never go negative, `update(0.016)` returns an array and every event is JSON-serializable. It hits the impl\n * (and calls `reset()` afterwards when there is one).\n */\nexport const checkDestructible = (impl: DestructibleImpl, options: CheckOptions = {}): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const known = options.materials ? new Set(options.materials) : null\n let spec: DestructibleSpec | undefined\n try {\n spec = impl.describe()\n } catch (e) {\n return { ok: false, problems: [`describe() threw: ${String(e)}`] }\n }\n if (!spec || !Array.isArray(spec.parts) || spec.parts.length === 0) return { ok: false, problems: ['describe() must return at least one part'] }\n const ids = new Set<string>()\n for (const p of spec.parts) {\n if (ids.has(p.id)) problems.push(`duplicate part id \"${p.id}\"`)\n ids.add(p.id)\n if (known && !known.has(p.material)) problems.push(`part \"${p.id}\" has unknown material \"${p.material}\"`)\n if (!p.shape || !['box', 'cylinder', 'sphere'].includes(p.shape.kind)) problems.push(`part \"${p.id}\" has no valid shape`)\n if (!Array.isArray(p.position) || p.position.length !== 3) problems.push(`part \"${p.id}\" has no position`)\n }\n const jointIds = new Set<string>()\n for (const j of (spec.joints ?? []) as JointSpec[]) {\n if (!ids.has(j.a) || !ids.has(j.b)) problems.push(`joint \"${j.id ?? `${j.a}~${j.b}`}\" references an unknown part`)\n const jid = j.id ?? `${j.a}~${j.b}`\n if (jointIds.has(jid)) problems.push(`duplicate joint id \"${jid}\"`)\n jointIds.add(jid)\n }\n const checkState = (label: string) => {\n let st: ObjectState\n try {\n st = impl.state()\n } catch (e) {\n problems.push(`state() threw ${label}: ${String(e)}`)\n return\n }\n const sids = st.parts.map((p) => p.id).sort().join(',')\n if (sids !== [...ids].sort().join(',')) problems.push(`state() part ids do not match describe() ${label}`)\n for (const p of st.parts) if (!(p.hp >= 0)) problems.push(`part \"${p.id}\" has negative or invalid hp ${label}`)\n }\n const checkEvents = (events: unknown, label: string) => {\n if (!Array.isArray(events)) {\n problems.push(`${label} must return an array`)\n return\n }\n const evs = events as DestructionEvent[]\n evs.forEach((e, i) => {\n if (e.object !== impl.id) problems.push(`${label} event ${i} (${e.type}) has object \"${String(e.object)}\", expected \"${impl.id}\"`)\n const part = (e as { part?: string }).part\n if (part !== undefined && !ids.has(part)) problems.push(`${label} event ${i} (${e.type}) names unknown part \"${part}\"`)\n })\n const bad: string[] = []\n finiteJson(evs, 'events', bad)\n problems.push(...bad.map((b) => `${label}: ${b}`))\n try {\n const s = JSON.stringify(evs)\n if (JSON.stringify(JSON.parse(s)) !== s) problems.push(`${label} events do not survive a JSON round trip`)\n } catch (e) {\n problems.push(`${label} events are not JSON-serializable: ${String(e)}`)\n }\n }\n checkState('before the hit')\n const target = spec.parts[0]\n try {\n const hit: Hit = { type: 'ballistic', point: [target.position[0], target.position[1], target.position[2]], direction: [0, 0, -1], energy: 500, caliber: 9 }\n checkEvents(impl.hit(hit), 'hit()')\n } catch (e) {\n problems.push(`hit() threw: ${String(e)}`)\n }\n checkState('after the hit')\n if (impl.update) {\n try {\n checkEvents(impl.update(0.016), 'update()')\n } catch (e) {\n problems.push(`update() threw: ${String(e)}`)\n }\n checkState('after update')\n }\n try {\n impl.reset?.()\n } catch (e) {\n problems.push(`reset() threw: ${String(e)}`)\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The slots of a body an item can cover (the item slot vocabulary: game-items imports it from here). */\nexport type BodySlot = 'head' | 'eyes' | 'torso' | 'arms' | 'hands' | 'legs' | 'feet' | 'over'\n\n/** The 11 segments of the detailed body model (the Arsenal ids). */\nexport type SegmentKind = 'head' | 'jaw' | 'neck' | 'torso' | 'pelvis' | 'upperArm' | 'forearm' | 'hand' | 'thigh' | 'shin' | 'foot'\n\n/** The 6 regions of the crowd model (the game regions 0..5). */\nexport type BodyRegion = 'head' | 'torso' | 'armL' | 'armR' | 'legL' | 'legR'\n\n/** Bits of the `missing` mask of a body. The instanced body shader reads them, so the values are part of the contract. */\nexport const MISSING_BITS = {\n head: 1,\n armL: 2,\n armR: 4,\n legL: 8,\n legR: 16,\n armLBroken: 32,\n armRBroken: 64,\n legLBroken: 128,\n legRBroken: 256,\n jaw: 512,\n} as const\n","import type { DestructibleSpec, PartSpec } from './destruction'\n\nexport interface CheckSpecOptions {\n /** Known material ids. When given, every part's material must be one of them. */\n materials?: Iterable<string>\n}\n\nexport interface SpecReport {\n /** False when there is at least one problem (warnings do not count). */\n ok: boolean\n problems: string[]\n /** Things that are legal but probably wrong, e.g. structural parts that cannot reach an anchored part. */\n warnings: string[]\n}\n\nconst STRUCTURE_ROLES = ['foundation', 'column', 'beam', 'slab', 'wall', 'partition', 'roof', 'stair', 'none']\nconst BEARS = [true, false, 'a_on_b', 'b_on_a']\n\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\nconst positive = (n: unknown): boolean => finite(n) && n > 0\nconst vec3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every(finite)\nconst nonNegative = (n: unknown): boolean => n === undefined || (finite(n) && n >= 0)\n\nconst FACES = ['both', '+', '-']\nconst BONDS = ['running', 'stack']\n\n/** Layers, reinforcement, units and breach: known materials, finite positive sizes. */\nconst checkStagedFields = (p: PartSpec | undefined, label: string, materials: Set<string> | null, problems: string[]): void => {\n const known = (m: unknown, what: string): void => {\n if (materials && !materials.has(m as string)) problems.push(`${label} ${what} has unknown material \"${String(m)}\"`)\n }\n ;(p?.layers ?? []).forEach((l, i) => {\n known(l?.material, `layer ${i}`)\n if (!positive(l?.thickness)) problems.push(`${label} layer ${i} thickness must be finite and positive`)\n if (!FACES.includes(l?.faces)) problems.push(`${label} layer ${i} has an invalid faces \"${String(l?.faces)}\"`)\n })\n const r = p?.reinforcement\n if (r) {\n known(r.material, 'reinforcement')\n if (!positive(r.spacing) || !positive(r.diameter)) problems.push(`${label} reinforcement spacing and diameter must be finite and positive`)\n if (r.residualCapacity !== undefined && !(finite(r.residualCapacity) && r.residualCapacity >= 0 && r.residualCapacity <= 1)) problems.push(`${label} reinforcement residualCapacity must be between 0 and 1`)\n }\n const u = p?.units\n if (u) {\n known(u.mortar, 'units mortar')\n if (!vec3(u.size) || !u.size.every(positive)) problems.push(`${label} units size must be three finite positive numbers`)\n if (!BONDS.includes(u.bond)) problems.push(`${label} units has an invalid bond \"${String(u.bond)}\"`)\n }\n if (p?.breach && p.breach.area !== undefined && !positive(p.breach.area)) problems.push(`${label} breach area must be finite and positive`)\n}\n\n/**\n * Static checks on a `DestructibleSpec` (no world needed), for items that give a `spec` instead of an `impl`: unique part ids, known materials\n * (when `options.materials` is given), joints that reference existing, different parts, finite positive sizes, non-negative mass / hp / capacity,\n * valid structure roles and `bears` directions. A structural part with no path of joints to an anchored part is a warning, not a problem: it\n * is allowed, it just falls.\n */\nexport const checkSpec = (spec: DestructibleSpec, options: CheckSpecOptions = {}): SpecReport => {\n const problems: string[] = []\n const warnings: string[] = []\n const materials = options.materials ? new Set(options.materials) : null\n const parts = Array.isArray(spec?.parts) ? spec.parts : []\n if (!Array.isArray(spec?.parts) || parts.length === 0) problems.push('the spec needs at least one part')\n const ids = new Set<string>()\n for (const p of parts) {\n const id = String(p?.id ?? '')\n const label = `part \"${id}\"`\n if (id === '') problems.push('a part has no id')\n else if (ids.has(id)) problems.push(`duplicate part id \"${id}\"`)\n ids.add(id)\n if (materials && !materials.has(p?.material)) problems.push(`${label} has unknown material \"${String(p?.material)}\"`)\n else if (typeof p?.material !== 'string' || p.material === '') problems.push(`${label} needs a material`)\n const s = p?.shape\n if (s?.kind === 'box') {\n if (!Array.isArray(s.size) || s.size.length !== 3 || !s.size.every(positive)) problems.push(`${label} box size must be three finite positive numbers`)\n } else if (s?.kind === 'cylinder') {\n if (!positive(s.radius) || !positive(s.height)) problems.push(`${label} cylinder radius and height must be finite and positive`)\n } else if (s?.kind === 'sphere') {\n if (!positive(s.radius)) problems.push(`${label} sphere radius must be finite and positive`)\n } else problems.push(`${label} has an unknown shape`)\n if (!vec3(p?.position)) problems.push(`${label} needs a finite position [x, y, z]`)\n if (p?.rotation !== undefined && !vec3(p.rotation)) problems.push(`${label} has an invalid rotation`)\n for (const f of ['mass', 'hp', 'thickness'] as const) if (!nonNegative(p?.[f])) problems.push(`${label} ${f} must be a finite number >= 0`)\n if (p?.fill !== undefined && !(finite(p.fill) && p.fill >= 0 && p.fill <= 1)) problems.push(`${label} fill must be between 0 and 1`)\n if (p?.structure) {\n if (!STRUCTURE_ROLES.includes(p.structure.role)) problems.push(`${label} has an invalid structure role \"${String(p.structure.role)}\"`)\n if (!nonNegative(p.structure.capacity)) problems.push(`${label} structure capacity must be a finite number >= 0`)\n }\n if (p?.contains && !nonNegative(p.contains.liters)) problems.push(`${label} contains.liters must be a finite number >= 0`)\n checkStagedFields(p, label, materials, problems)\n }\n\n const jointIds = new Set<string>()\n const edges: Array<[string, string]> = []\n for (const j of spec?.joints ?? []) {\n const id = j.id ?? `${j.a}~${j.b}`\n const label = `joint \"${id}\"`\n if (jointIds.has(id)) problems.push(`duplicate joint id \"${id}\"`)\n jointIds.add(id)\n if (!ids.has(j.a)) problems.push(`${label} references unknown part \"${String(j.a)}\"`)\n if (!ids.has(j.b)) problems.push(`${label} references unknown part \"${String(j.b)}\"`)\n if (j.a === j.b) problems.push(`${label} joins part \"${String(j.a)}\" to itself`)\n if (j.bears !== undefined && !BEARS.includes(j.bears)) problems.push(`${label} has an invalid bears \"${String(j.bears)}\"`)\n if (!nonNegative(j.strength)) problems.push(`${label} strength must be a finite number >= 0`)\n if (!nonNegative(j.seconds)) problems.push(`${label} seconds must be a finite number >= 0`)\n if (j.bears !== false && ids.has(j.a) && ids.has(j.b) && j.a !== j.b) edges.push([j.a, j.b])\n }\n\n // structural parts must reach an anchored part over the joints (only when the spec lists its joints: otherwise they are inferred later)\n const structural = parts.filter((p) => p?.structure && p.structure.role !== 'none')\n if (structural.length && spec.joints) {\n const anchored = parts.filter((p) => p.anchored || p.structure?.role === 'foundation').map((p) => p.id)\n const adj = new Map<string, string[]>()\n for (const [a, b] of edges) {\n adj.set(a, [...(adj.get(a) ?? []), b])\n adj.set(b, [...(adj.get(b) ?? []), a])\n }\n const seen = new Set(anchored)\n const queue = [...anchored]\n while (queue.length) {\n for (const n of adj.get(queue.pop()!) ?? []) {\n if (seen.has(n)) continue\n seen.add(n)\n queue.push(n)\n }\n }\n const lost = structural.filter((p) => !seen.has(p.id)).map((p) => p.id)\n if (lost.length) warnings.push(`structural part(s) ${lost.map((i) => `\"${i}\"`).join(', ')} cannot reach an anchored part, so they will fall`)\n }\n return { ok: problems.length === 0, problems, warnings }\n}\n","import type { Hit, Vec3 } from './destruction'\n\n/**\n * A ballistic shot (one per pellet) as a weapon module produces it. Plain data: any damage model can consume it (`shotToHit` makes the `Hit`).\n */\nexport interface Shot {\n origin: Vec3\n /** Unit vector. */\n direction: Vec3\n /** Muzzle energy in joules. */\n energy: number\n /** Bullet diameter in millimetres. */\n caliber: number\n /** snake_case calibre id, e.g. `9x19`. */\n caliberId: string\n /** Muzzle speed in m/s. */\n speed: number\n /** Projectile mass in grams (one pellet for a shotgun load). */\n mass?: number\n /** Energy at a distance in metres, with the calibre's drag. */\n energyAt(distance: number): number\n pellet: number\n /** snake_case weapon id. */\n weapon: string\n source?: string\n}\n\n/** One use of a non-firearm (melee swing, flame tick, heat tick, throw, tool tick, extinguisher tick). */\nexport type Use =\n | { kind: 'melee'; damage: 'blunt' | 'slash'; energy: number; radius: number; reach: number; origin: Vec3; direction: Vec3 }\n | { kind: 'flame'; temperature: number; radius: number; range: number; cone: boolean; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'heat'; temperature: number; radius: number; range: number; duration: number; origin: Vec3; direction: Vec3 }\n | {\n kind: 'throw'\n energy: number\n radius: number\n fuse: number\n velocity: Vec3\n origin: Vec3\n fire?: { temperature: number; radius: number; seconds: number }\n /** Explosive id (`m67`, `molotov`, `pg7v`...): the host looks the numbers up in the arsenal. Absent on a plain thrown object. */\n explosive?: string\n /** What sets it off: `timer` after `fuse` seconds, `impact` on the first hit once armed. */\n trigger?: 'timer' | 'impact'\n /** Metres the projectile must fly before an impact sets it off; closer, it hits without exploding (rocket launchers). */\n armDistance?: number\n /** Radius in metres of the wounding area (`radius` is the lethal one). */\n injuryRadius?: number\n /** Seconds the blast stuns whoever is inside the injury radius (flashbang). */\n stunSeconds?: number\n /** Seconds the thrower held it after pulling the pin: `fuse` is what was left of the full fuse when it left the hand. */\n cooked?: number\n /** Went off in the thrower's hand (held past the fuse): no velocity, `fuse` is 0 and the blast is at `origin`. */\n inHand?: boolean\n }\n /** An explosive set down where the trigger is pulled (mine, C4, charge): it waits for its trigger instead of flying. */\n | {\n kind: 'place'\n explosive: string\n /** `wire`: something trips it; `remote`: the player fires it; `timer`: after `fuse` seconds. */\n triggers: readonly ('timer' | 'wire' | 'remote')[]\n energy: number\n /** Lethal radius in metres. */\n radius: number\n injuryRadius: number\n /** Cone the blast goes through, in front of `direction` (mine); absent for a blast in every direction. */\n cone?: { halfAngleDeg: number; length: number }\n /** The wire a `wire` charge stretches from where it is set down: metres out, degrees from its facing (counter-clockwise from above) and metres above it. */\n tripwire?: { length: number; angleDeg: number; height: number }\n fuse?: number\n origin: Vec3\n direction: Vec3\n }\n /** The blast behind a rocket launcher: dangerous inside `radius` metres, in the cone around `direction` (pointing away from the target). */\n | { kind: 'backblast'; radius: number; halfAngleDeg: number; origin: Vec3; direction: Vec3 }\n | { kind: 'tool'; tool: string; reach: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'extinguish'; range: number; coolC: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'hand'; reach: number; origin: Vec3; direction: Vec3 }\n\n/**\n * The `Hit` a ballistic shot makes at `point` (world space): the energy is what is left of the shot at that distance from its origin\n * (`shot.energyAt`), the direction and calibre come from the shot. `normal` (the surface normal at the point, when the caller knows it) does not\n * change the hit; it is accepted so that callers can pass what their raycast returned and so that a stricter implementation could use it.\n */\nexport const shotToHit = (shot: Shot, point: Vec3, normal?: Vec3): Hit => {\n void normal\n const dx = point[0] - shot.origin[0]\n const dy = point[1] - shot.origin[1]\n const dz = point[2] - shot.origin[2]\n const distance = Math.hypot(dx, dy, dz)\n return {\n type: 'ballistic',\n point: [point[0], point[1], point[2]],\n direction: [shot.direction[0], shot.direction[1], shot.direction[2]],\n energy: shot.energyAt(distance),\n caliber: shot.caliber,\n ...(shot.mass === undefined ? {} : { mass: shot.mass }),\n source: shot.source ?? shot.weapon,\n }\n}\n","import type { Vec3 } from './destruction'\n\n/** Pose of one part (or of the whole item) at a key: position offset in metres and Euler XYZ rotation in radians, both relative to the rest pose. */\nexport interface AnimationPose {\n position?: Vec3\n rotation?: Vec3\n}\n\nexport interface AnimationKey {\n /** Seconds from the start of the clip. */\n t: number\n /** Pose of the item's root. */\n position?: Vec3\n rotation?: Vec3\n /** Poses of named parts: the key is a destruction part id (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */\n parts?: Record<string, AnimationPose>\n}\n\nexport interface AnimationEventSpec {\n /** Seconds from the start of the clip. */\n t: number\n /** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */\n type: string\n data?: Record<string, unknown>\n}\n\n/** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */\nexport interface AnimationClipSpec {\n /** snake_case: `reload`, `open`, `drink`... Unique within the item. */\n id: string\n /** Seconds. */\n duration: number\n keys: AnimationKey[]\n events?: AnimationEventSpec[]\n}\n\n/** What an item exposes when it brings its own animations. */\nexport interface ItemAnimationContract {\n animations: AnimationClipSpec[]\n}\n\nconst finite3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === 'number' && Number.isFinite(n))\n\n/**\n * Problems with a clip (empty when it is valid): a snake_case id, a positive finite duration, at least one key, key and event times inside\n * `[0, duration]` and not going backwards, finite vectors, every named part one of `partIds`, event types that are non-empty strings and\n * JSON-serializable event data.\n */\nexport const checkAnimation = (clip: AnimationClipSpec, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const parts = new Set(partIds)\n const id = String(clip?.id ?? '')\n if (!/^[a-z][a-z0-9_]*$/.test(id)) problems.push(`clip id \"${id}\" must be snake_case`)\n const label = `clip \"${id}\"`\n if (!(typeof clip?.duration === 'number' && Number.isFinite(clip.duration) && clip.duration > 0)) problems.push(`${label} needs a positive finite duration`)\n const dur = typeof clip?.duration === 'number' && clip.duration > 0 ? clip.duration : Infinity\n if (!Array.isArray(clip?.keys) || clip.keys.length === 0) {\n problems.push(`${label} needs at least one key`)\n return { ok: false, problems }\n }\n let prev = -Infinity\n clip.keys.forEach((k, i) => {\n if (!(typeof k.t === 'number' && Number.isFinite(k.t)) || k.t < 0 || k.t > dur + 1e-9) problems.push(`${label} key ${i} has t=${String(k.t)} outside 0..${dur}`)\n else if (k.t < prev) problems.push(`${label} key ${i} goes back in time (${k.t} < ${prev})`)\n else prev = k.t\n for (const f of ['position', 'rotation'] as const) if (k[f] !== undefined && !finite3(k[f])) problems.push(`${label} key ${i} has an invalid ${f}`)\n for (const [pid, pose] of Object.entries(k.parts ?? {})) {\n if (!parts.has(pid)) problems.push(`${label} key ${i} moves unknown part \"${pid}\"`)\n for (const f of ['position', 'rotation'] as const) if (pose?.[f] !== undefined && !finite3(pose[f])) problems.push(`${label} key ${i} part \"${pid}\" has an invalid ${f}`)\n }\n })\n let prevEv = -Infinity\n ;(clip.events ?? []).forEach((e, i) => {\n if (!(typeof e.t === 'number' && Number.isFinite(e.t)) || e.t < 0 || e.t > dur + 1e-9) problems.push(`${label} event ${i} has t=${String(e.t)} outside 0..${dur}`)\n else if (e.t < prevEv) problems.push(`${label} event ${i} goes back in time`)\n else prevEv = e.t\n if (typeof e.type !== 'string' || e.type === '') problems.push(`${label} event ${i} needs a type`)\n try {\n JSON.stringify(e.data ?? {})\n } catch {\n problems.push(`${label} event ${i} data is not JSON`)\n }\n })\n return { ok: problems.length === 0, problems }\n}\n","import type { AnimationClipSpec, AnimationEventSpec, AnimationPose } from './animation'\nimport { checkAnimation } from './animation'\nimport type { HostEvent } from './module'\n\n/** Clips a tool can bring, one per move of its cycle. A tool brings the ones it has; a missing clip is simply not played. */\nexport const TOOL_CLIP = {\n DRAW: 'draw',\n SHOT: 'shot',\n MISFIRE: 'misfire',\n EMPTY: 'empty',\n CYCLE: 'cycle',\n RELOAD: 'reload',\n FEED: 'feed',\n FAULT: 'fault',\n CLEAR: 'clear',\n SAFETY_ON: 'safety_on',\n SAFETY_OFF: 'safety_off',\n MODE: 'mode',\n /** A throwable with the pin out, held until it is thrown. */\n COOK: 'cook',\n /** The stock folds, unfolds, slides in or out (variants `fold`, `unfold`, `collapse`, `extend`). */\n STOCK: 'stock',\n /** A light or a laser is switched (variants `on`, `off`). */\n TOGGLE: 'toggle',\n} as const\nexport type ToolClipId = (typeof TOOL_CLIP)[keyof typeof TOOL_CLIP]\n\n/** What a renderer does when a rule event arrives. `variant` picks `<clip>_<variant>` when the tool has it, else `<clip>`. */\nexport type ToolAnimationStep =\n | { readonly type: 'play'; readonly clip: ToolClipId; readonly variant?: string; readonly duration?: number; readonly repeat?: number }\n /** Move the clip that is playing to the marker (a clip event `stage` with `data.stage`) the rule just reached. */\n | { readonly type: 'sync'; readonly stage: string }\n /** The rule says the phase is over: the clip jumps to its end if it is still running. */\n | { readonly type: 'finish'; readonly clip: ToolClipId }\n\nconst stockVariant = (event: Extract<HostEvent, { type: 'stock' }>): string => {\n const isFolding = event.kind === undefined ? false : event.kind.includes('fold')\n if (event.state === 'collapsed') return isFolding ? 'fold' : 'collapse'\n return isFolding ? 'unfold' : 'extend'\n}\n\n/** The move a host event asks of the tool in hand, or null when it has no motion. */\nexport const toolAnimationStep = (event: HostEvent): ToolAnimationStep | null => {\n switch (event.type) {\n case 'tool':\n return { type: 'play', clip: TOOL_CLIP.DRAW, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'shot':\n return { type: 'play', clip: TOOL_CLIP.SHOT, repeat: Math.max(1, event.rounds ?? 1) }\n case 'misfire':\n return { type: 'play', clip: TOOL_CLIP.MISFIRE }\n case 'empty':\n return { type: 'play', clip: TOOL_CLIP.EMPTY }\n case 'mode':\n return { type: 'play', clip: TOOL_CLIP.MODE, variant: event.mode }\n case 'fault':\n return { type: 'play', clip: TOOL_CLIP.FAULT, variant: event.fault }\n case 'clear':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CLEAR }\n return { type: 'play', clip: TOOL_CLIP.CLEAR, variant: event.fault, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'safety':\n return { type: 'play', clip: event.on ? TOOL_CLIP.SAFETY_ON : TOOL_CLIP.SAFETY_OFF }\n case 'cycle':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CYCLE }\n return { type: 'play', clip: TOOL_CLIP.CYCLE, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'reload':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.RELOAD }\n return { type: 'play', clip: TOOL_CLIP.RELOAD, ...(event.kind === undefined ? {} : { variant: event.kind }), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'stage':\n return { type: 'sync', stage: event.stage }\n case 'feed':\n return { type: 'play', clip: TOOL_CLIP.FEED }\n case 'cook':\n if (event.phase === 'start') return { type: 'play', clip: TOOL_CLIP.COOK, duration: event.fuse }\n return { type: 'finish', clip: TOOL_CLIP.COOK }\n case 'stock':\n return { type: 'play', clip: TOOL_CLIP.STOCK, variant: stockVariant(event), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'toggle':\n return { type: 'play', clip: TOOL_CLIP.TOGGLE, variant: event.on ? 'on' : 'off' }\n default:\n return null\n }\n}\n\n/** Where a renderer puts the poses: every part of the tool by id (the ids the clips move), and the whole tool. Poses are offsets from rest. */\nexport interface ToolRig {\n setPartPose(partId: string, pose: AnimationPose): void\n setRootPose?(pose: AnimationPose): void\n}\n\n/** Clips by id, with the variants as `<clip>_<variant>`. */\nexport type ToolClipSet = Readonly<Record<string, AnimationClipSpec>>\n\n/** A renderer's tool animator: feed it the host events and the frame time. */\nexport interface ToolAnimator {\n handle(event: HostEvent): void\n update(dt: number): void\n /** Ids of the clips playing now. */\n readonly playing: readonly string[]\n dispose(): void\n}\n\nexport const STAGE_EVENT = 'stage'\n\n/** The clip with its times (keys and events) scaled so that it lasts `duration` seconds. */\nexport const scaleClip = (clip: AnimationClipSpec, duration: number): AnimationClipSpec => {\n const factor = clip.duration > 0 ? duration / clip.duration : 1\n return {\n ...clip,\n duration,\n keys: clip.keys.map((key) => ({ ...key, t: key.t * factor })),\n ...(clip.events ? { events: clip.events.map((event) => ({ ...event, t: event.t * factor })) } : {}),\n }\n}\n\nconst ZERO: [number, number, number] = [0, 0, 0]\nconst mix = (from: readonly number[], to: readonly number[], fraction: number): [number, number, number] => [\n (from[0] ?? 0) + ((to[0] ?? 0) - (from[0] ?? 0)) * fraction,\n (from[1] ?? 0) + ((to[1] ?? 0) - (from[1] ?? 0)) * fraction,\n (from[2] ?? 0) + ((to[2] ?? 0) - (from[2] ?? 0)) * fraction,\n]\n\n/** Pose of the whole tool (`partId` undefined) or of one part at `time`, linear between keys; a key that does not name it means rest. */\nexport const sampleClip = (clip: AnimationClipSpec, partId: string | undefined, time: number): { position: [number, number, number]; rotation: [number, number, number] } => {\n const points = clip.keys.map((key) => {\n const pose = partId === undefined ? key : key.parts?.[partId]\n return { t: key.t, position: pose?.position ?? ZERO, rotation: pose?.rotation ?? ZERO }\n })\n const first = points[0]\n if (!first || first.t > 0) points.unshift({ t: 0, position: ZERO, rotation: ZERO })\n const last = points[points.length - 1]\n if (!last) return { position: ZERO, rotation: ZERO }\n if (time >= last.t) return { position: mix(last.position, last.position, 0), rotation: mix(last.rotation, last.rotation, 0) }\n const index = Math.max(0, points.findIndex((point, i) => time >= point.t && time < (points[i + 1]?.t ?? Infinity)))\n const from = points[index]\n const to = points[index + 1]\n if (!from || !to) return { position: ZERO, rotation: ZERO }\n const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0\n return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) }\n}\n\n/** Problems with a clip set (empty when valid): each key is the clip id (or `<clip>_<variant>`), every clip passes `checkAnimation`, and a `stage` event names its stage. */\nexport const checkToolClips = (clips: ToolClipSet, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const parts = [...partIds]\n const problems: string[] = []\n for (const [key, clip] of Object.entries(clips)) {\n if (clip.id !== key) problems.push(`clip \"${key}\" has the id \"${clip.id}\"`)\n if (!(clip.duration > 0)) problems.push(`clip \"${key}\" has no duration`)\n problems.push(...checkAnimation(clip, parts).problems)\n for (const event of clip.events ?? []) if (event.type === STAGE_EVENT && typeof event.data?.stage !== 'string') problems.push(`clip \"${key}\" has a stage event without data.stage`)\n }\n return { ok: problems.length === 0, problems }\n}\n\ninterface Instance {\n readonly name: string\n readonly clip: AnimationClipSpec\n time: number\n delay: number\n fired: number\n}\n\nexport interface ToolClipPlayerOptions {\n clips: ToolClipSet\n rig: ToolRig\n /** Called for each clip event as its time passes (`eject_casing`, `drop_magazine`, `stage`...). */\n onEvent?(event: AnimationEventSpec, clip: string): void\n /** Seconds between the rounds of a `repeat` (a burst): default 0.1. */\n repeatInterval?(): number\n}\n\n/** The default player of a tool's clips: runs the steps of `toolAnimationStep` against a rig, scaled to the duration the rule gives. */\nexport interface ToolClipPlayer {\n apply(step: ToolAnimationStep): void\n update(dt: number): void\n readonly playing: readonly string[]\n}\n\nexport const createToolClipPlayer = (options: ToolClipPlayerOptions): ToolClipPlayer => {\n const { clips, rig, onEvent } = options\n let instances: Instance[] = []\n\n const find = (clip: string, variant?: string): { name: string; clip: AnimationClipSpec } | undefined => {\n const name = variant !== undefined && clips[`${clip}_${variant}`] ? `${clip}_${variant}` : clip\n const found = clips[name]\n return found ? { name, clip: found } : undefined\n }\n\n const render = (instance: Instance): void => {\n const time = Math.min(instance.time, instance.clip.duration)\n const root = sampleClip(instance.clip, undefined, time)\n if (instance.clip.keys.some((key) => key.position || key.rotation)) rig.setRootPose?.(root)\n const touched = new Set(instance.clip.keys.flatMap((key) => Object.keys(key.parts ?? {})))\n for (const id of touched) rig.setPartPose(id, sampleClip(instance.clip, id, time))\n }\n\n const fireEvents = (instance: Instance, upTo: number): void => {\n const events = instance.clip.events ?? []\n while (instance.fired < events.length && (events[instance.fired]?.t ?? Infinity) <= upTo + 1e-9) {\n const event = events[instance.fired]\n instance.fired += 1\n if (event) onEvent?.(event, instance.name)\n }\n }\n\n return {\n apply(step) {\n if (step.type === 'sync') {\n for (const instance of instances) {\n const marker = (instance.clip.events ?? []).find((event) => event.type === STAGE_EVENT && event.data?.stage === step.stage)\n if (marker && instance.time < marker.t) instance.time = marker.t\n }\n return\n }\n if (step.type === 'finish') {\n for (const instance of instances) {\n if (instance.name !== step.clip && !instance.name.startsWith(`${step.clip}_`)) continue\n fireEvents(instance, instance.clip.duration)\n instance.time = instance.clip.duration\n }\n return\n }\n const found = find(step.clip, step.variant)\n if (!found) return\n const clip = step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip\n const interval = options.repeatInterval?.() ?? 0.1\n instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0)\n for (let round = 0; round < (step.repeat ?? 1); round++) {\n instances.push({ name: found.name, clip, time: 0, delay: round * interval, fired: 0 })\n }\n },\n update(dt) {\n for (const instance of instances) {\n const carried = instance.delay > 0 ? dt - instance.delay : dt\n if (instance.delay > 0) {\n instance.delay = Math.max(0, instance.delay - dt)\n if (instance.delay > 0) continue\n }\n instance.time += carried\n fireEvents(instance, instance.time)\n render(instance)\n }\n instances = instances.filter((instance) => instance.delay > 0 || instance.time < instance.clip.duration)\n },\n get playing() {\n return instances.map((instance) => instance.name)\n },\n }\n}\n","type PlainObject = Record<string, unknown>\ntype Entry = { readonly id: string }\n\n/** Keys that would reach the prototype of the result when a patch comes from parsed JSON. */\nconst UNSAFE_KEYS: ReadonlySet<string> = new Set(['__proto__', 'constructor', 'prototype'])\n\nconst isPlainObject = (value: unknown): value is PlainObject =>\n typeof value === 'object' && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype\n\n/** Recursively optional; arrays and functions are replaced as a whole, objects are merged. */\nexport type DeepPartial<TValue> = TValue extends (...args: never[]) => unknown\n ? TValue\n : TValue extends readonly unknown[]\n ? TValue\n : TValue extends object\n ? { [TKey in keyof TValue]?: DeepPartial<TValue[TKey]> }\n : TValue\n\n/** Merges `patch` over `base`: plain objects merge field by field, arrays, functions and primitives replace. `undefined` in the patch is ignored. */\nexport const deepMerge = <TBase extends object>(base: TBase, patch: object | undefined): TBase => {\n if (!patch) return base\n const result: PlainObject = { ...(base as PlainObject) }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined || UNSAFE_KEYS.has(key)) continue\n const current = result[key]\n result[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value\n }\n return result as TBase\n}\n\n/** The shipped entries with the ones from `extra` added or, when the id exists, replaced. */\nexport const mergeById = <TEntry extends Entry>(shipped: readonly TEntry[], extra: readonly TEntry[] | undefined): Map<string, TEntry> => {\n const entries = new Map<string, TEntry>()\n for (const entry of [...shipped, ...(extra ?? [])]) entries.set(entry.id, entry)\n return entries\n}\n\n/** Applies `patch` by id; a patch for an id that does not exist is ignored. */\nexport const applyPatches = <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, DeepPartial<TEntry> | Partial<TEntry>>> | undefined): void => {\n for (const [id, patch] of Object.entries(patches ?? {})) {\n const entry = entries.get(id)\n if (entry) entries.set(id, deepMerge(entry, patch))\n }\n}\n\nexport const removeIds = (entries: Map<string, unknown>, ids: readonly string[] | undefined): void => {\n for (const id of ids ?? []) entries.delete(id)\n}\n","import type { Vec3 } from './destruction'\n\n/**\n * 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\n * every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.\n */\nexport type ControlValue = number | boolean | string\n\nexport interface ChoiceOption {\n readonly id: string\n readonly name: string\n /** Heading the host lists the option under (`optgroup`, a grouped row of buttons). Options of a group must sit next to each other. */\n readonly group?: string\n}\n\ninterface ControlBase {\n /** snake_case, unique across the module's controls, layers and metrics. */\n readonly id: string\n /** Display name, already translated for the locale the host asked for. */\n readonly name: string\n readonly hint?: string\n readonly hidden?: boolean\n}\n\nexport interface NumberControl extends ControlBase {\n readonly kind: 'number'\n readonly min: number\n readonly max: number\n readonly step?: number\n readonly unit?: string\n readonly default: number\n}\n\nexport interface BooleanControl extends ControlBase {\n readonly kind: 'boolean'\n readonly default: boolean\n}\n\nexport interface ChoiceControl extends ControlBase {\n readonly kind: 'choice'\n readonly options: readonly ChoiceOption[]\n readonly default: string\n}\n\n/** A button: no value, only the press. */\nexport interface ActionControl extends ControlBase {\n readonly kind: 'action'\n}\n\nexport type ModuleControl = NumberControl | BooleanControl | ChoiceControl | ActionControl\n\n/** Something of the module the viewer turns on or off (smoke, flash, casings). */\nexport interface ModuleLayer {\n readonly id: string\n readonly name: string\n /** On unless `false`. */\n readonly default?: boolean\n}\n\n/** Pulled: the room calls read only when it samples (~4 Hz, or on TestRoom.metrics()). */\nexport interface ModuleMetric {\n readonly id: string\n readonly name: string\n readonly unit?: string\n /** Budget: the host shows a bar and warns past it. */\n readonly max?: number\n readonly digits?: number\n read(): number | string\n}\n\nexport type BenchChange =\n | { readonly kind: 'control'; readonly id: string; readonly value: ControlValue }\n | { readonly kind: 'layer'; readonly id: string; readonly on: boolean }\n | { readonly kind: 'action'; readonly id: string }\n\nexport interface ControlPatch {\n readonly hidden?: boolean\n readonly disabled?: boolean\n readonly options?: readonly ChoiceOption[]\n readonly value?: ControlValue\n}\n\n/** What the host lends a module that declares a bench: the current values and the changes. */\nexport interface ModuleBench {\n value(id: string): ControlValue | undefined\n layer(id: string): boolean\n /** Right after attach returns, the host sends one change per control (not actions) and per layer, in declaration order. */\n onChange(listener: (change: BenchChange) => void): () => void\n /** `value` sets without emitting (no echo loops); `options` falls back to the first option if the value is no longer offered. */\n patch(id: string, patch: ControlPatch): void\n /** Move the host camera (a near / middle / far choice). */\n look(eye: Vec3, target: Vec3): void\n /** Shows a sheet on the host's panel, or takes it down with `null`. A host without a sheet area ignores it. */\n sheet?(sheet: BenchSheet | null): void\n}\n\n/**\n * A card of text the module puts on the host's panel (the sheet of an effect, of a weapon): what it is, its numbers, its steps, its colours and,\n * optionally, a strip of segments over time. Names and texts are already translated for the host's locale.\n */\nexport interface BenchSheet {\n readonly kicker?: string\n readonly title: string\n readonly body?: string\n readonly facts?: readonly { readonly label: string; readonly value: string }[]\n readonly steps?: readonly { readonly at: string; readonly text: string }[]\n readonly swatches?: readonly { readonly color: string; readonly name: string }[]\n readonly note?: string\n /** A strip of named segments (seconds), with tick marks and a playhead that follows the module metric `metric`. */\n readonly timeline?: {\n readonly segments: readonly { readonly name: string; readonly start: number; readonly end: number; readonly color: string }[]\n readonly ticks: readonly number[]\n readonly metric: string\n }\n}\n\nexport interface BenchReport {\n readonly ok: boolean\n readonly problems: readonly string[]\n}\n\nexport interface BenchDeclaration {\n readonly controls?: readonly ModuleControl[]\n readonly layers?: readonly ModuleLayer[]\n readonly metrics?: readonly ModuleMetric[]\n}\n\nconst SNAKE_CASE = /^[a-z][a-z0-9_]*$/\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\n\nfunction checkControl(control: ModuleControl, problems: string[]): void {\n const label = `control \"${control.id}\"`\n if (control.kind === 'number') {\n if (!finite(control.min) || !finite(control.max) || control.min >= control.max) problems.push(`${label} needs min < max`)\n if (control.step !== undefined && !(finite(control.step) && control.step > 0)) problems.push(`${label} needs a step > 0`)\n if (!finite(control.default) || control.default < control.min || control.default > control.max) problems.push(`${label} default ${String(control.default)} is outside ${control.min}..${control.max}`)\n return\n }\n if (control.kind !== 'choice') return\n if (control.options.length === 0) {\n problems.push(`${label} needs at least one option`)\n return\n }\n const seen = new Set<string>()\n const groups = new Set<string>()\n let previousGroup: string | undefined\n for (const option of control.options) {\n if (seen.has(option.id)) problems.push(`${label} repeats option \"${option.id}\"`)\n seen.add(option.id)\n if (option.group !== undefined && (typeof option.group !== 'string' || option.group === '')) problems.push(`${label} option \"${option.id}\" has an empty group`)\n else if (option.group !== undefined && option.group !== previousGroup && groups.has(option.group)) problems.push(`${label} group \"${option.group}\" is split: its options must be next to each other`)\n if (typeof option.group === 'string' && option.group !== '') groups.add(option.group)\n previousGroup = option.group\n }\n if (!seen.has(control.default)) problems.push(`${label} default \"${control.default}\" is not among its options`)\n}\n\n/** snake_case ids unique across controls, layers and metrics; min < max; step > 0; default in range; options non-empty, unique, default among them, each group in one run; metric max > 0. */\nexport function checkBench(declared: BenchDeclaration): BenchReport {\n const problems: string[] = []\n const ids = new Set<string>()\n const claim = (kind: string, id: string): void => {\n if (!SNAKE_CASE.test(id)) problems.push(`${kind} id \"${id}\" must be snake_case`)\n if (ids.has(id)) problems.push(`duplicate id \"${id}\" across controls, layers and metrics`)\n ids.add(id)\n }\n for (const control of declared.controls ?? []) {\n claim('control', control.id)\n checkControl(control, problems)\n }\n for (const layer of declared.layers ?? []) claim('layer', layer.id)\n for (const metric of declared.metrics ?? []) {\n claim('metric', metric.id)\n if (metric.max !== undefined && !(finite(metric.max) && metric.max > 0)) problems.push(`metric \"${metric.id}\" needs a max > 0`)\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The weather, light and ground as the world is right now. Written by whoever drives the weather, read by whoever reacts to it. */\nexport interface EnvironmentState {\n /** Rain in mm/h; 0 is dry. */\n readonly rain: number\n /** Wind in m/s along x and z, and its strength (the length of that vector). */\n readonly windX: number\n readonly windZ: number\n readonly windSpeed: number\n /** Strongest gust in m/s. */\n readonly gust: number\n /** Render fog far distance in metres. */\n readonly fogFar: number\n /** How far one can see for gameplay, in metres (not the render fog). */\n readonly visibility: number\n /** 0 is full daylight, 1 is the darkest night. */\n readonly nightFactor: number\n readonly temperatureC: number\n /** Wet ground, 0..1: rises with rain and dries after it stops. */\n readonly wetness: number\n}\n\nexport type EnvironmentKey = keyof EnvironmentState\n\n/** A calm dry day (the normal world, before anything went wrong). */\nexport const DEFAULT_ENVIRONMENT: EnvironmentState = {\n rain: 0,\n windX: 0,\n windZ: 0,\n windSpeed: 0,\n gust: 0,\n fogFar: 220,\n visibility: 220,\n nightFactor: 0,\n temperatureC: 20,\n wetness: 0,\n}\n\n/** The host's environment (`ModuleHost.environment`): a store the weather module writes to and the others read. */\nexport interface EnvironmentBus {\n get(): EnvironmentState\n /** Merges `patch` into the state; listeners hear it only when a value really changed. */\n set(patch: Partial<EnvironmentState>): void\n /** Hears every change with the keys that changed. Returns the unsubscribe function. */\n on(listener: (state: EnvironmentState, changed: readonly EnvironmentKey[]) => void): () => void\n}\n\n/** A plain store that implements the bus; the test room and tests use it, a game may bring its own. */\nexport function createEnvironmentBus(initial: Partial<EnvironmentState> = {}): EnvironmentBus {\n let state: EnvironmentState = { ...DEFAULT_ENVIRONMENT, ...initial }\n const listeners = new Set<(state: EnvironmentState, changed: readonly EnvironmentKey[]) => void>()\n return {\n get: () => state,\n set(patch) {\n const changed = (Object.keys(patch) as EnvironmentKey[]).filter((key) => patch[key] !== undefined && patch[key] !== state[key])\n if (changed.length === 0) return\n const next = { ...state } as { -readonly [Key in EnvironmentKey]: number }\n for (const key of changed) next[key] = patch[key] as number\n state = next\n for (const listener of [...listeners]) listener(state, changed)\n },\n on(listener) {\n listeners.add(listener)\n return () => void listeners.delete(listener)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACuCO,MAAM,kBAAsD;AAAA,IACjE,WAAW,EAAE,UAAU,CAAC,QAAQ,SAAS,aAAa,UAAU,SAAS,GAAG,UAAU,CAAC,SAAS,QAAQ,GAAG,MAAM,0DAA0D;AAAA,IAC3K,OAAO,EAAE,UAAU,CAAC,QAAQ,SAAS,QAAQ,GAAG,UAAU,CAAC,aAAa,UAAU,SAAS,QAAQ,GAAG,MAAM,0CAA0C;AAAA,IACtJ,OAAO,EAAE,UAAU,CAAC,QAAQ,SAAS,QAAQ,GAAG,UAAU,CAAC,aAAa,UAAU,SAAS,QAAQ,GAAG,MAAM,yBAAyB;AAAA,IACrI,WAAW,EAAE,UAAU,CAAC,QAAQ,SAAS,UAAU,QAAQ,GAAG,UAAU,CAAC,SAAS,QAAQ,GAAG,MAAM,+DAA+D;AAAA,IAClK,MAAM,EAAE,UAAU,CAAC,QAAQ,SAAS,eAAe,QAAQ,GAAG,UAAU,CAAC,YAAY,SAAS,QAAQ,GAAG,MAAM,+EAA4E;AAAA,IAC3L,MAAM,EAAE,UAAU,CAAC,QAAQ,SAAS,eAAe,QAAQ,GAAG,UAAU,CAAC,YAAY,SAAS,QAAQ,GAAG,MAAM,gDAAgD;AAAA,EACjK;AAGO,MAAM,WAAW,CAAC,QAAuB;AAC9C,UAAM,IAAI,gBAAgB,IAAI,IAAI;AAClC,QAAI,CAAC,EAAG,QAAO,CAAC,wBAAwB,OAAO,IAAI,IAAI,CAAC,GAAG;AAC3D,UAAM,MAAgB,CAAC;AACvB,eAAW,KAAK,EAAE,UAAU;AAC1B,YAAM,IAAK,IAA2C,CAAC;AACvD,UAAI,MAAM,WAAc,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC,IAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,GAAI,KAAI,KAAK,GAAG,IAAI,IAAI,eAAe,CAAC,GAAG;AAAA,IACxK;AACA,WAAO;AAAA,EACT;AAUA,MAAM,aAAa,CAAC,GAAY,MAAc,QAAkB;AAC9D,QAAI,OAAO,MAAM,UAAU;AACzB,UAAI,CAAC,OAAO,SAAS,CAAC,EAAG,KAAI,KAAK,GAAG,IAAI,yBAAyB;AAAA,IACpE,WAAW,MAAM,QAAQ,CAAC,EAAG,GAAE,QAAQ,CAAC,GAAG,MAAM,WAAW,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC;AAAA,aAC3E,KAAK,OAAO,MAAM,SAAU,YAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,CAAC,EAAG,YAAW,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG;AAAA,aACrG,OAAO,MAAM,cAAc,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,KAAI,KAAK,GAAG,IAAI,cAAc;AAAA,EACpH;AAOO,MAAM,oBAAoB,CAAC,MAAwB,UAAwB,CAAC,MAA2C;AAC5H,UAAM,WAAqB,CAAC;AAC5B,UAAM,QAAQ,QAAQ,YAAY,IAAI,IAAI,QAAQ,SAAS,IAAI;AAC/D,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,SAAS;AAAA,IACvB,SAAS,GAAG;AACV,aAAO,EAAE,IAAI,OAAO,UAAU,CAAC,qBAAqB,OAAO,CAAC,CAAC,EAAE,EAAE;AAAA,IACnE;AACA,QAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,WAAW,EAAG,QAAO,EAAE,IAAI,OAAO,UAAU,CAAC,0CAA0C,EAAE;AAC/I,UAAM,MAAM,oBAAI,IAAY;AAC5B,eAAW,KAAK,KAAK,OAAO;AAC1B,UAAI,IAAI,IAAI,EAAE,EAAE,EAAG,UAAS,KAAK,sBAAsB,EAAE,EAAE,GAAG;AAC9D,UAAI,IAAI,EAAE,EAAE;AACZ,UAAI,SAAS,CAAC,MAAM,IAAI,EAAE,QAAQ,EAAG,UAAS,KAAK,SAAS,EAAE,EAAE,2BAA2B,EAAE,QAAQ,GAAG;AACxG,UAAI,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,YAAY,QAAQ,EAAE,SAAS,EAAE,MAAM,IAAI,EAAG,UAAS,KAAK,SAAS,EAAE,EAAE,sBAAsB;AACxH,UAAI,CAAC,MAAM,QAAQ,EAAE,QAAQ,KAAK,EAAE,SAAS,WAAW,EAAG,UAAS,KAAK,SAAS,EAAE,EAAE,mBAAmB;AAAA,IAC3G;AACA,UAAM,WAAW,oBAAI,IAAY;AACjC,eAAW,KAAM,KAAK,UAAU,CAAC,GAAmB;AAClD,UAAI,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,EAAG,UAAS,KAAK,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,8BAA8B;AACjH,YAAM,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;AACjC,UAAI,SAAS,IAAI,GAAG,EAAG,UAAS,KAAK,uBAAuB,GAAG,GAAG;AAClE,eAAS,IAAI,GAAG;AAAA,IAClB;AACA,UAAM,aAAa,CAAC,UAAkB;AACpC,UAAI;AACJ,UAAI;AACF,aAAK,KAAK,MAAM;AAAA,MAClB,SAAS,GAAG;AACV,iBAAS,KAAK,iBAAiB,KAAK,KAAK,OAAO,CAAC,CAAC,EAAE;AACpD;AAAA,MACF;AACA,YAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG;AACtD,UAAI,SAAS,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,EAAG,UAAS,KAAK,4CAA4C,KAAK,EAAE;AACzG,iBAAW,KAAK,GAAG,MAAO,KAAI,EAAE,EAAE,MAAM,GAAI,UAAS,KAAK,SAAS,EAAE,EAAE,gCAAgC,KAAK,EAAE;AAAA,IAChH;AACA,UAAM,cAAc,CAAC,QAAiB,UAAkB;AACtD,UAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,iBAAS,KAAK,GAAG,KAAK,uBAAuB;AAC7C;AAAA,MACF;AACA,YAAM,MAAM;AACZ,UAAI,QAAQ,CAAC,GAAG,MAAM;AACpB,YAAI,EAAE,WAAW,KAAK,GAAI,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,KAAK,EAAE,IAAI,iBAAiB,OAAO,EAAE,MAAM,CAAC,gBAAgB,KAAK,EAAE,GAAG;AACjI,cAAM,OAAQ,EAAwB;AACtC,YAAI,SAAS,UAAa,CAAC,IAAI,IAAI,IAAI,EAAG,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,KAAK,EAAE,IAAI,yBAAyB,IAAI,GAAG;AAAA,MACxH,CAAC;AACD,YAAM,MAAgB,CAAC;AACvB,iBAAW,KAAK,UAAU,GAAG;AAC7B,eAAS,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK,KAAK,CAAC,EAAE,CAAC;AACjD,UAAI;AACF,cAAM,IAAI,KAAK,UAAU,GAAG;AAC5B,YAAI,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,MAAM,EAAG,UAAS,KAAK,GAAG,KAAK,0CAA0C;AAAA,MAC3G,SAAS,GAAG;AACV,iBAAS,KAAK,GAAG,KAAK,sCAAsC,OAAO,CAAC,CAAC,EAAE;AAAA,MACzE;AAAA,IACF;AACA,eAAW,gBAAgB;AAC3B,UAAM,SAAS,KAAK,MAAM,CAAC;AAC3B,QAAI;AACF,YAAM,MAAW,EAAE,MAAM,aAAa,OAAO,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO,SAAS,CAAC,GAAG,OAAO,SAAS,CAAC,CAAC,GAAG,WAAW,CAAC,GAAG,GAAG,EAAE,GAAG,QAAQ,KAAK,SAAS,EAAE;AAC1J,kBAAY,KAAK,IAAI,GAAG,GAAG,OAAO;AAAA,IACpC,SAAS,GAAG;AACV,eAAS,KAAK,gBAAgB,OAAO,CAAC,CAAC,EAAE;AAAA,IAC3C;AACA,eAAW,eAAe;AAC1B,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,oBAAY,KAAK,OAAO,KAAK,GAAG,UAAU;AAAA,MAC5C,SAAS,GAAG;AACV,iBAAS,KAAK,mBAAmB,OAAO,CAAC,CAAC,EAAE;AAAA,MAC9C;AACA,iBAAW,cAAc;AAAA,IAC3B;AACA,QAAI;AACF,WAAK,QAAQ;AAAA,IACf,SAAS,GAAG;AACV,eAAS,KAAK,kBAAkB,OAAO,CAAC,CAAC,EAAE;AAAA,IAC7C;AACA,WAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAAA,EAC/C;;;ACxJO,MAAM,eAAe;AAAA,IAC1B,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,KAAK;AAAA,EACP;;;ACNA,MAAM,kBAAkB,CAAC,cAAc,UAAU,QAAQ,QAAQ,QAAQ,aAAa,QAAQ,SAAS,MAAM;AAC7G,MAAM,QAAQ,CAAC,MAAM,OAAO,UAAU,QAAQ;AAE9C,MAAM,SAAS,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AACtF,MAAM,WAAW,CAAC,MAAwB,OAAO,CAAC,KAAK,IAAI;AAC3D,MAAM,OAAO,CAAC,MAAwB,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,KAAK,EAAE,MAAM,MAAM;AAC1F,MAAM,cAAc,CAAC,MAAwB,MAAM,UAAc,OAAO,CAAC,KAAK,KAAK;AAEnF,MAAM,QAAQ,CAAC,QAAQ,KAAK,GAAG;AAC/B,MAAM,QAAQ,CAAC,WAAW,OAAO;AAGjC,MAAM,oBAAoB,CAAC,GAAyB,OAAe,WAA+B,aAA6B;AAC7H,UAAM,QAAQ,CAAC,GAAY,SAAuB;AAChD,UAAI,aAAa,CAAC,UAAU,IAAI,CAAW,EAAG,UAAS,KAAK,GAAG,KAAK,IAAI,IAAI,0BAA0B,OAAO,CAAC,CAAC,GAAG;AAAA,IACpH;AACC,KAAC,GAAG,UAAU,CAAC,GAAG,QAAQ,CAAC,GAAG,MAAM;AACnC,YAAM,GAAG,UAAU,SAAS,CAAC,EAAE;AAC/B,UAAI,CAAC,SAAS,GAAG,SAAS,EAAG,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,wCAAwC;AACtG,UAAI,CAAC,MAAM,SAAS,GAAG,KAAK,EAAG,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,0BAA0B,OAAO,GAAG,KAAK,CAAC,GAAG;AAAA,IAC/G,CAAC;AACD,UAAM,IAAI,GAAG;AACb,QAAI,GAAG;AACL,YAAM,EAAE,UAAU,eAAe;AACjC,UAAI,CAAC,SAAS,EAAE,OAAO,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,iEAAiE;AAC1I,UAAI,EAAE,qBAAqB,UAAa,EAAE,OAAO,EAAE,gBAAgB,KAAK,EAAE,oBAAoB,KAAK,EAAE,oBAAoB,GAAI,UAAS,KAAK,GAAG,KAAK,yDAAyD;AAAA,IAC9M;AACA,UAAM,IAAI,GAAG;AACb,QAAI,GAAG;AACL,YAAM,EAAE,QAAQ,cAAc;AAC9B,UAAI,CAAC,KAAK,EAAE,IAAI,KAAK,CAAC,EAAE,KAAK,MAAM,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,mDAAmD;AACvH,UAAI,CAAC,MAAM,SAAS,EAAE,IAAI,EAAG,UAAS,KAAK,GAAG,KAAK,+BAA+B,OAAO,EAAE,IAAI,CAAC,GAAG;AAAA,IACrG;AACA,QAAI,GAAG,UAAU,EAAE,OAAO,SAAS,UAAa,CAAC,SAAS,EAAE,OAAO,IAAI,EAAG,UAAS,KAAK,GAAG,KAAK,0CAA0C;AAAA,EAC5I;AAQO,MAAM,YAAY,CAAC,MAAwB,UAA4B,CAAC,MAAkB;AAC/F,UAAM,WAAqB,CAAC;AAC5B,UAAM,WAAqB,CAAC;AAC5B,UAAM,YAAY,QAAQ,YAAY,IAAI,IAAI,QAAQ,SAAS,IAAI;AACnE,UAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAAI,KAAK,QAAQ,CAAC;AACzD,QAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,KAAK,MAAM,WAAW,EAAG,UAAS,KAAK,kCAAkC;AACvG,UAAM,MAAM,oBAAI,IAAY;AAC5B,eAAW,KAAK,OAAO;AACrB,YAAM,KAAK,OAAO,GAAG,MAAM,EAAE;AAC7B,YAAM,QAAQ,SAAS,EAAE;AACzB,UAAI,OAAO,GAAI,UAAS,KAAK,kBAAkB;AAAA,eACtC,IAAI,IAAI,EAAE,EAAG,UAAS,KAAK,sBAAsB,EAAE,GAAG;AAC/D,UAAI,IAAI,EAAE;AACV,UAAI,aAAa,CAAC,UAAU,IAAI,GAAG,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,0BAA0B,OAAO,GAAG,QAAQ,CAAC,GAAG;AAAA,eAC3G,OAAO,GAAG,aAAa,YAAY,EAAE,aAAa,GAAI,UAAS,KAAK,GAAG,KAAK,mBAAmB;AACxG,YAAM,IAAI,GAAG;AACb,UAAI,GAAG,SAAS,OAAO;AACrB,YAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,KAAK,EAAE,KAAK,WAAW,KAAK,CAAC,EAAE,KAAK,MAAM,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,iDAAiD;AAAA,MACvJ,WAAW,GAAG,SAAS,YAAY;AACjC,YAAI,CAAC,SAAS,EAAE,MAAM,KAAK,CAAC,SAAS,EAAE,MAAM,EAAG,UAAS,KAAK,GAAG,KAAK,yDAAyD;AAAA,MACjI,WAAW,GAAG,SAAS,UAAU;AAC/B,YAAI,CAAC,SAAS,EAAE,MAAM,EAAG,UAAS,KAAK,GAAG,KAAK,4CAA4C;AAAA,MAC7F,MAAO,UAAS,KAAK,GAAG,KAAK,uBAAuB;AACpD,UAAI,CAAC,KAAK,GAAG,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,oCAAoC;AAClF,UAAI,GAAG,aAAa,UAAa,CAAC,KAAK,EAAE,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,0BAA0B;AACpG,iBAAW,KAAK,CAAC,QAAQ,MAAM,WAAW,EAAY,KAAI,CAAC,YAAY,IAAI,CAAC,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,IAAI,CAAC,+BAA+B;AAC1I,UAAI,GAAG,SAAS,UAAa,EAAE,OAAO,EAAE,IAAI,KAAK,EAAE,QAAQ,KAAK,EAAE,QAAQ,GAAI,UAAS,KAAK,GAAG,KAAK,+BAA+B;AACnI,UAAI,GAAG,WAAW;AAChB,YAAI,CAAC,gBAAgB,SAAS,EAAE,UAAU,IAAI,EAAG,UAAS,KAAK,GAAG,KAAK,mCAAmC,OAAO,EAAE,UAAU,IAAI,CAAC,GAAG;AACrI,YAAI,CAAC,YAAY,EAAE,UAAU,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,kDAAkD;AAAA,MAClH;AACA,UAAI,GAAG,YAAY,CAAC,YAAY,EAAE,SAAS,MAAM,EAAG,UAAS,KAAK,GAAG,KAAK,+CAA+C;AACzH,wBAAkB,GAAG,OAAO,WAAW,QAAQ;AAAA,IACjD;AAEA,UAAM,WAAW,oBAAI,IAAY;AACjC,UAAM,QAAiC,CAAC;AACxC,eAAW,KAAK,MAAM,UAAU,CAAC,GAAG;AAClC,YAAM,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;AAChC,YAAM,QAAQ,UAAU,EAAE;AAC1B,UAAI,SAAS,IAAI,EAAE,EAAG,UAAS,KAAK,uBAAuB,EAAE,GAAG;AAChE,eAAS,IAAI,EAAE;AACf,UAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,6BAA6B,OAAO,EAAE,CAAC,CAAC,GAAG;AACpF,UAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,6BAA6B,OAAO,EAAE,CAAC,CAAC,GAAG;AACpF,UAAI,EAAE,MAAM,EAAE,EAAG,UAAS,KAAK,GAAG,KAAK,gBAAgB,OAAO,EAAE,CAAC,CAAC,aAAa;AAC/E,UAAI,EAAE,UAAU,UAAa,CAAC,MAAM,SAAS,EAAE,KAAK,EAAG,UAAS,KAAK,GAAG,KAAK,0BAA0B,OAAO,EAAE,KAAK,CAAC,GAAG;AACzH,UAAI,CAAC,YAAY,EAAE,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,wCAAwC;AAC5F,UAAI,CAAC,YAAY,EAAE,OAAO,EAAG,UAAS,KAAK,GAAG,KAAK,uCAAuC;AAC1F,UAAI,EAAE,UAAU,SAAS,IAAI,IAAI,EAAE,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAG,OAAM,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;AAAA,IAC7F;AAGA,UAAM,aAAa,MAAM,OAAO,CAAC,MAAM,GAAG,aAAa,EAAE,UAAU,SAAS,MAAM;AAClF,QAAI,WAAW,UAAU,KAAK,QAAQ;AACpC,YAAM,WAAW,MAAM,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,SAAS,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;AACtG,YAAM,MAAM,oBAAI,IAAsB;AACtC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO;AAC1B,YAAI,IAAI,GAAG,CAAC,GAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAI,CAAC,CAAC;AACrC,YAAI,IAAI,GAAG,CAAC,GAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAI,CAAC,CAAC;AAAA,MACvC;AACA,YAAM,OAAO,IAAI,IAAI,QAAQ;AAC7B,YAAM,QAAQ,CAAC,GAAG,QAAQ;AAC1B,aAAO,MAAM,QAAQ;AACnB,mBAAW,KAAK,IAAI,IAAI,MAAM,IAAI,CAAE,KAAK,CAAC,GAAG;AAC3C,cAAI,KAAK,IAAI,CAAC,EAAG;AACjB,eAAK,IAAI,CAAC;AACV,gBAAM,KAAK,CAAC;AAAA,QACd;AAAA,MACF;AACA,YAAM,OAAO,WAAW,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;AACtE,UAAI,KAAK,OAAQ,UAAS,KAAK,sBAAsB,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,mDAAmD;AAAA,IAC9I;AACA,WAAO,EAAE,IAAI,SAAS,WAAW,GAAG,UAAU,SAAS;AAAA,EACzD;;;AC9CO,MAAM,YAAY,CAAC,MAAY,OAAa,WAAuB;AACxE,SAAK;AACL,UAAM,KAAK,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC;AACnC,UAAM,KAAK,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC;AACnC,UAAM,KAAK,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC;AACnC,UAAM,WAAW,KAAK,MAAM,IAAI,IAAI,EAAE;AACtC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AAAA,MACpC,WAAW,CAAC,KAAK,UAAU,CAAC,GAAG,KAAK,UAAU,CAAC,GAAG,KAAK,UAAU,CAAC,CAAC;AAAA,MACnE,QAAQ,KAAK,SAAS,QAAQ;AAAA,MAC9B,SAAS,KAAK;AAAA,MACd,GAAI,KAAK,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK;AAAA,MACrD,QAAQ,KAAK,UAAU,KAAK;AAAA,IAC9B;AAAA,EACF;;;AC1DA,MAAM,UAAU,CAAC,MAAwB,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,CAAC;AAOlI,MAAM,iBAAiB,CAAC,MAAyB,YAAmE;AACzH,UAAM,WAAqB,CAAC;AAC5B,UAAM,QAAQ,IAAI,IAAI,OAAO;AAC7B,UAAM,KAAK,OAAO,MAAM,MAAM,EAAE;AAChC,QAAI,CAAC,oBAAoB,KAAK,EAAE,EAAG,UAAS,KAAK,YAAY,EAAE,sBAAsB;AACrF,UAAM,QAAQ,SAAS,EAAE;AACzB,QAAI,EAAE,OAAO,MAAM,aAAa,YAAY,OAAO,SAAS,KAAK,QAAQ,KAAK,KAAK,WAAW,GAAI,UAAS,KAAK,GAAG,KAAK,mCAAmC;AAC3J,UAAM,MAAM,OAAO,MAAM,aAAa,YAAY,KAAK,WAAW,IAAI,KAAK,WAAW;AACtF,QAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,KAAK,KAAK,KAAK,WAAW,GAAG;AACxD,eAAS,KAAK,GAAG,KAAK,yBAAyB;AAC/C,aAAO,EAAE,IAAI,OAAO,SAAS;AAAA,IAC/B;AACA,QAAI,OAAO;AACX,SAAK,KAAK,QAAQ,CAAC,GAAG,MAAM;AAC1B,UAAI,EAAE,OAAO,EAAE,MAAM,YAAY,OAAO,SAAS,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,EAAE,IAAI,MAAM,KAAM,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC,eAAe,GAAG,EAAE;AAAA,eACtJ,EAAE,IAAI,KAAM,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,uBAAuB,EAAE,CAAC,MAAM,IAAI,GAAG;AAAA,UACtF,QAAO,EAAE;AACd,iBAAW,KAAK,CAAC,YAAY,UAAU,EAAY,KAAI,EAAE,CAAC,MAAM,UAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,mBAAmB,CAAC,EAAE;AAClJ,iBAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,EAAE,SAAS,CAAC,CAAC,GAAG;AACvD,YAAI,CAAC,MAAM,IAAI,GAAG,EAAG,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,wBAAwB,GAAG,GAAG;AAClF,mBAAW,KAAK,CAAC,YAAY,UAAU,EAAY,KAAI,OAAO,CAAC,MAAM,UAAa,CAAC,QAAQ,KAAK,CAAC,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,UAAU,GAAG,oBAAoB,CAAC,EAAE;AAAA,MAC1K;AAAA,IACF,CAAC;AACD,QAAI,SAAS;AACZ,KAAC,KAAK,UAAU,CAAC,GAAG,QAAQ,CAAC,GAAG,MAAM;AACrC,UAAI,EAAE,OAAO,EAAE,MAAM,YAAY,OAAO,SAAS,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,EAAE,IAAI,MAAM,KAAM,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC,eAAe,GAAG,EAAE;AAAA,eACxJ,EAAE,IAAI,OAAQ,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,oBAAoB;AAAA,UACvE,UAAS,EAAE;AAChB,UAAI,OAAO,EAAE,SAAS,YAAY,EAAE,SAAS,GAAI,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,eAAe;AACjG,UAAI;AACF,aAAK,UAAU,EAAE,QAAQ,CAAC,CAAC;AAAA,MAC7B,QAAQ;AACN,iBAAS,KAAK,GAAG,KAAK,UAAU,CAAC,mBAAmB;AAAA,MACtD;AAAA,IACF,CAAC;AACD,WAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAAA,EAC/C;;;AC/EO,MAAM,YAAY;AAAA,IACvB,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,MAAM;AAAA;AAAA,IAEN,MAAM;AAAA;AAAA,IAEN,OAAO;AAAA;AAAA,IAEP,QAAQ;AAAA,EACV;AAWA,MAAM,eAAe,CAAC,UAAyD;AAC7E,UAAM,YAAY,MAAM,SAAS,SAAY,QAAQ,MAAM,KAAK,SAAS,MAAM;AAC/E,QAAI,MAAM,UAAU,YAAa,QAAO,YAAY,SAAS;AAC7D,WAAO,YAAY,WAAW;AAAA,EAChC;AAGO,MAAM,oBAAoB,CAAC,UAA+C;AAC/E,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,MACrH,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,UAAU,CAAC,EAAE;AAAA,MACtF,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ;AAAA,MACjD,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM;AAAA,MAC/C,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,SAAS,MAAM,KAAK;AAAA,MACnE,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,SAAS,MAAM,MAAM;AAAA,MACrE,KAAK;AACH,YAAI,MAAM,UAAU,MAAO,QAAO,EAAE,MAAM,UAAU,MAAM,UAAU,MAAM;AAC1E,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,SAAS,MAAM,OAAO,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,MAC5I,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK,UAAU,YAAY,UAAU,WAAW;AAAA,MACrF,KAAK;AACH,YAAI,MAAM,UAAU,MAAO,QAAO,EAAE,MAAM,UAAU,MAAM,UAAU,MAAM;AAC1E,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,MACtH,KAAK;AACH,YAAI,MAAM,UAAU,MAAO,QAAO,EAAE,MAAM,UAAU,MAAM,UAAU,OAAO;AAC3E,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ,GAAI,MAAM,SAAS,SAAY,CAAC,IAAI,EAAE,SAAS,MAAM,KAAK,GAAI,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,MACrL,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,OAAO,MAAM,MAAM;AAAA,MAC5C,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,KAAK;AAAA,MAC9C,KAAK;AACH,YAAI,MAAM,UAAU,QAAS,QAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,UAAU,MAAM,KAAK;AAC/F,eAAO,EAAE,MAAM,UAAU,MAAM,UAAU,KAAK;AAAA,MAChD,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,SAAS,aAAa,KAAK,GAAG,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,MACpJ,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ,SAAS,MAAM,KAAK,OAAO,MAAM;AAAA,MAClF;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAoBO,MAAM,cAAc;AAGpB,MAAM,YAAY,CAAC,MAAyB,aAAwC;AACzF,UAAM,SAAS,KAAK,WAAW,IAAI,WAAW,KAAK,WAAW;AAC9D,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,MACA,MAAM,KAAK,KAAK,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,GAAG,IAAI,IAAI,OAAO,EAAE;AAAA,MAC5D,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO,GAAG,MAAM,IAAI,OAAO,EAAE,EAAE,IAAI,CAAC;AAAA,IACnG;AAAA,EACF;AAEA,MAAM,OAAiC,CAAC,GAAG,GAAG,CAAC;AAC/C,MAAM,MAAM,CAAC,MAAyB,IAAuB,aAA+C;AAAA,KACzG,KAAK,CAAC,KAAK,OAAO,GAAG,CAAC,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM;AAAA,KAClD,KAAK,CAAC,KAAK,OAAO,GAAG,CAAC,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM;AAAA,KAClD,KAAK,CAAC,KAAK,OAAO,GAAG,CAAC,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM;AAAA,EACrD;AAGO,MAAM,aAAa,CAAC,MAAyB,QAA4B,SAA6F;AAC3K,UAAM,SAAS,KAAK,KAAK,IAAI,CAAC,QAAQ;AACpC,YAAM,OAAO,WAAW,SAAY,MAAM,IAAI,QAAQ,MAAM;AAC5D,aAAO,EAAE,GAAG,IAAI,GAAG,UAAU,MAAM,YAAY,MAAM,UAAU,MAAM,YAAY,KAAK;AAAA,IACxF,CAAC;AACD,UAAM,QAAQ,OAAO,CAAC;AACtB,QAAI,CAAC,SAAS,MAAM,IAAI,EAAG,QAAO,QAAQ,EAAE,GAAG,GAAG,UAAU,MAAM,UAAU,KAAK,CAAC;AAClF,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,QAAI,CAAC,KAAM,QAAO,EAAE,UAAU,MAAM,UAAU,KAAK;AACnD,QAAI,QAAQ,KAAK,EAAG,QAAO,EAAE,UAAU,IAAI,KAAK,UAAU,KAAK,UAAU,CAAC,GAAG,UAAU,IAAI,KAAK,UAAU,KAAK,UAAU,CAAC,EAAE;AAC5H,UAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,UAAU,CAAC,OAAO,MAAM,QAAQ,MAAM,KAAK,QAAQ,OAAO,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC;AAClH,UAAM,OAAO,OAAO,KAAK;AACzB,UAAM,KAAK,OAAO,QAAQ,CAAC;AAC3B,QAAI,CAAC,QAAQ,CAAC,GAAI,QAAO,EAAE,UAAU,MAAM,UAAU,KAAK;AAC1D,UAAM,WAAW,GAAG,IAAI,KAAK,KAAK,OAAO,KAAK,MAAM,GAAG,IAAI,KAAK,KAAK;AACrE,WAAO,EAAE,UAAU,IAAI,KAAK,UAAU,GAAG,UAAU,QAAQ,GAAG,UAAU,IAAI,KAAK,UAAU,GAAG,UAAU,QAAQ,EAAE;AAAA,EACpH;AAGO,MAAM,iBAAiB,CAAC,OAAoB,YAAmE;AACpH,UAAM,QAAQ,CAAC,GAAG,OAAO;AACzB,UAAM,WAAqB,CAAC;AAC5B,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC/C,UAAI,KAAK,OAAO,IAAK,UAAS,KAAK,SAAS,GAAG,iBAAiB,KAAK,EAAE,GAAG;AAC1E,UAAI,EAAE,KAAK,WAAW,GAAI,UAAS,KAAK,SAAS,GAAG,mBAAmB;AACvE,eAAS,KAAK,GAAG,eAAe,MAAM,KAAK,EAAE,QAAQ;AACrD,iBAAW,SAAS,KAAK,UAAU,CAAC,EAAG,KAAI,MAAM,SAAS,eAAe,OAAO,MAAM,MAAM,UAAU,SAAU,UAAS,KAAK,SAAS,GAAG,wCAAwC;AAAA,IACpL;AACA,WAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAAA,EAC/C;AA0BO,MAAM,uBAAuB,CAAC,YAAmD;AACtF,UAAM,EAAE,OAAO,KAAK,QAAQ,IAAI;AAChC,QAAI,YAAwB,CAAC;AAE7B,UAAM,OAAO,CAAC,MAAc,YAA4E;AACtG,YAAM,OAAO,YAAY,UAAa,MAAM,GAAG,IAAI,IAAI,OAAO,EAAE,IAAI,GAAG,IAAI,IAAI,OAAO,KAAK;AAC3F,YAAM,QAAQ,MAAM,IAAI;AACxB,aAAO,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAI;AAAA,IACzC;AAEA,UAAM,SAAS,CAAC,aAA6B;AAC3C,YAAM,OAAO,KAAK,IAAI,SAAS,MAAM,SAAS,KAAK,QAAQ;AAC3D,YAAM,OAAO,WAAW,SAAS,MAAM,QAAW,IAAI;AACtD,UAAI,SAAS,KAAK,KAAK,KAAK,CAAC,QAAQ,IAAI,YAAY,IAAI,QAAQ,EAAG,KAAI,cAAc,IAAI;AAC1F,YAAM,UAAU,IAAI,IAAI,SAAS,KAAK,KAAK,QAAQ,CAAC,QAAQ,OAAO,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;AACzF,iBAAW,MAAM,QAAS,KAAI,YAAY,IAAI,WAAW,SAAS,MAAM,IAAI,IAAI,CAAC;AAAA,IACnF;AAEA,UAAM,aAAa,CAAC,UAAoB,SAAuB;AAC7D,YAAM,SAAS,SAAS,KAAK,UAAU,CAAC;AACxC,aAAO,SAAS,QAAQ,OAAO,WAAW,OAAO,SAAS,KAAK,GAAG,KAAK,aAAa,OAAO,MAAM;AAC/F,cAAM,QAAQ,OAAO,SAAS,KAAK;AACnC,iBAAS,SAAS;AAClB,YAAI,MAAO,WAAU,OAAO,SAAS,IAAI;AAAA,MAC3C;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,MAAM;AACV,YAAI,KAAK,SAAS,QAAQ;AACxB,qBAAW,YAAY,WAAW;AAChC,kBAAM,UAAU,SAAS,KAAK,UAAU,CAAC,GAAG,KAAK,CAAC,UAAU,MAAM,SAAS,eAAe,MAAM,MAAM,UAAU,KAAK,KAAK;AAC1H,gBAAI,UAAU,SAAS,OAAO,OAAO,EAAG,UAAS,OAAO,OAAO;AAAA,UACjE;AACA;AAAA,QACF;AACA,YAAI,KAAK,SAAS,UAAU;AAC1B,qBAAW,YAAY,WAAW;AAChC,gBAAI,SAAS,SAAS,KAAK,QAAQ,CAAC,SAAS,KAAK,WAAW,GAAG,KAAK,IAAI,GAAG,EAAG;AAC/E,uBAAW,UAAU,SAAS,KAAK,QAAQ;AAC3C,qBAAS,OAAO,SAAS,KAAK;AAAA,UAChC;AACA;AAAA,QACF;AACA,cAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,OAAO;AAC1C,YAAI,CAAC,MAAO;AACZ,cAAM,OAAO,KAAK,YAAY,KAAK,WAAW,IAAI,UAAU,MAAM,MAAM,KAAK,QAAQ,IAAI,MAAM;AAC/F,cAAM,WAAW,QAAQ,iBAAiB,KAAK;AAC/C,oBAAY,UAAU,OAAO,CAAC,aAAa,SAAS,SAAS,MAAM,QAAQ,SAAS,QAAQ,CAAC;AAC7F,iBAAS,QAAQ,GAAG,SAAS,KAAK,UAAU,IAAI,SAAS;AACvD,oBAAU,KAAK,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,GAAG,OAAO,QAAQ,UAAU,OAAO,EAAE,CAAC;AAAA,QACvF;AAAA,MACF;AAAA,MACA,OAAO,IAAI;AACT,mBAAW,YAAY,WAAW;AAChC,gBAAM,UAAU,SAAS,QAAQ,IAAI,KAAK,SAAS,QAAQ;AAC3D,cAAI,SAAS,QAAQ,GAAG;AACtB,qBAAS,QAAQ,KAAK,IAAI,GAAG,SAAS,QAAQ,EAAE;AAChD,gBAAI,SAAS,QAAQ,EAAG;AAAA,UAC1B;AACA,mBAAS,QAAQ;AACjB,qBAAW,UAAU,SAAS,IAAI;AAClC,iBAAO,QAAQ;AAAA,QACjB;AACA,oBAAY,UAAU,OAAO,CAAC,aAAa,SAAS,QAAQ,KAAK,SAAS,OAAO,SAAS,KAAK,QAAQ;AAAA,MACzG;AAAA,MACA,IAAI,UAAU;AACZ,eAAO,UAAU,IAAI,CAAC,aAAa,SAAS,IAAI;AAAA,MAClD;AAAA,IACF;AAAA,EACF;;;ACnPA,MAAM,cAAmC,oBAAI,IAAI,CAAC,aAAa,eAAe,WAAW,CAAC;AAE1F,MAAM,gBAAgB,CAAC,UACrB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,KAAK,OAAO,eAAe,KAAK,MAAM,OAAO;AAY3G,MAAM,YAAY,CAAuB,MAAa,UAAqC;AAChG,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,SAAsB,EAAE,GAAI,KAAqB;AACvD,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,UAAI,UAAU,UAAa,YAAY,IAAI,GAAG,EAAG;AACjD,YAAM,UAAU,OAAO,GAAG;AAC1B,aAAO,GAAG,IAAI,cAAc,OAAO,KAAK,cAAc,KAAK,IAAI,UAAU,SAAS,KAAK,IAAI;AAAA,IAC7F;AACA,WAAO;AAAA,EACT;AAGO,MAAM,YAAY,CAAuB,SAA4B,UAA8D;AACxI,UAAM,UAAU,oBAAI,IAAoB;AACxC,eAAW,SAAS,CAAC,GAAG,SAAS,GAAI,SAAS,CAAC,CAAE,EAAG,SAAQ,IAAI,MAAM,IAAI,KAAK;AAC/E,WAAO;AAAA,EACT;AAGO,MAAM,eAAe,CAAuB,SAA8B,YAA+F;AAC9K,eAAW,CAAC,IAAI,KAAK,KAAK,OAAO,QAAQ,WAAW,CAAC,CAAC,GAAG;AACvD,YAAM,QAAQ,QAAQ,IAAI,EAAE;AAC5B,UAAI,MAAO,SAAQ,IAAI,IAAI,UAAU,OAAO,KAAK,CAAC;AAAA,IACpD;AAAA,EACF;AAEO,MAAM,YAAY,CAAC,SAA+B,QAA6C;AACpG,eAAW,MAAM,OAAO,CAAC,EAAG,SAAQ,OAAO,EAAE;AAAA,EAC/C;;;ACgFA,MAAM,aAAa;AACnB,MAAMA,UAAS,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AAEtF,WAAS,aAAa,SAAwB,UAA0B;AACtE,UAAM,QAAQ,YAAY,QAAQ,EAAE;AACpC,QAAI,QAAQ,SAAS,UAAU;AAC7B,UAAI,CAACA,QAAO,QAAQ,GAAG,KAAK,CAACA,QAAO,QAAQ,GAAG,KAAK,QAAQ,OAAO,QAAQ,IAAK,UAAS,KAAK,GAAG,KAAK,kBAAkB;AACxH,UAAI,QAAQ,SAAS,UAAa,EAAEA,QAAO,QAAQ,IAAI,KAAK,QAAQ,OAAO,GAAI,UAAS,KAAK,GAAG,KAAK,mBAAmB;AACxH,UAAI,CAACA,QAAO,QAAQ,OAAO,KAAK,QAAQ,UAAU,QAAQ,OAAO,QAAQ,UAAU,QAAQ,IAAK,UAAS,KAAK,GAAG,KAAK,YAAY,OAAO,QAAQ,OAAO,CAAC,eAAe,QAAQ,GAAG,KAAK,QAAQ,GAAG,EAAE;AACrM;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,SAAU;AAC/B,QAAI,QAAQ,QAAQ,WAAW,GAAG;AAChC,eAAS,KAAK,GAAG,KAAK,4BAA4B;AAClD;AAAA,IACF;AACA,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,SAAS,oBAAI,IAAY;AAC/B,QAAI;AACJ,eAAW,UAAU,QAAQ,SAAS;AACpC,UAAI,KAAK,IAAI,OAAO,EAAE,EAAG,UAAS,KAAK,GAAG,KAAK,oBAAoB,OAAO,EAAE,GAAG;AAC/E,WAAK,IAAI,OAAO,EAAE;AAClB,UAAI,OAAO,UAAU,WAAc,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,IAAK,UAAS,KAAK,GAAG,KAAK,YAAY,OAAO,EAAE,sBAAsB;AAAA,eACrJ,OAAO,UAAU,UAAa,OAAO,UAAU,iBAAiB,OAAO,IAAI,OAAO,KAAK,EAAG,UAAS,KAAK,GAAG,KAAK,WAAW,OAAO,KAAK,oDAAoD;AACpM,UAAI,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,GAAI,QAAO,IAAI,OAAO,KAAK;AACpF,sBAAgB,OAAO;AAAA,IACzB;AACA,QAAI,CAAC,KAAK,IAAI,QAAQ,OAAO,EAAG,UAAS,KAAK,GAAG,KAAK,aAAa,QAAQ,OAAO,4BAA4B;AAAA,EAChH;AAGO,WAAS,WAAW,UAAyC;AAClE,UAAM,WAAqB,CAAC;AAC5B,UAAM,MAAM,oBAAI,IAAY;AAC5B,UAAM,QAAQ,CAAC,MAAc,OAAqB;AAChD,UAAI,CAAC,WAAW,KAAK,EAAE,EAAG,UAAS,KAAK,GAAG,IAAI,QAAQ,EAAE,sBAAsB;AAC/E,UAAI,IAAI,IAAI,EAAE,EAAG,UAAS,KAAK,iBAAiB,EAAE,uCAAuC;AACzF,UAAI,IAAI,EAAE;AAAA,IACZ;AACA,eAAW,WAAW,SAAS,YAAY,CAAC,GAAG;AAC7C,YAAM,WAAW,QAAQ,EAAE;AAC3B,mBAAa,SAAS,QAAQ;AAAA,IAChC;AACA,eAAW,SAAS,SAAS,UAAU,CAAC,EAAG,OAAM,SAAS,MAAM,EAAE;AAClE,eAAW,UAAU,SAAS,WAAW,CAAC,GAAG;AAC3C,YAAM,UAAU,OAAO,EAAE;AACzB,UAAI,OAAO,QAAQ,UAAa,EAAEA,QAAO,OAAO,GAAG,KAAK,OAAO,MAAM,GAAI,UAAS,KAAK,WAAW,OAAO,EAAE,mBAAmB;AAAA,IAChI;AACA,WAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAAA,EAC/C;;;ACxJO,MAAM,sBAAwC;AAAA,IACnD,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,WAAW;AAAA,IACX,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,SAAS;AAAA,EACX;AAYO,WAAS,qBAAqB,UAAqC,CAAC,GAAmB;AAC5F,QAAI,QAA0B,EAAE,GAAG,qBAAqB,GAAG,QAAQ;AACnE,UAAM,YAAY,oBAAI,IAA2E;AACjG,WAAO;AAAA,MACL,KAAK,MAAM;AAAA,MACX,IAAI,OAAO;AACT,cAAM,UAAW,OAAO,KAAK,KAAK,EAAuB,OAAO,CAAC,QAAQ,MAAM,GAAG,MAAM,UAAa,MAAM,GAAG,MAAM,MAAM,GAAG,CAAC;AAC9H,YAAI,QAAQ,WAAW,EAAG;AAC1B,cAAM,OAAO,EAAE,GAAG,MAAM;AACxB,mBAAW,OAAO,QAAS,MAAK,GAAG,IAAI,MAAM,GAAG;AAChD,gBAAQ;AACR,mBAAW,YAAY,CAAC,GAAG,SAAS,EAAG,UAAS,OAAO,OAAO;AAAA,MAChE;AAAA,MACA,GAAG,UAAU;AACX,kBAAU,IAAI,QAAQ;AACtB,eAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;","names":["finite"]}
|
package/dist/index.js
CHANGED
|
@@ -506,9 +506,15 @@ function checkControl(control, problems) {
|
|
|
506
506
|
return;
|
|
507
507
|
}
|
|
508
508
|
const seen = /* @__PURE__ */ new Set();
|
|
509
|
+
const groups = /* @__PURE__ */ new Set();
|
|
510
|
+
let previousGroup;
|
|
509
511
|
for (const option of control.options) {
|
|
510
512
|
if (seen.has(option.id)) problems.push(`${label} repeats option "${option.id}"`);
|
|
511
513
|
seen.add(option.id);
|
|
514
|
+
if (option.group !== void 0 && (typeof option.group !== "string" || option.group === "")) problems.push(`${label} option "${option.id}" has an empty group`);
|
|
515
|
+
else if (option.group !== void 0 && option.group !== previousGroup && groups.has(option.group)) problems.push(`${label} group "${option.group}" is split: its options must be next to each other`);
|
|
516
|
+
if (typeof option.group === "string" && option.group !== "") groups.add(option.group);
|
|
517
|
+
previousGroup = option.group;
|
|
512
518
|
}
|
|
513
519
|
if (!seen.has(control.default)) problems.push(`${label} default "${control.default}" is not among its options`);
|
|
514
520
|
}
|
|
@@ -531,7 +537,41 @@ function checkBench(declared) {
|
|
|
531
537
|
}
|
|
532
538
|
return { ok: problems.length === 0, problems };
|
|
533
539
|
}
|
|
540
|
+
|
|
541
|
+
// src/environment.ts
|
|
542
|
+
var DEFAULT_ENVIRONMENT = {
|
|
543
|
+
rain: 0,
|
|
544
|
+
windX: 0,
|
|
545
|
+
windZ: 0,
|
|
546
|
+
windSpeed: 0,
|
|
547
|
+
gust: 0,
|
|
548
|
+
fogFar: 220,
|
|
549
|
+
visibility: 220,
|
|
550
|
+
nightFactor: 0,
|
|
551
|
+
temperatureC: 20,
|
|
552
|
+
wetness: 0
|
|
553
|
+
};
|
|
554
|
+
function createEnvironmentBus(initial = {}) {
|
|
555
|
+
let state = { ...DEFAULT_ENVIRONMENT, ...initial };
|
|
556
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
557
|
+
return {
|
|
558
|
+
get: () => state,
|
|
559
|
+
set(patch) {
|
|
560
|
+
const changed = Object.keys(patch).filter((key) => patch[key] !== void 0 && patch[key] !== state[key]);
|
|
561
|
+
if (changed.length === 0) return;
|
|
562
|
+
const next = { ...state };
|
|
563
|
+
for (const key of changed) next[key] = patch[key];
|
|
564
|
+
state = next;
|
|
565
|
+
for (const listener of [...listeners]) listener(state, changed);
|
|
566
|
+
},
|
|
567
|
+
on(listener) {
|
|
568
|
+
listeners.add(listener);
|
|
569
|
+
return () => void listeners.delete(listener);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
534
573
|
export {
|
|
574
|
+
DEFAULT_ENVIRONMENT,
|
|
535
575
|
IMPACT_CONTRACT,
|
|
536
576
|
MISSING_BITS,
|
|
537
577
|
STAGE_EVENT,
|
|
@@ -543,6 +583,7 @@ export {
|
|
|
543
583
|
checkHit,
|
|
544
584
|
checkSpec,
|
|
545
585
|
checkToolClips,
|
|
586
|
+
createEnvironmentBus,
|
|
546
587
|
createToolClipPlayer,
|
|
547
588
|
deepMerge,
|
|
548
589
|
mergeById,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/impact.ts","../src/body.ts","../src/spec.ts","../src/weapons.ts","../src/animation.ts","../src/tool-animation.ts","../src/registry.ts","../src/bench.ts"],"sourcesContent":["import type { DamageType, DestructibleSpec, DestructionEvent, Hit, JointSpec, ObjectState, ToolKind, Vec3 } from './destruction'\n\n/**\n * What a game module implements for an object whose behaviour it owns (a vehicle with its own panel deformation, a creature, a machine). The\n * world (`DestructionWorld.register`) hosts it next to its own objects: raycast, shoot, explode, hit, disassemble, extinguish, update, state,\n * reset and `on` all route to it.\n *\n * Space: the impl receives hits in WORLD space and returns events in WORLD space (it owns its transform; the transform given to `register` or\n * `setTransform` is only used by the world for raycasts and blast range). Every event must carry `object === id` and name parts listed by\n * `describe()`.\n */\nexport interface DestructibleImpl {\n readonly id: string\n readonly name?: string\n /** Parts, joints and structure in the impl's local space, for raycast, inspector and log. Called often: keep it cheap. */\n describe(): DestructibleSpec\n /** Apply one hit (see `IMPACT_CONTRACT` for the fields per type) and return the events it caused. */\n hit(hit: Hit): DestructionEvent[]\n /** Advance fire, melting, collapse and other timed behaviour. */\n update?(dt: number): DestructionEvent[]\n state(): ObjectState\n reset?(): void\n /** Same contract as `DestructionWorld.disassemble`: progress 0..1, or -1 when the tool cannot undo the joint. */\n disassemble?(jointId: string, tool: ToolKind, dt: number): number\n extinguish?(partId?: string): void\n /** Optional: an animation moved a part; `position` / `rotation` (Euler XYZ, radians) are object-local. Keep raycast and hit shapes in sync. */\n setPartPose?(partId: string, position: Vec3, rotation?: Vec3): void\n /** Plain JSON of the impl's own state, stored in `DestructionSnapshot.impls`. */\n serialize?(): unknown\n load?(data: unknown): void\n}\n\n/** The fields a weapon or hazard must put in a `Hit`, per damage type. THE impact contract. */\nexport interface ImpactContract {\n required: string[]\n optional: string[]\n note: string\n}\n\nexport const IMPACT_CONTRACT: Record<DamageType, ImpactContract> = {\n ballistic: { required: ['type', 'point', 'direction', 'energy', 'caliber'], optional: ['space', 'source'], note: 'bullets and pellets: joules and diameter in millimetres' },\n blunt: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'contact radius in metres (default 3 cm)' },\n slash: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'axes, knives, machetes' },\n explosive: { required: ['type', 'point', 'energy', 'radius'], optional: ['space', 'source'], note: 'point is the centre of the blast, radius the range in metres' },\n fire: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'flame temperature in °C; duration in seconds (call every frame with `dt`)' },\n heat: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'heat without flame (cutting torch, hot plate)' },\n}\n\n/** Problems with a hit according to the impact contract (empty when it is valid). */\nexport const checkHit = (hit: Hit): string[] => {\n const c = IMPACT_CONTRACT[hit.type]\n if (!c) return [`unknown damage type \"${String(hit.type)}\"`]\n const out: string[] = []\n for (const f of c.required) {\n const v = (hit as unknown as Record<string, unknown>)[f]\n if (v === undefined || (Array.isArray(v) ? v.some((n) => !Number.isFinite(n)) : typeof v === 'number' && !Number.isFinite(v))) out.push(`${hit.type} hit needs \"${f}\"`)\n }\n return out\n}\n\nexport interface CheckOptions {\n /**\n * Ids of every material the impl may use (the contracts package does not know the materials of your world: pass the ids of\n * `world.materials`, or the built-in plus your own). When omitted, the material check is skipped.\n */\n materials?: Iterable<string>\n}\n\nconst finiteJson = (v: unknown, path: string, out: string[]) => {\n if (typeof v === 'number') {\n if (!Number.isFinite(v)) out.push(`${path} is not a finite number`)\n } else if (Array.isArray(v)) v.forEach((x, i) => finiteJson(x, `${path}[${i}]`, out))\n else if (v && typeof v === 'object') for (const [k, x] of Object.entries(v)) finiteJson(x, `${path}.${k}`, out)\n else if (typeof v === 'function' || typeof v === 'symbol' || typeof v === 'bigint') out.push(`${path} is not JSON`)\n}\n\n/**\n * Contract checker for the test room (it needs no world): `describe()` is valid, a 500 J ballistic hit at a part centre returns well-formed events of this impl, `state()`\n * matches `describe()`, hit points never go negative, `update(0.016)` returns an array and every event is JSON-serializable. It hits the impl\n * (and calls `reset()` afterwards when there is one).\n */\nexport const checkDestructible = (impl: DestructibleImpl, options: CheckOptions = {}): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const known = options.materials ? new Set(options.materials) : null\n let spec: DestructibleSpec | undefined\n try {\n spec = impl.describe()\n } catch (e) {\n return { ok: false, problems: [`describe() threw: ${String(e)}`] }\n }\n if (!spec || !Array.isArray(spec.parts) || spec.parts.length === 0) return { ok: false, problems: ['describe() must return at least one part'] }\n const ids = new Set<string>()\n for (const p of spec.parts) {\n if (ids.has(p.id)) problems.push(`duplicate part id \"${p.id}\"`)\n ids.add(p.id)\n if (known && !known.has(p.material)) problems.push(`part \"${p.id}\" has unknown material \"${p.material}\"`)\n if (!p.shape || !['box', 'cylinder', 'sphere'].includes(p.shape.kind)) problems.push(`part \"${p.id}\" has no valid shape`)\n if (!Array.isArray(p.position) || p.position.length !== 3) problems.push(`part \"${p.id}\" has no position`)\n }\n const jointIds = new Set<string>()\n for (const j of (spec.joints ?? []) as JointSpec[]) {\n if (!ids.has(j.a) || !ids.has(j.b)) problems.push(`joint \"${j.id ?? `${j.a}~${j.b}`}\" references an unknown part`)\n const jid = j.id ?? `${j.a}~${j.b}`\n if (jointIds.has(jid)) problems.push(`duplicate joint id \"${jid}\"`)\n jointIds.add(jid)\n }\n const checkState = (label: string) => {\n let st: ObjectState\n try {\n st = impl.state()\n } catch (e) {\n problems.push(`state() threw ${label}: ${String(e)}`)\n return\n }\n const sids = st.parts.map((p) => p.id).sort().join(',')\n if (sids !== [...ids].sort().join(',')) problems.push(`state() part ids do not match describe() ${label}`)\n for (const p of st.parts) if (!(p.hp >= 0)) problems.push(`part \"${p.id}\" has negative or invalid hp ${label}`)\n }\n const checkEvents = (events: unknown, label: string) => {\n if (!Array.isArray(events)) {\n problems.push(`${label} must return an array`)\n return\n }\n const evs = events as DestructionEvent[]\n evs.forEach((e, i) => {\n if (e.object !== impl.id) problems.push(`${label} event ${i} (${e.type}) has object \"${String(e.object)}\", expected \"${impl.id}\"`)\n const part = (e as { part?: string }).part\n if (part !== undefined && !ids.has(part)) problems.push(`${label} event ${i} (${e.type}) names unknown part \"${part}\"`)\n })\n const bad: string[] = []\n finiteJson(evs, 'events', bad)\n problems.push(...bad.map((b) => `${label}: ${b}`))\n try {\n const s = JSON.stringify(evs)\n if (JSON.stringify(JSON.parse(s)) !== s) problems.push(`${label} events do not survive a JSON round trip`)\n } catch (e) {\n problems.push(`${label} events are not JSON-serializable: ${String(e)}`)\n }\n }\n checkState('before the hit')\n const target = spec.parts[0]\n try {\n const hit: Hit = { type: 'ballistic', point: [target.position[0], target.position[1], target.position[2]], direction: [0, 0, -1], energy: 500, caliber: 9 }\n checkEvents(impl.hit(hit), 'hit()')\n } catch (e) {\n problems.push(`hit() threw: ${String(e)}`)\n }\n checkState('after the hit')\n if (impl.update) {\n try {\n checkEvents(impl.update(0.016), 'update()')\n } catch (e) {\n problems.push(`update() threw: ${String(e)}`)\n }\n checkState('after update')\n }\n try {\n impl.reset?.()\n } catch (e) {\n problems.push(`reset() threw: ${String(e)}`)\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The slots of a body an item can cover (the item slot vocabulary: game-items imports it from here). */\nexport type BodySlot = 'head' | 'eyes' | 'torso' | 'arms' | 'hands' | 'legs' | 'feet' | 'over'\n\n/** The 11 segments of the detailed body model (the Arsenal ids). */\nexport type SegmentKind = 'head' | 'jaw' | 'neck' | 'torso' | 'pelvis' | 'upperArm' | 'forearm' | 'hand' | 'thigh' | 'shin' | 'foot'\n\n/** The 6 regions of the crowd model (the game regions 0..5). */\nexport type BodyRegion = 'head' | 'torso' | 'armL' | 'armR' | 'legL' | 'legR'\n\n/** Bits of the `missing` mask of a body. The instanced body shader reads them, so the values are part of the contract. */\nexport const MISSING_BITS = {\n head: 1,\n armL: 2,\n armR: 4,\n legL: 8,\n legR: 16,\n armLBroken: 32,\n armRBroken: 64,\n legLBroken: 128,\n legRBroken: 256,\n jaw: 512,\n} as const\n","import type { DestructibleSpec, PartSpec } from './destruction'\n\nexport interface CheckSpecOptions {\n /** Known material ids. When given, every part's material must be one of them. */\n materials?: Iterable<string>\n}\n\nexport interface SpecReport {\n /** False when there is at least one problem (warnings do not count). */\n ok: boolean\n problems: string[]\n /** Things that are legal but probably wrong, e.g. structural parts that cannot reach an anchored part. */\n warnings: string[]\n}\n\nconst STRUCTURE_ROLES = ['foundation', 'column', 'beam', 'slab', 'wall', 'partition', 'roof', 'stair', 'none']\nconst BEARS = [true, false, 'a_on_b', 'b_on_a']\n\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\nconst positive = (n: unknown): boolean => finite(n) && n > 0\nconst vec3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every(finite)\nconst nonNegative = (n: unknown): boolean => n === undefined || (finite(n) && n >= 0)\n\nconst FACES = ['both', '+', '-']\nconst BONDS = ['running', 'stack']\n\n/** Layers, reinforcement, units and breach: known materials, finite positive sizes. */\nconst checkStagedFields = (p: PartSpec | undefined, label: string, materials: Set<string> | null, problems: string[]): void => {\n const known = (m: unknown, what: string): void => {\n if (materials && !materials.has(m as string)) problems.push(`${label} ${what} has unknown material \"${String(m)}\"`)\n }\n ;(p?.layers ?? []).forEach((l, i) => {\n known(l?.material, `layer ${i}`)\n if (!positive(l?.thickness)) problems.push(`${label} layer ${i} thickness must be finite and positive`)\n if (!FACES.includes(l?.faces)) problems.push(`${label} layer ${i} has an invalid faces \"${String(l?.faces)}\"`)\n })\n const r = p?.reinforcement\n if (r) {\n known(r.material, 'reinforcement')\n if (!positive(r.spacing) || !positive(r.diameter)) problems.push(`${label} reinforcement spacing and diameter must be finite and positive`)\n if (r.residualCapacity !== undefined && !(finite(r.residualCapacity) && r.residualCapacity >= 0 && r.residualCapacity <= 1)) problems.push(`${label} reinforcement residualCapacity must be between 0 and 1`)\n }\n const u = p?.units\n if (u) {\n known(u.mortar, 'units mortar')\n if (!vec3(u.size) || !u.size.every(positive)) problems.push(`${label} units size must be three finite positive numbers`)\n if (!BONDS.includes(u.bond)) problems.push(`${label} units has an invalid bond \"${String(u.bond)}\"`)\n }\n if (p?.breach && p.breach.area !== undefined && !positive(p.breach.area)) problems.push(`${label} breach area must be finite and positive`)\n}\n\n/**\n * Static checks on a `DestructibleSpec` (no world needed), for items that give a `spec` instead of an `impl`: unique part ids, known materials\n * (when `options.materials` is given), joints that reference existing, different parts, finite positive sizes, non-negative mass / hp / capacity,\n * valid structure roles and `bears` directions. A structural part with no path of joints to an anchored part is a warning, not a problem: it\n * is allowed, it just falls.\n */\nexport const checkSpec = (spec: DestructibleSpec, options: CheckSpecOptions = {}): SpecReport => {\n const problems: string[] = []\n const warnings: string[] = []\n const materials = options.materials ? new Set(options.materials) : null\n const parts = Array.isArray(spec?.parts) ? spec.parts : []\n if (!Array.isArray(spec?.parts) || parts.length === 0) problems.push('the spec needs at least one part')\n const ids = new Set<string>()\n for (const p of parts) {\n const id = String(p?.id ?? '')\n const label = `part \"${id}\"`\n if (id === '') problems.push('a part has no id')\n else if (ids.has(id)) problems.push(`duplicate part id \"${id}\"`)\n ids.add(id)\n if (materials && !materials.has(p?.material)) problems.push(`${label} has unknown material \"${String(p?.material)}\"`)\n else if (typeof p?.material !== 'string' || p.material === '') problems.push(`${label} needs a material`)\n const s = p?.shape\n if (s?.kind === 'box') {\n if (!Array.isArray(s.size) || s.size.length !== 3 || !s.size.every(positive)) problems.push(`${label} box size must be three finite positive numbers`)\n } else if (s?.kind === 'cylinder') {\n if (!positive(s.radius) || !positive(s.height)) problems.push(`${label} cylinder radius and height must be finite and positive`)\n } else if (s?.kind === 'sphere') {\n if (!positive(s.radius)) problems.push(`${label} sphere radius must be finite and positive`)\n } else problems.push(`${label} has an unknown shape`)\n if (!vec3(p?.position)) problems.push(`${label} needs a finite position [x, y, z]`)\n if (p?.rotation !== undefined && !vec3(p.rotation)) problems.push(`${label} has an invalid rotation`)\n for (const f of ['mass', 'hp', 'thickness'] as const) if (!nonNegative(p?.[f])) problems.push(`${label} ${f} must be a finite number >= 0`)\n if (p?.fill !== undefined && !(finite(p.fill) && p.fill >= 0 && p.fill <= 1)) problems.push(`${label} fill must be between 0 and 1`)\n if (p?.structure) {\n if (!STRUCTURE_ROLES.includes(p.structure.role)) problems.push(`${label} has an invalid structure role \"${String(p.structure.role)}\"`)\n if (!nonNegative(p.structure.capacity)) problems.push(`${label} structure capacity must be a finite number >= 0`)\n }\n if (p?.contains && !nonNegative(p.contains.liters)) problems.push(`${label} contains.liters must be a finite number >= 0`)\n checkStagedFields(p, label, materials, problems)\n }\n\n const jointIds = new Set<string>()\n const edges: Array<[string, string]> = []\n for (const j of spec?.joints ?? []) {\n const id = j.id ?? `${j.a}~${j.b}`\n const label = `joint \"${id}\"`\n if (jointIds.has(id)) problems.push(`duplicate joint id \"${id}\"`)\n jointIds.add(id)\n if (!ids.has(j.a)) problems.push(`${label} references unknown part \"${String(j.a)}\"`)\n if (!ids.has(j.b)) problems.push(`${label} references unknown part \"${String(j.b)}\"`)\n if (j.a === j.b) problems.push(`${label} joins part \"${String(j.a)}\" to itself`)\n if (j.bears !== undefined && !BEARS.includes(j.bears)) problems.push(`${label} has an invalid bears \"${String(j.bears)}\"`)\n if (!nonNegative(j.strength)) problems.push(`${label} strength must be a finite number >= 0`)\n if (!nonNegative(j.seconds)) problems.push(`${label} seconds must be a finite number >= 0`)\n if (j.bears !== false && ids.has(j.a) && ids.has(j.b) && j.a !== j.b) edges.push([j.a, j.b])\n }\n\n // structural parts must reach an anchored part over the joints (only when the spec lists its joints: otherwise they are inferred later)\n const structural = parts.filter((p) => p?.structure && p.structure.role !== 'none')\n if (structural.length && spec.joints) {\n const anchored = parts.filter((p) => p.anchored || p.structure?.role === 'foundation').map((p) => p.id)\n const adj = new Map<string, string[]>()\n for (const [a, b] of edges) {\n adj.set(a, [...(adj.get(a) ?? []), b])\n adj.set(b, [...(adj.get(b) ?? []), a])\n }\n const seen = new Set(anchored)\n const queue = [...anchored]\n while (queue.length) {\n for (const n of adj.get(queue.pop()!) ?? []) {\n if (seen.has(n)) continue\n seen.add(n)\n queue.push(n)\n }\n }\n const lost = structural.filter((p) => !seen.has(p.id)).map((p) => p.id)\n if (lost.length) warnings.push(`structural part(s) ${lost.map((i) => `\"${i}\"`).join(', ')} cannot reach an anchored part, so they will fall`)\n }\n return { ok: problems.length === 0, problems, warnings }\n}\n","import type { Hit, Vec3 } from './destruction'\n\n/**\n * A ballistic shot (one per pellet) as a weapon module produces it. Plain data: any damage model can consume it (`shotToHit` makes the `Hit`).\n */\nexport interface Shot {\n origin: Vec3\n /** Unit vector. */\n direction: Vec3\n /** Muzzle energy in joules. */\n energy: number\n /** Bullet diameter in millimetres. */\n caliber: number\n /** snake_case calibre id, e.g. `9x19`. */\n caliberId: string\n /** Muzzle speed in m/s. */\n speed: number\n /** Projectile mass in grams (one pellet for a shotgun load). */\n mass?: number\n /** Energy at a distance in metres, with the calibre's drag. */\n energyAt(distance: number): number\n pellet: number\n /** snake_case weapon id. */\n weapon: string\n source?: string\n}\n\n/** One use of a non-firearm (melee swing, flame tick, heat tick, throw, tool tick, extinguisher tick). */\nexport type Use =\n | { kind: 'melee'; damage: 'blunt' | 'slash'; energy: number; radius: number; reach: number; origin: Vec3; direction: Vec3 }\n | { kind: 'flame'; temperature: number; radius: number; range: number; cone: boolean; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'heat'; temperature: number; radius: number; range: number; duration: number; origin: Vec3; direction: Vec3 }\n | {\n kind: 'throw'\n energy: number\n radius: number\n fuse: number\n velocity: Vec3\n origin: Vec3\n fire?: { temperature: number; radius: number; seconds: number }\n /** Explosive id (`m67`, `molotov`, `pg7v`...): the host looks the numbers up in the arsenal. Absent on a plain thrown object. */\n explosive?: string\n /** What sets it off: `timer` after `fuse` seconds, `impact` on the first hit once armed. */\n trigger?: 'timer' | 'impact'\n /** Metres the projectile must fly before an impact sets it off; closer, it hits without exploding (rocket launchers). */\n armDistance?: number\n /** Radius in metres of the wounding area (`radius` is the lethal one). */\n injuryRadius?: number\n /** Seconds the blast stuns whoever is inside the injury radius (flashbang). */\n stunSeconds?: number\n /** Seconds the thrower held it after pulling the pin: `fuse` is what was left of the full fuse when it left the hand. */\n cooked?: number\n /** Went off in the thrower's hand (held past the fuse): no velocity, `fuse` is 0 and the blast is at `origin`. */\n inHand?: boolean\n }\n /** An explosive set down where the trigger is pulled (mine, C4, charge): it waits for its trigger instead of flying. */\n | {\n kind: 'place'\n explosive: string\n /** `wire`: something trips it; `remote`: the player fires it; `timer`: after `fuse` seconds. */\n triggers: readonly ('timer' | 'wire' | 'remote')[]\n energy: number\n /** Lethal radius in metres. */\n radius: number\n injuryRadius: number\n /** Cone the blast goes through, in front of `direction` (mine); absent for a blast in every direction. */\n cone?: { halfAngleDeg: number; length: number }\n /** The wire a `wire` charge stretches from where it is set down: metres out, degrees from its facing (counter-clockwise from above) and metres above it. */\n tripwire?: { length: number; angleDeg: number; height: number }\n fuse?: number\n origin: Vec3\n direction: Vec3\n }\n /** The blast behind a rocket launcher: dangerous inside `radius` metres, in the cone around `direction` (pointing away from the target). */\n | { kind: 'backblast'; radius: number; halfAngleDeg: number; origin: Vec3; direction: Vec3 }\n | { kind: 'tool'; tool: string; reach: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'extinguish'; range: number; coolC: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'hand'; reach: number; origin: Vec3; direction: Vec3 }\n\n/**\n * The `Hit` a ballistic shot makes at `point` (world space): the energy is what is left of the shot at that distance from its origin\n * (`shot.energyAt`), the direction and calibre come from the shot. `normal` (the surface normal at the point, when the caller knows it) does not\n * change the hit; it is accepted so that callers can pass what their raycast returned and so that a stricter implementation could use it.\n */\nexport const shotToHit = (shot: Shot, point: Vec3, normal?: Vec3): Hit => {\n void normal\n const dx = point[0] - shot.origin[0]\n const dy = point[1] - shot.origin[1]\n const dz = point[2] - shot.origin[2]\n const distance = Math.hypot(dx, dy, dz)\n return {\n type: 'ballistic',\n point: [point[0], point[1], point[2]],\n direction: [shot.direction[0], shot.direction[1], shot.direction[2]],\n energy: shot.energyAt(distance),\n caliber: shot.caliber,\n ...(shot.mass === undefined ? {} : { mass: shot.mass }),\n source: shot.source ?? shot.weapon,\n }\n}\n","import type { Vec3 } from './destruction'\n\n/** Pose of one part (or of the whole item) at a key: position offset in metres and Euler XYZ rotation in radians, both relative to the rest pose. */\nexport interface AnimationPose {\n position?: Vec3\n rotation?: Vec3\n}\n\nexport interface AnimationKey {\n /** Seconds from the start of the clip. */\n t: number\n /** Pose of the item's root. */\n position?: Vec3\n rotation?: Vec3\n /** Poses of named parts: the key is a destruction part id (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */\n parts?: Record<string, AnimationPose>\n}\n\nexport interface AnimationEventSpec {\n /** Seconds from the start of the clip. */\n t: number\n /** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */\n type: string\n data?: Record<string, unknown>\n}\n\n/** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */\nexport interface AnimationClipSpec {\n /** snake_case: `reload`, `open`, `drink`... Unique within the item. */\n id: string\n /** Seconds. */\n duration: number\n keys: AnimationKey[]\n events?: AnimationEventSpec[]\n}\n\n/** What an item exposes when it brings its own animations. */\nexport interface ItemAnimationContract {\n animations: AnimationClipSpec[]\n}\n\nconst finite3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === 'number' && Number.isFinite(n))\n\n/**\n * Problems with a clip (empty when it is valid): a snake_case id, a positive finite duration, at least one key, key and event times inside\n * `[0, duration]` and not going backwards, finite vectors, every named part one of `partIds`, event types that are non-empty strings and\n * JSON-serializable event data.\n */\nexport const checkAnimation = (clip: AnimationClipSpec, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const parts = new Set(partIds)\n const id = String(clip?.id ?? '')\n if (!/^[a-z][a-z0-9_]*$/.test(id)) problems.push(`clip id \"${id}\" must be snake_case`)\n const label = `clip \"${id}\"`\n if (!(typeof clip?.duration === 'number' && Number.isFinite(clip.duration) && clip.duration > 0)) problems.push(`${label} needs a positive finite duration`)\n const dur = typeof clip?.duration === 'number' && clip.duration > 0 ? clip.duration : Infinity\n if (!Array.isArray(clip?.keys) || clip.keys.length === 0) {\n problems.push(`${label} needs at least one key`)\n return { ok: false, problems }\n }\n let prev = -Infinity\n clip.keys.forEach((k, i) => {\n if (!(typeof k.t === 'number' && Number.isFinite(k.t)) || k.t < 0 || k.t > dur + 1e-9) problems.push(`${label} key ${i} has t=${String(k.t)} outside 0..${dur}`)\n else if (k.t < prev) problems.push(`${label} key ${i} goes back in time (${k.t} < ${prev})`)\n else prev = k.t\n for (const f of ['position', 'rotation'] as const) if (k[f] !== undefined && !finite3(k[f])) problems.push(`${label} key ${i} has an invalid ${f}`)\n for (const [pid, pose] of Object.entries(k.parts ?? {})) {\n if (!parts.has(pid)) problems.push(`${label} key ${i} moves unknown part \"${pid}\"`)\n for (const f of ['position', 'rotation'] as const) if (pose?.[f] !== undefined && !finite3(pose[f])) problems.push(`${label} key ${i} part \"${pid}\" has an invalid ${f}`)\n }\n })\n let prevEv = -Infinity\n ;(clip.events ?? []).forEach((e, i) => {\n if (!(typeof e.t === 'number' && Number.isFinite(e.t)) || e.t < 0 || e.t > dur + 1e-9) problems.push(`${label} event ${i} has t=${String(e.t)} outside 0..${dur}`)\n else if (e.t < prevEv) problems.push(`${label} event ${i} goes back in time`)\n else prevEv = e.t\n if (typeof e.type !== 'string' || e.type === '') problems.push(`${label} event ${i} needs a type`)\n try {\n JSON.stringify(e.data ?? {})\n } catch {\n problems.push(`${label} event ${i} data is not JSON`)\n }\n })\n return { ok: problems.length === 0, problems }\n}\n","import type { AnimationClipSpec, AnimationEventSpec, AnimationPose } from './animation'\nimport { checkAnimation } from './animation'\nimport type { HostEvent } from './module'\n\n/** Clips a tool can bring, one per move of its cycle. A tool brings the ones it has; a missing clip is simply not played. */\nexport const TOOL_CLIP = {\n DRAW: 'draw',\n SHOT: 'shot',\n MISFIRE: 'misfire',\n EMPTY: 'empty',\n CYCLE: 'cycle',\n RELOAD: 'reload',\n FEED: 'feed',\n FAULT: 'fault',\n CLEAR: 'clear',\n SAFETY_ON: 'safety_on',\n SAFETY_OFF: 'safety_off',\n MODE: 'mode',\n /** A throwable with the pin out, held until it is thrown. */\n COOK: 'cook',\n /** The stock folds, unfolds, slides in or out (variants `fold`, `unfold`, `collapse`, `extend`). */\n STOCK: 'stock',\n /** A light or a laser is switched (variants `on`, `off`). */\n TOGGLE: 'toggle',\n} as const\nexport type ToolClipId = (typeof TOOL_CLIP)[keyof typeof TOOL_CLIP]\n\n/** What a renderer does when a rule event arrives. `variant` picks `<clip>_<variant>` when the tool has it, else `<clip>`. */\nexport type ToolAnimationStep =\n | { readonly type: 'play'; readonly clip: ToolClipId; readonly variant?: string; readonly duration?: number; readonly repeat?: number }\n /** Move the clip that is playing to the marker (a clip event `stage` with `data.stage`) the rule just reached. */\n | { readonly type: 'sync'; readonly stage: string }\n /** The rule says the phase is over: the clip jumps to its end if it is still running. */\n | { readonly type: 'finish'; readonly clip: ToolClipId }\n\nconst stockVariant = (event: Extract<HostEvent, { type: 'stock' }>): string => {\n const isFolding = event.kind === undefined ? false : event.kind.includes('fold')\n if (event.state === 'collapsed') return isFolding ? 'fold' : 'collapse'\n return isFolding ? 'unfold' : 'extend'\n}\n\n/** The move a host event asks of the tool in hand, or null when it has no motion. */\nexport const toolAnimationStep = (event: HostEvent): ToolAnimationStep | null => {\n switch (event.type) {\n case 'tool':\n return { type: 'play', clip: TOOL_CLIP.DRAW, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'shot':\n return { type: 'play', clip: TOOL_CLIP.SHOT, repeat: Math.max(1, event.rounds ?? 1) }\n case 'misfire':\n return { type: 'play', clip: TOOL_CLIP.MISFIRE }\n case 'empty':\n return { type: 'play', clip: TOOL_CLIP.EMPTY }\n case 'mode':\n return { type: 'play', clip: TOOL_CLIP.MODE, variant: event.mode }\n case 'fault':\n return { type: 'play', clip: TOOL_CLIP.FAULT, variant: event.fault }\n case 'clear':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CLEAR }\n return { type: 'play', clip: TOOL_CLIP.CLEAR, variant: event.fault, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'safety':\n return { type: 'play', clip: event.on ? TOOL_CLIP.SAFETY_ON : TOOL_CLIP.SAFETY_OFF }\n case 'cycle':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CYCLE }\n return { type: 'play', clip: TOOL_CLIP.CYCLE, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'reload':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.RELOAD }\n return { type: 'play', clip: TOOL_CLIP.RELOAD, ...(event.kind === undefined ? {} : { variant: event.kind }), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'stage':\n return { type: 'sync', stage: event.stage }\n case 'feed':\n return { type: 'play', clip: TOOL_CLIP.FEED }\n case 'cook':\n if (event.phase === 'start') return { type: 'play', clip: TOOL_CLIP.COOK, duration: event.fuse }\n return { type: 'finish', clip: TOOL_CLIP.COOK }\n case 'stock':\n return { type: 'play', clip: TOOL_CLIP.STOCK, variant: stockVariant(event), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'toggle':\n return { type: 'play', clip: TOOL_CLIP.TOGGLE, variant: event.on ? 'on' : 'off' }\n default:\n return null\n }\n}\n\n/** Where a renderer puts the poses: every part of the tool by id (the ids the clips move), and the whole tool. Poses are offsets from rest. */\nexport interface ToolRig {\n setPartPose(partId: string, pose: AnimationPose): void\n setRootPose?(pose: AnimationPose): void\n}\n\n/** Clips by id, with the variants as `<clip>_<variant>`. */\nexport type ToolClipSet = Readonly<Record<string, AnimationClipSpec>>\n\n/** A renderer's tool animator: feed it the host events and the frame time. */\nexport interface ToolAnimator {\n handle(event: HostEvent): void\n update(dt: number): void\n /** Ids of the clips playing now. */\n readonly playing: readonly string[]\n dispose(): void\n}\n\nexport const STAGE_EVENT = 'stage'\n\n/** The clip with its times (keys and events) scaled so that it lasts `duration` seconds. */\nexport const scaleClip = (clip: AnimationClipSpec, duration: number): AnimationClipSpec => {\n const factor = clip.duration > 0 ? duration / clip.duration : 1\n return {\n ...clip,\n duration,\n keys: clip.keys.map((key) => ({ ...key, t: key.t * factor })),\n ...(clip.events ? { events: clip.events.map((event) => ({ ...event, t: event.t * factor })) } : {}),\n }\n}\n\nconst ZERO: [number, number, number] = [0, 0, 0]\nconst mix = (from: readonly number[], to: readonly number[], fraction: number): [number, number, number] => [\n (from[0] ?? 0) + ((to[0] ?? 0) - (from[0] ?? 0)) * fraction,\n (from[1] ?? 0) + ((to[1] ?? 0) - (from[1] ?? 0)) * fraction,\n (from[2] ?? 0) + ((to[2] ?? 0) - (from[2] ?? 0)) * fraction,\n]\n\n/** Pose of the whole tool (`partId` undefined) or of one part at `time`, linear between keys; a key that does not name it means rest. */\nexport const sampleClip = (clip: AnimationClipSpec, partId: string | undefined, time: number): { position: [number, number, number]; rotation: [number, number, number] } => {\n const points = clip.keys.map((key) => {\n const pose = partId === undefined ? key : key.parts?.[partId]\n return { t: key.t, position: pose?.position ?? ZERO, rotation: pose?.rotation ?? ZERO }\n })\n const first = points[0]\n if (!first || first.t > 0) points.unshift({ t: 0, position: ZERO, rotation: ZERO })\n const last = points[points.length - 1]\n if (!last) return { position: ZERO, rotation: ZERO }\n if (time >= last.t) return { position: mix(last.position, last.position, 0), rotation: mix(last.rotation, last.rotation, 0) }\n const index = Math.max(0, points.findIndex((point, i) => time >= point.t && time < (points[i + 1]?.t ?? Infinity)))\n const from = points[index]\n const to = points[index + 1]\n if (!from || !to) return { position: ZERO, rotation: ZERO }\n const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0\n return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) }\n}\n\n/** Problems with a clip set (empty when valid): each key is the clip id (or `<clip>_<variant>`), every clip passes `checkAnimation`, and a `stage` event names its stage. */\nexport const checkToolClips = (clips: ToolClipSet, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const parts = [...partIds]\n const problems: string[] = []\n for (const [key, clip] of Object.entries(clips)) {\n if (clip.id !== key) problems.push(`clip \"${key}\" has the id \"${clip.id}\"`)\n if (!(clip.duration > 0)) problems.push(`clip \"${key}\" has no duration`)\n problems.push(...checkAnimation(clip, parts).problems)\n for (const event of clip.events ?? []) if (event.type === STAGE_EVENT && typeof event.data?.stage !== 'string') problems.push(`clip \"${key}\" has a stage event without data.stage`)\n }\n return { ok: problems.length === 0, problems }\n}\n\ninterface Instance {\n readonly name: string\n readonly clip: AnimationClipSpec\n time: number\n delay: number\n fired: number\n}\n\nexport interface ToolClipPlayerOptions {\n clips: ToolClipSet\n rig: ToolRig\n /** Called for each clip event as its time passes (`eject_casing`, `drop_magazine`, `stage`...). */\n onEvent?(event: AnimationEventSpec, clip: string): void\n /** Seconds between the rounds of a `repeat` (a burst): default 0.1. */\n repeatInterval?(): number\n}\n\n/** The default player of a tool's clips: runs the steps of `toolAnimationStep` against a rig, scaled to the duration the rule gives. */\nexport interface ToolClipPlayer {\n apply(step: ToolAnimationStep): void\n update(dt: number): void\n readonly playing: readonly string[]\n}\n\nexport const createToolClipPlayer = (options: ToolClipPlayerOptions): ToolClipPlayer => {\n const { clips, rig, onEvent } = options\n let instances: Instance[] = []\n\n const find = (clip: string, variant?: string): { name: string; clip: AnimationClipSpec } | undefined => {\n const name = variant !== undefined && clips[`${clip}_${variant}`] ? `${clip}_${variant}` : clip\n const found = clips[name]\n return found ? { name, clip: found } : undefined\n }\n\n const render = (instance: Instance): void => {\n const time = Math.min(instance.time, instance.clip.duration)\n const root = sampleClip(instance.clip, undefined, time)\n if (instance.clip.keys.some((key) => key.position || key.rotation)) rig.setRootPose?.(root)\n const touched = new Set(instance.clip.keys.flatMap((key) => Object.keys(key.parts ?? {})))\n for (const id of touched) rig.setPartPose(id, sampleClip(instance.clip, id, time))\n }\n\n const fireEvents = (instance: Instance, upTo: number): void => {\n const events = instance.clip.events ?? []\n while (instance.fired < events.length && (events[instance.fired]?.t ?? Infinity) <= upTo + 1e-9) {\n const event = events[instance.fired]\n instance.fired += 1\n if (event) onEvent?.(event, instance.name)\n }\n }\n\n return {\n apply(step) {\n if (step.type === 'sync') {\n for (const instance of instances) {\n const marker = (instance.clip.events ?? []).find((event) => event.type === STAGE_EVENT && event.data?.stage === step.stage)\n if (marker && instance.time < marker.t) instance.time = marker.t\n }\n return\n }\n if (step.type === 'finish') {\n for (const instance of instances) {\n if (instance.name !== step.clip && !instance.name.startsWith(`${step.clip}_`)) continue\n fireEvents(instance, instance.clip.duration)\n instance.time = instance.clip.duration\n }\n return\n }\n const found = find(step.clip, step.variant)\n if (!found) return\n const clip = step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip\n const interval = options.repeatInterval?.() ?? 0.1\n instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0)\n for (let round = 0; round < (step.repeat ?? 1); round++) {\n instances.push({ name: found.name, clip, time: 0, delay: round * interval, fired: 0 })\n }\n },\n update(dt) {\n for (const instance of instances) {\n const carried = instance.delay > 0 ? dt - instance.delay : dt\n if (instance.delay > 0) {\n instance.delay = Math.max(0, instance.delay - dt)\n if (instance.delay > 0) continue\n }\n instance.time += carried\n fireEvents(instance, instance.time)\n render(instance)\n }\n instances = instances.filter((instance) => instance.delay > 0 || instance.time < instance.clip.duration)\n },\n get playing() {\n return instances.map((instance) => instance.name)\n },\n }\n}\n","type PlainObject = Record<string, unknown>\ntype Entry = { readonly id: string }\n\n/** Keys that would reach the prototype of the result when a patch comes from parsed JSON. */\nconst UNSAFE_KEYS: ReadonlySet<string> = new Set(['__proto__', 'constructor', 'prototype'])\n\nconst isPlainObject = (value: unknown): value is PlainObject =>\n typeof value === 'object' && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype\n\n/** Recursively optional; arrays and functions are replaced as a whole, objects are merged. */\nexport type DeepPartial<TValue> = TValue extends (...args: never[]) => unknown\n ? TValue\n : TValue extends readonly unknown[]\n ? TValue\n : TValue extends object\n ? { [TKey in keyof TValue]?: DeepPartial<TValue[TKey]> }\n : TValue\n\n/** Merges `patch` over `base`: plain objects merge field by field, arrays, functions and primitives replace. `undefined` in the patch is ignored. */\nexport const deepMerge = <TBase extends object>(base: TBase, patch: object | undefined): TBase => {\n if (!patch) return base\n const result: PlainObject = { ...(base as PlainObject) }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined || UNSAFE_KEYS.has(key)) continue\n const current = result[key]\n result[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value\n }\n return result as TBase\n}\n\n/** The shipped entries with the ones from `extra` added or, when the id exists, replaced. */\nexport const mergeById = <TEntry extends Entry>(shipped: readonly TEntry[], extra: readonly TEntry[] | undefined): Map<string, TEntry> => {\n const entries = new Map<string, TEntry>()\n for (const entry of [...shipped, ...(extra ?? [])]) entries.set(entry.id, entry)\n return entries\n}\n\n/** Applies `patch` by id; a patch for an id that does not exist is ignored. */\nexport const applyPatches = <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, DeepPartial<TEntry> | Partial<TEntry>>> | undefined): void => {\n for (const [id, patch] of Object.entries(patches ?? {})) {\n const entry = entries.get(id)\n if (entry) entries.set(id, deepMerge(entry, patch))\n }\n}\n\nexport const removeIds = (entries: Map<string, unknown>, ids: readonly string[] | undefined): void => {\n for (const id of ids ?? []) entries.delete(id)\n}\n","import type { Vec3 } from './destruction'\n\n/**\n * 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\n * every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.\n */\nexport type ControlValue = number | boolean | string\n\nexport interface ChoiceOption {\n readonly id: string\n readonly name: string\n}\n\ninterface ControlBase {\n /** snake_case, unique across the module's controls, layers and metrics. */\n readonly id: string\n /** Display name, already translated for the locale the host asked for. */\n readonly name: string\n readonly hint?: string\n readonly hidden?: boolean\n}\n\nexport interface NumberControl extends ControlBase {\n readonly kind: 'number'\n readonly min: number\n readonly max: number\n readonly step?: number\n readonly unit?: string\n readonly default: number\n}\n\nexport interface BooleanControl extends ControlBase {\n readonly kind: 'boolean'\n readonly default: boolean\n}\n\nexport interface ChoiceControl extends ControlBase {\n readonly kind: 'choice'\n readonly options: readonly ChoiceOption[]\n readonly default: string\n}\n\n/** A button: no value, only the press. */\nexport interface ActionControl extends ControlBase {\n readonly kind: 'action'\n}\n\nexport type ModuleControl = NumberControl | BooleanControl | ChoiceControl | ActionControl\n\n/** Something of the module the viewer turns on or off (smoke, flash, casings). */\nexport interface ModuleLayer {\n readonly id: string\n readonly name: string\n /** On unless `false`. */\n readonly default?: boolean\n}\n\n/** Pulled: the room calls read only when it samples (~4 Hz, or on TestRoom.metrics()). */\nexport interface ModuleMetric {\n readonly id: string\n readonly name: string\n readonly unit?: string\n /** Budget: the host shows a bar and warns past it. */\n readonly max?: number\n readonly digits?: number\n read(): number | string\n}\n\nexport type BenchChange =\n | { readonly kind: 'control'; readonly id: string; readonly value: ControlValue }\n | { readonly kind: 'layer'; readonly id: string; readonly on: boolean }\n | { readonly kind: 'action'; readonly id: string }\n\nexport interface ControlPatch {\n readonly hidden?: boolean\n readonly disabled?: boolean\n readonly options?: readonly ChoiceOption[]\n readonly value?: ControlValue\n}\n\n/** What the host lends a module that declares a bench: the current values and the changes. */\nexport interface ModuleBench {\n value(id: string): ControlValue | undefined\n layer(id: string): boolean\n /** Right after attach returns, the host sends one change per control (not actions) and per layer, in declaration order. */\n onChange(listener: (change: BenchChange) => void): () => void\n /** `value` sets without emitting (no echo loops); `options` falls back to the first option if the value is no longer offered. */\n patch(id: string, patch: ControlPatch): void\n /** Move the host camera (a near / middle / far choice). */\n look(eye: Vec3, target: Vec3): void\n}\n\nexport interface BenchReport {\n readonly ok: boolean\n readonly problems: readonly string[]\n}\n\nexport interface BenchDeclaration {\n readonly controls?: readonly ModuleControl[]\n readonly layers?: readonly ModuleLayer[]\n readonly metrics?: readonly ModuleMetric[]\n}\n\nconst SNAKE_CASE = /^[a-z][a-z0-9_]*$/\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\n\nfunction checkControl(control: ModuleControl, problems: string[]): void {\n const label = `control \"${control.id}\"`\n if (control.kind === 'number') {\n if (!finite(control.min) || !finite(control.max) || control.min >= control.max) problems.push(`${label} needs min < max`)\n if (control.step !== undefined && !(finite(control.step) && control.step > 0)) problems.push(`${label} needs a step > 0`)\n if (!finite(control.default) || control.default < control.min || control.default > control.max) problems.push(`${label} default ${String(control.default)} is outside ${control.min}..${control.max}`)\n return\n }\n if (control.kind !== 'choice') return\n if (control.options.length === 0) {\n problems.push(`${label} needs at least one option`)\n return\n }\n const seen = new Set<string>()\n for (const option of control.options) {\n if (seen.has(option.id)) problems.push(`${label} repeats option \"${option.id}\"`)\n seen.add(option.id)\n }\n if (!seen.has(control.default)) problems.push(`${label} default \"${control.default}\" is not among its options`)\n}\n\n/** snake_case ids unique across controls, layers and metrics; min < max; step > 0; default in range; options non-empty, unique, default among them; metric max > 0. */\nexport function checkBench(declared: BenchDeclaration): BenchReport {\n const problems: string[] = []\n const ids = new Set<string>()\n const claim = (kind: string, id: string): void => {\n if (!SNAKE_CASE.test(id)) problems.push(`${kind} id \"${id}\" must be snake_case`)\n if (ids.has(id)) problems.push(`duplicate id \"${id}\" across controls, layers and metrics`)\n ids.add(id)\n }\n for (const control of declared.controls ?? []) {\n claim('control', control.id)\n checkControl(control, problems)\n }\n for (const layer of declared.layers ?? []) claim('layer', layer.id)\n for (const metric of declared.metrics ?? []) {\n claim('metric', metric.id)\n if (metric.max !== undefined && !(finite(metric.max) && metric.max > 0)) problems.push(`metric \"${metric.id}\" needs a max > 0`)\n }\n return { ok: problems.length === 0, problems }\n}\n"],"mappings":"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","names":["finite"]}
|
|
1
|
+
{"version":3,"sources":["../src/impact.ts","../src/body.ts","../src/spec.ts","../src/weapons.ts","../src/animation.ts","../src/tool-animation.ts","../src/registry.ts","../src/bench.ts","../src/environment.ts"],"sourcesContent":["import type { DamageType, DestructibleSpec, DestructionEvent, Hit, JointSpec, ObjectState, ToolKind, Vec3 } from './destruction'\n\n/**\n * What a game module implements for an object whose behaviour it owns (a vehicle with its own panel deformation, a creature, a machine). The\n * world (`DestructionWorld.register`) hosts it next to its own objects: raycast, shoot, explode, hit, disassemble, extinguish, update, state,\n * reset and `on` all route to it.\n *\n * Space: the impl receives hits in WORLD space and returns events in WORLD space (it owns its transform; the transform given to `register` or\n * `setTransform` is only used by the world for raycasts and blast range). Every event must carry `object === id` and name parts listed by\n * `describe()`.\n */\nexport interface DestructibleImpl {\n readonly id: string\n readonly name?: string\n /** Parts, joints and structure in the impl's local space, for raycast, inspector and log. Called often: keep it cheap. */\n describe(): DestructibleSpec\n /** Apply one hit (see `IMPACT_CONTRACT` for the fields per type) and return the events it caused. */\n hit(hit: Hit): DestructionEvent[]\n /** Advance fire, melting, collapse and other timed behaviour. */\n update?(dt: number): DestructionEvent[]\n state(): ObjectState\n reset?(): void\n /** Same contract as `DestructionWorld.disassemble`: progress 0..1, or -1 when the tool cannot undo the joint. */\n disassemble?(jointId: string, tool: ToolKind, dt: number): number\n extinguish?(partId?: string): void\n /** Optional: an animation moved a part; `position` / `rotation` (Euler XYZ, radians) are object-local. Keep raycast and hit shapes in sync. */\n setPartPose?(partId: string, position: Vec3, rotation?: Vec3): void\n /** Plain JSON of the impl's own state, stored in `DestructionSnapshot.impls`. */\n serialize?(): unknown\n load?(data: unknown): void\n}\n\n/** The fields a weapon or hazard must put in a `Hit`, per damage type. THE impact contract. */\nexport interface ImpactContract {\n required: string[]\n optional: string[]\n note: string\n}\n\nexport const IMPACT_CONTRACT: Record<DamageType, ImpactContract> = {\n ballistic: { required: ['type', 'point', 'direction', 'energy', 'caliber'], optional: ['space', 'source'], note: 'bullets and pellets: joules and diameter in millimetres' },\n blunt: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'contact radius in metres (default 3 cm)' },\n slash: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'axes, knives, machetes' },\n explosive: { required: ['type', 'point', 'energy', 'radius'], optional: ['space', 'source'], note: 'point is the centre of the blast, radius the range in metres' },\n fire: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'flame temperature in °C; duration in seconds (call every frame with `dt`)' },\n heat: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'heat without flame (cutting torch, hot plate)' },\n}\n\n/** Problems with a hit according to the impact contract (empty when it is valid). */\nexport const checkHit = (hit: Hit): string[] => {\n const c = IMPACT_CONTRACT[hit.type]\n if (!c) return [`unknown damage type \"${String(hit.type)}\"`]\n const out: string[] = []\n for (const f of c.required) {\n const v = (hit as unknown as Record<string, unknown>)[f]\n if (v === undefined || (Array.isArray(v) ? v.some((n) => !Number.isFinite(n)) : typeof v === 'number' && !Number.isFinite(v))) out.push(`${hit.type} hit needs \"${f}\"`)\n }\n return out\n}\n\nexport interface CheckOptions {\n /**\n * Ids of every material the impl may use (the contracts package does not know the materials of your world: pass the ids of\n * `world.materials`, or the built-in plus your own). When omitted, the material check is skipped.\n */\n materials?: Iterable<string>\n}\n\nconst finiteJson = (v: unknown, path: string, out: string[]) => {\n if (typeof v === 'number') {\n if (!Number.isFinite(v)) out.push(`${path} is not a finite number`)\n } else if (Array.isArray(v)) v.forEach((x, i) => finiteJson(x, `${path}[${i}]`, out))\n else if (v && typeof v === 'object') for (const [k, x] of Object.entries(v)) finiteJson(x, `${path}.${k}`, out)\n else if (typeof v === 'function' || typeof v === 'symbol' || typeof v === 'bigint') out.push(`${path} is not JSON`)\n}\n\n/**\n * Contract checker for the test room (it needs no world): `describe()` is valid, a 500 J ballistic hit at a part centre returns well-formed events of this impl, `state()`\n * matches `describe()`, hit points never go negative, `update(0.016)` returns an array and every event is JSON-serializable. It hits the impl\n * (and calls `reset()` afterwards when there is one).\n */\nexport const checkDestructible = (impl: DestructibleImpl, options: CheckOptions = {}): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const known = options.materials ? new Set(options.materials) : null\n let spec: DestructibleSpec | undefined\n try {\n spec = impl.describe()\n } catch (e) {\n return { ok: false, problems: [`describe() threw: ${String(e)}`] }\n }\n if (!spec || !Array.isArray(spec.parts) || spec.parts.length === 0) return { ok: false, problems: ['describe() must return at least one part'] }\n const ids = new Set<string>()\n for (const p of spec.parts) {\n if (ids.has(p.id)) problems.push(`duplicate part id \"${p.id}\"`)\n ids.add(p.id)\n if (known && !known.has(p.material)) problems.push(`part \"${p.id}\" has unknown material \"${p.material}\"`)\n if (!p.shape || !['box', 'cylinder', 'sphere'].includes(p.shape.kind)) problems.push(`part \"${p.id}\" has no valid shape`)\n if (!Array.isArray(p.position) || p.position.length !== 3) problems.push(`part \"${p.id}\" has no position`)\n }\n const jointIds = new Set<string>()\n for (const j of (spec.joints ?? []) as JointSpec[]) {\n if (!ids.has(j.a) || !ids.has(j.b)) problems.push(`joint \"${j.id ?? `${j.a}~${j.b}`}\" references an unknown part`)\n const jid = j.id ?? `${j.a}~${j.b}`\n if (jointIds.has(jid)) problems.push(`duplicate joint id \"${jid}\"`)\n jointIds.add(jid)\n }\n const checkState = (label: string) => {\n let st: ObjectState\n try {\n st = impl.state()\n } catch (e) {\n problems.push(`state() threw ${label}: ${String(e)}`)\n return\n }\n const sids = st.parts.map((p) => p.id).sort().join(',')\n if (sids !== [...ids].sort().join(',')) problems.push(`state() part ids do not match describe() ${label}`)\n for (const p of st.parts) if (!(p.hp >= 0)) problems.push(`part \"${p.id}\" has negative or invalid hp ${label}`)\n }\n const checkEvents = (events: unknown, label: string) => {\n if (!Array.isArray(events)) {\n problems.push(`${label} must return an array`)\n return\n }\n const evs = events as DestructionEvent[]\n evs.forEach((e, i) => {\n if (e.object !== impl.id) problems.push(`${label} event ${i} (${e.type}) has object \"${String(e.object)}\", expected \"${impl.id}\"`)\n const part = (e as { part?: string }).part\n if (part !== undefined && !ids.has(part)) problems.push(`${label} event ${i} (${e.type}) names unknown part \"${part}\"`)\n })\n const bad: string[] = []\n finiteJson(evs, 'events', bad)\n problems.push(...bad.map((b) => `${label}: ${b}`))\n try {\n const s = JSON.stringify(evs)\n if (JSON.stringify(JSON.parse(s)) !== s) problems.push(`${label} events do not survive a JSON round trip`)\n } catch (e) {\n problems.push(`${label} events are not JSON-serializable: ${String(e)}`)\n }\n }\n checkState('before the hit')\n const target = spec.parts[0]\n try {\n const hit: Hit = { type: 'ballistic', point: [target.position[0], target.position[1], target.position[2]], direction: [0, 0, -1], energy: 500, caliber: 9 }\n checkEvents(impl.hit(hit), 'hit()')\n } catch (e) {\n problems.push(`hit() threw: ${String(e)}`)\n }\n checkState('after the hit')\n if (impl.update) {\n try {\n checkEvents(impl.update(0.016), 'update()')\n } catch (e) {\n problems.push(`update() threw: ${String(e)}`)\n }\n checkState('after update')\n }\n try {\n impl.reset?.()\n } catch (e) {\n problems.push(`reset() threw: ${String(e)}`)\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The slots of a body an item can cover (the item slot vocabulary: game-items imports it from here). */\nexport type BodySlot = 'head' | 'eyes' | 'torso' | 'arms' | 'hands' | 'legs' | 'feet' | 'over'\n\n/** The 11 segments of the detailed body model (the Arsenal ids). */\nexport type SegmentKind = 'head' | 'jaw' | 'neck' | 'torso' | 'pelvis' | 'upperArm' | 'forearm' | 'hand' | 'thigh' | 'shin' | 'foot'\n\n/** The 6 regions of the crowd model (the game regions 0..5). */\nexport type BodyRegion = 'head' | 'torso' | 'armL' | 'armR' | 'legL' | 'legR'\n\n/** Bits of the `missing` mask of a body. The instanced body shader reads them, so the values are part of the contract. */\nexport const MISSING_BITS = {\n head: 1,\n armL: 2,\n armR: 4,\n legL: 8,\n legR: 16,\n armLBroken: 32,\n armRBroken: 64,\n legLBroken: 128,\n legRBroken: 256,\n jaw: 512,\n} as const\n","import type { DestructibleSpec, PartSpec } from './destruction'\n\nexport interface CheckSpecOptions {\n /** Known material ids. When given, every part's material must be one of them. */\n materials?: Iterable<string>\n}\n\nexport interface SpecReport {\n /** False when there is at least one problem (warnings do not count). */\n ok: boolean\n problems: string[]\n /** Things that are legal but probably wrong, e.g. structural parts that cannot reach an anchored part. */\n warnings: string[]\n}\n\nconst STRUCTURE_ROLES = ['foundation', 'column', 'beam', 'slab', 'wall', 'partition', 'roof', 'stair', 'none']\nconst BEARS = [true, false, 'a_on_b', 'b_on_a']\n\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\nconst positive = (n: unknown): boolean => finite(n) && n > 0\nconst vec3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every(finite)\nconst nonNegative = (n: unknown): boolean => n === undefined || (finite(n) && n >= 0)\n\nconst FACES = ['both', '+', '-']\nconst BONDS = ['running', 'stack']\n\n/** Layers, reinforcement, units and breach: known materials, finite positive sizes. */\nconst checkStagedFields = (p: PartSpec | undefined, label: string, materials: Set<string> | null, problems: string[]): void => {\n const known = (m: unknown, what: string): void => {\n if (materials && !materials.has(m as string)) problems.push(`${label} ${what} has unknown material \"${String(m)}\"`)\n }\n ;(p?.layers ?? []).forEach((l, i) => {\n known(l?.material, `layer ${i}`)\n if (!positive(l?.thickness)) problems.push(`${label} layer ${i} thickness must be finite and positive`)\n if (!FACES.includes(l?.faces)) problems.push(`${label} layer ${i} has an invalid faces \"${String(l?.faces)}\"`)\n })\n const r = p?.reinforcement\n if (r) {\n known(r.material, 'reinforcement')\n if (!positive(r.spacing) || !positive(r.diameter)) problems.push(`${label} reinforcement spacing and diameter must be finite and positive`)\n if (r.residualCapacity !== undefined && !(finite(r.residualCapacity) && r.residualCapacity >= 0 && r.residualCapacity <= 1)) problems.push(`${label} reinforcement residualCapacity must be between 0 and 1`)\n }\n const u = p?.units\n if (u) {\n known(u.mortar, 'units mortar')\n if (!vec3(u.size) || !u.size.every(positive)) problems.push(`${label} units size must be three finite positive numbers`)\n if (!BONDS.includes(u.bond)) problems.push(`${label} units has an invalid bond \"${String(u.bond)}\"`)\n }\n if (p?.breach && p.breach.area !== undefined && !positive(p.breach.area)) problems.push(`${label} breach area must be finite and positive`)\n}\n\n/**\n * Static checks on a `DestructibleSpec` (no world needed), for items that give a `spec` instead of an `impl`: unique part ids, known materials\n * (when `options.materials` is given), joints that reference existing, different parts, finite positive sizes, non-negative mass / hp / capacity,\n * valid structure roles and `bears` directions. A structural part with no path of joints to an anchored part is a warning, not a problem: it\n * is allowed, it just falls.\n */\nexport const checkSpec = (spec: DestructibleSpec, options: CheckSpecOptions = {}): SpecReport => {\n const problems: string[] = []\n const warnings: string[] = []\n const materials = options.materials ? new Set(options.materials) : null\n const parts = Array.isArray(spec?.parts) ? spec.parts : []\n if (!Array.isArray(spec?.parts) || parts.length === 0) problems.push('the spec needs at least one part')\n const ids = new Set<string>()\n for (const p of parts) {\n const id = String(p?.id ?? '')\n const label = `part \"${id}\"`\n if (id === '') problems.push('a part has no id')\n else if (ids.has(id)) problems.push(`duplicate part id \"${id}\"`)\n ids.add(id)\n if (materials && !materials.has(p?.material)) problems.push(`${label} has unknown material \"${String(p?.material)}\"`)\n else if (typeof p?.material !== 'string' || p.material === '') problems.push(`${label} needs a material`)\n const s = p?.shape\n if (s?.kind === 'box') {\n if (!Array.isArray(s.size) || s.size.length !== 3 || !s.size.every(positive)) problems.push(`${label} box size must be three finite positive numbers`)\n } else if (s?.kind === 'cylinder') {\n if (!positive(s.radius) || !positive(s.height)) problems.push(`${label} cylinder radius and height must be finite and positive`)\n } else if (s?.kind === 'sphere') {\n if (!positive(s.radius)) problems.push(`${label} sphere radius must be finite and positive`)\n } else problems.push(`${label} has an unknown shape`)\n if (!vec3(p?.position)) problems.push(`${label} needs a finite position [x, y, z]`)\n if (p?.rotation !== undefined && !vec3(p.rotation)) problems.push(`${label} has an invalid rotation`)\n for (const f of ['mass', 'hp', 'thickness'] as const) if (!nonNegative(p?.[f])) problems.push(`${label} ${f} must be a finite number >= 0`)\n if (p?.fill !== undefined && !(finite(p.fill) && p.fill >= 0 && p.fill <= 1)) problems.push(`${label} fill must be between 0 and 1`)\n if (p?.structure) {\n if (!STRUCTURE_ROLES.includes(p.structure.role)) problems.push(`${label} has an invalid structure role \"${String(p.structure.role)}\"`)\n if (!nonNegative(p.structure.capacity)) problems.push(`${label} structure capacity must be a finite number >= 0`)\n }\n if (p?.contains && !nonNegative(p.contains.liters)) problems.push(`${label} contains.liters must be a finite number >= 0`)\n checkStagedFields(p, label, materials, problems)\n }\n\n const jointIds = new Set<string>()\n const edges: Array<[string, string]> = []\n for (const j of spec?.joints ?? []) {\n const id = j.id ?? `${j.a}~${j.b}`\n const label = `joint \"${id}\"`\n if (jointIds.has(id)) problems.push(`duplicate joint id \"${id}\"`)\n jointIds.add(id)\n if (!ids.has(j.a)) problems.push(`${label} references unknown part \"${String(j.a)}\"`)\n if (!ids.has(j.b)) problems.push(`${label} references unknown part \"${String(j.b)}\"`)\n if (j.a === j.b) problems.push(`${label} joins part \"${String(j.a)}\" to itself`)\n if (j.bears !== undefined && !BEARS.includes(j.bears)) problems.push(`${label} has an invalid bears \"${String(j.bears)}\"`)\n if (!nonNegative(j.strength)) problems.push(`${label} strength must be a finite number >= 0`)\n if (!nonNegative(j.seconds)) problems.push(`${label} seconds must be a finite number >= 0`)\n if (j.bears !== false && ids.has(j.a) && ids.has(j.b) && j.a !== j.b) edges.push([j.a, j.b])\n }\n\n // structural parts must reach an anchored part over the joints (only when the spec lists its joints: otherwise they are inferred later)\n const structural = parts.filter((p) => p?.structure && p.structure.role !== 'none')\n if (structural.length && spec.joints) {\n const anchored = parts.filter((p) => p.anchored || p.structure?.role === 'foundation').map((p) => p.id)\n const adj = new Map<string, string[]>()\n for (const [a, b] of edges) {\n adj.set(a, [...(adj.get(a) ?? []), b])\n adj.set(b, [...(adj.get(b) ?? []), a])\n }\n const seen = new Set(anchored)\n const queue = [...anchored]\n while (queue.length) {\n for (const n of adj.get(queue.pop()!) ?? []) {\n if (seen.has(n)) continue\n seen.add(n)\n queue.push(n)\n }\n }\n const lost = structural.filter((p) => !seen.has(p.id)).map((p) => p.id)\n if (lost.length) warnings.push(`structural part(s) ${lost.map((i) => `\"${i}\"`).join(', ')} cannot reach an anchored part, so they will fall`)\n }\n return { ok: problems.length === 0, problems, warnings }\n}\n","import type { Hit, Vec3 } from './destruction'\n\n/**\n * A ballistic shot (one per pellet) as a weapon module produces it. Plain data: any damage model can consume it (`shotToHit` makes the `Hit`).\n */\nexport interface Shot {\n origin: Vec3\n /** Unit vector. */\n direction: Vec3\n /** Muzzle energy in joules. */\n energy: number\n /** Bullet diameter in millimetres. */\n caliber: number\n /** snake_case calibre id, e.g. `9x19`. */\n caliberId: string\n /** Muzzle speed in m/s. */\n speed: number\n /** Projectile mass in grams (one pellet for a shotgun load). */\n mass?: number\n /** Energy at a distance in metres, with the calibre's drag. */\n energyAt(distance: number): number\n pellet: number\n /** snake_case weapon id. */\n weapon: string\n source?: string\n}\n\n/** One use of a non-firearm (melee swing, flame tick, heat tick, throw, tool tick, extinguisher tick). */\nexport type Use =\n | { kind: 'melee'; damage: 'blunt' | 'slash'; energy: number; radius: number; reach: number; origin: Vec3; direction: Vec3 }\n | { kind: 'flame'; temperature: number; radius: number; range: number; cone: boolean; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'heat'; temperature: number; radius: number; range: number; duration: number; origin: Vec3; direction: Vec3 }\n | {\n kind: 'throw'\n energy: number\n radius: number\n fuse: number\n velocity: Vec3\n origin: Vec3\n fire?: { temperature: number; radius: number; seconds: number }\n /** Explosive id (`m67`, `molotov`, `pg7v`...): the host looks the numbers up in the arsenal. Absent on a plain thrown object. */\n explosive?: string\n /** What sets it off: `timer` after `fuse` seconds, `impact` on the first hit once armed. */\n trigger?: 'timer' | 'impact'\n /** Metres the projectile must fly before an impact sets it off; closer, it hits without exploding (rocket launchers). */\n armDistance?: number\n /** Radius in metres of the wounding area (`radius` is the lethal one). */\n injuryRadius?: number\n /** Seconds the blast stuns whoever is inside the injury radius (flashbang). */\n stunSeconds?: number\n /** Seconds the thrower held it after pulling the pin: `fuse` is what was left of the full fuse when it left the hand. */\n cooked?: number\n /** Went off in the thrower's hand (held past the fuse): no velocity, `fuse` is 0 and the blast is at `origin`. */\n inHand?: boolean\n }\n /** An explosive set down where the trigger is pulled (mine, C4, charge): it waits for its trigger instead of flying. */\n | {\n kind: 'place'\n explosive: string\n /** `wire`: something trips it; `remote`: the player fires it; `timer`: after `fuse` seconds. */\n triggers: readonly ('timer' | 'wire' | 'remote')[]\n energy: number\n /** Lethal radius in metres. */\n radius: number\n injuryRadius: number\n /** Cone the blast goes through, in front of `direction` (mine); absent for a blast in every direction. */\n cone?: { halfAngleDeg: number; length: number }\n /** The wire a `wire` charge stretches from where it is set down: metres out, degrees from its facing (counter-clockwise from above) and metres above it. */\n tripwire?: { length: number; angleDeg: number; height: number }\n fuse?: number\n origin: Vec3\n direction: Vec3\n }\n /** The blast behind a rocket launcher: dangerous inside `radius` metres, in the cone around `direction` (pointing away from the target). */\n | { kind: 'backblast'; radius: number; halfAngleDeg: number; origin: Vec3; direction: Vec3 }\n | { kind: 'tool'; tool: string; reach: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'extinguish'; range: number; coolC: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'hand'; reach: number; origin: Vec3; direction: Vec3 }\n\n/**\n * The `Hit` a ballistic shot makes at `point` (world space): the energy is what is left of the shot at that distance from its origin\n * (`shot.energyAt`), the direction and calibre come from the shot. `normal` (the surface normal at the point, when the caller knows it) does not\n * change the hit; it is accepted so that callers can pass what their raycast returned and so that a stricter implementation could use it.\n */\nexport const shotToHit = (shot: Shot, point: Vec3, normal?: Vec3): Hit => {\n void normal\n const dx = point[0] - shot.origin[0]\n const dy = point[1] - shot.origin[1]\n const dz = point[2] - shot.origin[2]\n const distance = Math.hypot(dx, dy, dz)\n return {\n type: 'ballistic',\n point: [point[0], point[1], point[2]],\n direction: [shot.direction[0], shot.direction[1], shot.direction[2]],\n energy: shot.energyAt(distance),\n caliber: shot.caliber,\n ...(shot.mass === undefined ? {} : { mass: shot.mass }),\n source: shot.source ?? shot.weapon,\n }\n}\n","import type { Vec3 } from './destruction'\n\n/** Pose of one part (or of the whole item) at a key: position offset in metres and Euler XYZ rotation in radians, both relative to the rest pose. */\nexport interface AnimationPose {\n position?: Vec3\n rotation?: Vec3\n}\n\nexport interface AnimationKey {\n /** Seconds from the start of the clip. */\n t: number\n /** Pose of the item's root. */\n position?: Vec3\n rotation?: Vec3\n /** Poses of named parts: the key is a destruction part id (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */\n parts?: Record<string, AnimationPose>\n}\n\nexport interface AnimationEventSpec {\n /** Seconds from the start of the clip. */\n t: number\n /** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */\n type: string\n data?: Record<string, unknown>\n}\n\n/** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */\nexport interface AnimationClipSpec {\n /** snake_case: `reload`, `open`, `drink`... Unique within the item. */\n id: string\n /** Seconds. */\n duration: number\n keys: AnimationKey[]\n events?: AnimationEventSpec[]\n}\n\n/** What an item exposes when it brings its own animations. */\nexport interface ItemAnimationContract {\n animations: AnimationClipSpec[]\n}\n\nconst finite3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === 'number' && Number.isFinite(n))\n\n/**\n * Problems with a clip (empty when it is valid): a snake_case id, a positive finite duration, at least one key, key and event times inside\n * `[0, duration]` and not going backwards, finite vectors, every named part one of `partIds`, event types that are non-empty strings and\n * JSON-serializable event data.\n */\nexport const checkAnimation = (clip: AnimationClipSpec, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const parts = new Set(partIds)\n const id = String(clip?.id ?? '')\n if (!/^[a-z][a-z0-9_]*$/.test(id)) problems.push(`clip id \"${id}\" must be snake_case`)\n const label = `clip \"${id}\"`\n if (!(typeof clip?.duration === 'number' && Number.isFinite(clip.duration) && clip.duration > 0)) problems.push(`${label} needs a positive finite duration`)\n const dur = typeof clip?.duration === 'number' && clip.duration > 0 ? clip.duration : Infinity\n if (!Array.isArray(clip?.keys) || clip.keys.length === 0) {\n problems.push(`${label} needs at least one key`)\n return { ok: false, problems }\n }\n let prev = -Infinity\n clip.keys.forEach((k, i) => {\n if (!(typeof k.t === 'number' && Number.isFinite(k.t)) || k.t < 0 || k.t > dur + 1e-9) problems.push(`${label} key ${i} has t=${String(k.t)} outside 0..${dur}`)\n else if (k.t < prev) problems.push(`${label} key ${i} goes back in time (${k.t} < ${prev})`)\n else prev = k.t\n for (const f of ['position', 'rotation'] as const) if (k[f] !== undefined && !finite3(k[f])) problems.push(`${label} key ${i} has an invalid ${f}`)\n for (const [pid, pose] of Object.entries(k.parts ?? {})) {\n if (!parts.has(pid)) problems.push(`${label} key ${i} moves unknown part \"${pid}\"`)\n for (const f of ['position', 'rotation'] as const) if (pose?.[f] !== undefined && !finite3(pose[f])) problems.push(`${label} key ${i} part \"${pid}\" has an invalid ${f}`)\n }\n })\n let prevEv = -Infinity\n ;(clip.events ?? []).forEach((e, i) => {\n if (!(typeof e.t === 'number' && Number.isFinite(e.t)) || e.t < 0 || e.t > dur + 1e-9) problems.push(`${label} event ${i} has t=${String(e.t)} outside 0..${dur}`)\n else if (e.t < prevEv) problems.push(`${label} event ${i} goes back in time`)\n else prevEv = e.t\n if (typeof e.type !== 'string' || e.type === '') problems.push(`${label} event ${i} needs a type`)\n try {\n JSON.stringify(e.data ?? {})\n } catch {\n problems.push(`${label} event ${i} data is not JSON`)\n }\n })\n return { ok: problems.length === 0, problems }\n}\n","import type { AnimationClipSpec, AnimationEventSpec, AnimationPose } from './animation'\nimport { checkAnimation } from './animation'\nimport type { HostEvent } from './module'\n\n/** Clips a tool can bring, one per move of its cycle. A tool brings the ones it has; a missing clip is simply not played. */\nexport const TOOL_CLIP = {\n DRAW: 'draw',\n SHOT: 'shot',\n MISFIRE: 'misfire',\n EMPTY: 'empty',\n CYCLE: 'cycle',\n RELOAD: 'reload',\n FEED: 'feed',\n FAULT: 'fault',\n CLEAR: 'clear',\n SAFETY_ON: 'safety_on',\n SAFETY_OFF: 'safety_off',\n MODE: 'mode',\n /** A throwable with the pin out, held until it is thrown. */\n COOK: 'cook',\n /** The stock folds, unfolds, slides in or out (variants `fold`, `unfold`, `collapse`, `extend`). */\n STOCK: 'stock',\n /** A light or a laser is switched (variants `on`, `off`). */\n TOGGLE: 'toggle',\n} as const\nexport type ToolClipId = (typeof TOOL_CLIP)[keyof typeof TOOL_CLIP]\n\n/** What a renderer does when a rule event arrives. `variant` picks `<clip>_<variant>` when the tool has it, else `<clip>`. */\nexport type ToolAnimationStep =\n | { readonly type: 'play'; readonly clip: ToolClipId; readonly variant?: string; readonly duration?: number; readonly repeat?: number }\n /** Move the clip that is playing to the marker (a clip event `stage` with `data.stage`) the rule just reached. */\n | { readonly type: 'sync'; readonly stage: string }\n /** The rule says the phase is over: the clip jumps to its end if it is still running. */\n | { readonly type: 'finish'; readonly clip: ToolClipId }\n\nconst stockVariant = (event: Extract<HostEvent, { type: 'stock' }>): string => {\n const isFolding = event.kind === undefined ? false : event.kind.includes('fold')\n if (event.state === 'collapsed') return isFolding ? 'fold' : 'collapse'\n return isFolding ? 'unfold' : 'extend'\n}\n\n/** The move a host event asks of the tool in hand, or null when it has no motion. */\nexport const toolAnimationStep = (event: HostEvent): ToolAnimationStep | null => {\n switch (event.type) {\n case 'tool':\n return { type: 'play', clip: TOOL_CLIP.DRAW, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'shot':\n return { type: 'play', clip: TOOL_CLIP.SHOT, repeat: Math.max(1, event.rounds ?? 1) }\n case 'misfire':\n return { type: 'play', clip: TOOL_CLIP.MISFIRE }\n case 'empty':\n return { type: 'play', clip: TOOL_CLIP.EMPTY }\n case 'mode':\n return { type: 'play', clip: TOOL_CLIP.MODE, variant: event.mode }\n case 'fault':\n return { type: 'play', clip: TOOL_CLIP.FAULT, variant: event.fault }\n case 'clear':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CLEAR }\n return { type: 'play', clip: TOOL_CLIP.CLEAR, variant: event.fault, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'safety':\n return { type: 'play', clip: event.on ? TOOL_CLIP.SAFETY_ON : TOOL_CLIP.SAFETY_OFF }\n case 'cycle':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CYCLE }\n return { type: 'play', clip: TOOL_CLIP.CYCLE, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'reload':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.RELOAD }\n return { type: 'play', clip: TOOL_CLIP.RELOAD, ...(event.kind === undefined ? {} : { variant: event.kind }), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'stage':\n return { type: 'sync', stage: event.stage }\n case 'feed':\n return { type: 'play', clip: TOOL_CLIP.FEED }\n case 'cook':\n if (event.phase === 'start') return { type: 'play', clip: TOOL_CLIP.COOK, duration: event.fuse }\n return { type: 'finish', clip: TOOL_CLIP.COOK }\n case 'stock':\n return { type: 'play', clip: TOOL_CLIP.STOCK, variant: stockVariant(event), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'toggle':\n return { type: 'play', clip: TOOL_CLIP.TOGGLE, variant: event.on ? 'on' : 'off' }\n default:\n return null\n }\n}\n\n/** Where a renderer puts the poses: every part of the tool by id (the ids the clips move), and the whole tool. Poses are offsets from rest. */\nexport interface ToolRig {\n setPartPose(partId: string, pose: AnimationPose): void\n setRootPose?(pose: AnimationPose): void\n}\n\n/** Clips by id, with the variants as `<clip>_<variant>`. */\nexport type ToolClipSet = Readonly<Record<string, AnimationClipSpec>>\n\n/** A renderer's tool animator: feed it the host events and the frame time. */\nexport interface ToolAnimator {\n handle(event: HostEvent): void\n update(dt: number): void\n /** Ids of the clips playing now. */\n readonly playing: readonly string[]\n dispose(): void\n}\n\nexport const STAGE_EVENT = 'stage'\n\n/** The clip with its times (keys and events) scaled so that it lasts `duration` seconds. */\nexport const scaleClip = (clip: AnimationClipSpec, duration: number): AnimationClipSpec => {\n const factor = clip.duration > 0 ? duration / clip.duration : 1\n return {\n ...clip,\n duration,\n keys: clip.keys.map((key) => ({ ...key, t: key.t * factor })),\n ...(clip.events ? { events: clip.events.map((event) => ({ ...event, t: event.t * factor })) } : {}),\n }\n}\n\nconst ZERO: [number, number, number] = [0, 0, 0]\nconst mix = (from: readonly number[], to: readonly number[], fraction: number): [number, number, number] => [\n (from[0] ?? 0) + ((to[0] ?? 0) - (from[0] ?? 0)) * fraction,\n (from[1] ?? 0) + ((to[1] ?? 0) - (from[1] ?? 0)) * fraction,\n (from[2] ?? 0) + ((to[2] ?? 0) - (from[2] ?? 0)) * fraction,\n]\n\n/** Pose of the whole tool (`partId` undefined) or of one part at `time`, linear between keys; a key that does not name it means rest. */\nexport const sampleClip = (clip: AnimationClipSpec, partId: string | undefined, time: number): { position: [number, number, number]; rotation: [number, number, number] } => {\n const points = clip.keys.map((key) => {\n const pose = partId === undefined ? key : key.parts?.[partId]\n return { t: key.t, position: pose?.position ?? ZERO, rotation: pose?.rotation ?? ZERO }\n })\n const first = points[0]\n if (!first || first.t > 0) points.unshift({ t: 0, position: ZERO, rotation: ZERO })\n const last = points[points.length - 1]\n if (!last) return { position: ZERO, rotation: ZERO }\n if (time >= last.t) return { position: mix(last.position, last.position, 0), rotation: mix(last.rotation, last.rotation, 0) }\n const index = Math.max(0, points.findIndex((point, i) => time >= point.t && time < (points[i + 1]?.t ?? Infinity)))\n const from = points[index]\n const to = points[index + 1]\n if (!from || !to) return { position: ZERO, rotation: ZERO }\n const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0\n return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) }\n}\n\n/** Problems with a clip set (empty when valid): each key is the clip id (or `<clip>_<variant>`), every clip passes `checkAnimation`, and a `stage` event names its stage. */\nexport const checkToolClips = (clips: ToolClipSet, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const parts = [...partIds]\n const problems: string[] = []\n for (const [key, clip] of Object.entries(clips)) {\n if (clip.id !== key) problems.push(`clip \"${key}\" has the id \"${clip.id}\"`)\n if (!(clip.duration > 0)) problems.push(`clip \"${key}\" has no duration`)\n problems.push(...checkAnimation(clip, parts).problems)\n for (const event of clip.events ?? []) if (event.type === STAGE_EVENT && typeof event.data?.stage !== 'string') problems.push(`clip \"${key}\" has a stage event without data.stage`)\n }\n return { ok: problems.length === 0, problems }\n}\n\ninterface Instance {\n readonly name: string\n readonly clip: AnimationClipSpec\n time: number\n delay: number\n fired: number\n}\n\nexport interface ToolClipPlayerOptions {\n clips: ToolClipSet\n rig: ToolRig\n /** Called for each clip event as its time passes (`eject_casing`, `drop_magazine`, `stage`...). */\n onEvent?(event: AnimationEventSpec, clip: string): void\n /** Seconds between the rounds of a `repeat` (a burst): default 0.1. */\n repeatInterval?(): number\n}\n\n/** The default player of a tool's clips: runs the steps of `toolAnimationStep` against a rig, scaled to the duration the rule gives. */\nexport interface ToolClipPlayer {\n apply(step: ToolAnimationStep): void\n update(dt: number): void\n readonly playing: readonly string[]\n}\n\nexport const createToolClipPlayer = (options: ToolClipPlayerOptions): ToolClipPlayer => {\n const { clips, rig, onEvent } = options\n let instances: Instance[] = []\n\n const find = (clip: string, variant?: string): { name: string; clip: AnimationClipSpec } | undefined => {\n const name = variant !== undefined && clips[`${clip}_${variant}`] ? `${clip}_${variant}` : clip\n const found = clips[name]\n return found ? { name, clip: found } : undefined\n }\n\n const render = (instance: Instance): void => {\n const time = Math.min(instance.time, instance.clip.duration)\n const root = sampleClip(instance.clip, undefined, time)\n if (instance.clip.keys.some((key) => key.position || key.rotation)) rig.setRootPose?.(root)\n const touched = new Set(instance.clip.keys.flatMap((key) => Object.keys(key.parts ?? {})))\n for (const id of touched) rig.setPartPose(id, sampleClip(instance.clip, id, time))\n }\n\n const fireEvents = (instance: Instance, upTo: number): void => {\n const events = instance.clip.events ?? []\n while (instance.fired < events.length && (events[instance.fired]?.t ?? Infinity) <= upTo + 1e-9) {\n const event = events[instance.fired]\n instance.fired += 1\n if (event) onEvent?.(event, instance.name)\n }\n }\n\n return {\n apply(step) {\n if (step.type === 'sync') {\n for (const instance of instances) {\n const marker = (instance.clip.events ?? []).find((event) => event.type === STAGE_EVENT && event.data?.stage === step.stage)\n if (marker && instance.time < marker.t) instance.time = marker.t\n }\n return\n }\n if (step.type === 'finish') {\n for (const instance of instances) {\n if (instance.name !== step.clip && !instance.name.startsWith(`${step.clip}_`)) continue\n fireEvents(instance, instance.clip.duration)\n instance.time = instance.clip.duration\n }\n return\n }\n const found = find(step.clip, step.variant)\n if (!found) return\n const clip = step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip\n const interval = options.repeatInterval?.() ?? 0.1\n instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0)\n for (let round = 0; round < (step.repeat ?? 1); round++) {\n instances.push({ name: found.name, clip, time: 0, delay: round * interval, fired: 0 })\n }\n },\n update(dt) {\n for (const instance of instances) {\n const carried = instance.delay > 0 ? dt - instance.delay : dt\n if (instance.delay > 0) {\n instance.delay = Math.max(0, instance.delay - dt)\n if (instance.delay > 0) continue\n }\n instance.time += carried\n fireEvents(instance, instance.time)\n render(instance)\n }\n instances = instances.filter((instance) => instance.delay > 0 || instance.time < instance.clip.duration)\n },\n get playing() {\n return instances.map((instance) => instance.name)\n },\n }\n}\n","type PlainObject = Record<string, unknown>\ntype Entry = { readonly id: string }\n\n/** Keys that would reach the prototype of the result when a patch comes from parsed JSON. */\nconst UNSAFE_KEYS: ReadonlySet<string> = new Set(['__proto__', 'constructor', 'prototype'])\n\nconst isPlainObject = (value: unknown): value is PlainObject =>\n typeof value === 'object' && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype\n\n/** Recursively optional; arrays and functions are replaced as a whole, objects are merged. */\nexport type DeepPartial<TValue> = TValue extends (...args: never[]) => unknown\n ? TValue\n : TValue extends readonly unknown[]\n ? TValue\n : TValue extends object\n ? { [TKey in keyof TValue]?: DeepPartial<TValue[TKey]> }\n : TValue\n\n/** Merges `patch` over `base`: plain objects merge field by field, arrays, functions and primitives replace. `undefined` in the patch is ignored. */\nexport const deepMerge = <TBase extends object>(base: TBase, patch: object | undefined): TBase => {\n if (!patch) return base\n const result: PlainObject = { ...(base as PlainObject) }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined || UNSAFE_KEYS.has(key)) continue\n const current = result[key]\n result[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value\n }\n return result as TBase\n}\n\n/** The shipped entries with the ones from `extra` added or, when the id exists, replaced. */\nexport const mergeById = <TEntry extends Entry>(shipped: readonly TEntry[], extra: readonly TEntry[] | undefined): Map<string, TEntry> => {\n const entries = new Map<string, TEntry>()\n for (const entry of [...shipped, ...(extra ?? [])]) entries.set(entry.id, entry)\n return entries\n}\n\n/** Applies `patch` by id; a patch for an id that does not exist is ignored. */\nexport const applyPatches = <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, DeepPartial<TEntry> | Partial<TEntry>>> | undefined): void => {\n for (const [id, patch] of Object.entries(patches ?? {})) {\n const entry = entries.get(id)\n if (entry) entries.set(id, deepMerge(entry, patch))\n }\n}\n\nexport const removeIds = (entries: Map<string, unknown>, ids: readonly string[] | undefined): void => {\n for (const id of ids ?? []) entries.delete(id)\n}\n","import type { Vec3 } from './destruction'\n\n/**\n * 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\n * every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.\n */\nexport type ControlValue = number | boolean | string\n\nexport interface ChoiceOption {\n readonly id: string\n readonly name: string\n /** Heading the host lists the option under (`optgroup`, a grouped row of buttons). Options of a group must sit next to each other. */\n readonly group?: string\n}\n\ninterface ControlBase {\n /** snake_case, unique across the module's controls, layers and metrics. */\n readonly id: string\n /** Display name, already translated for the locale the host asked for. */\n readonly name: string\n readonly hint?: string\n readonly hidden?: boolean\n}\n\nexport interface NumberControl extends ControlBase {\n readonly kind: 'number'\n readonly min: number\n readonly max: number\n readonly step?: number\n readonly unit?: string\n readonly default: number\n}\n\nexport interface BooleanControl extends ControlBase {\n readonly kind: 'boolean'\n readonly default: boolean\n}\n\nexport interface ChoiceControl extends ControlBase {\n readonly kind: 'choice'\n readonly options: readonly ChoiceOption[]\n readonly default: string\n}\n\n/** A button: no value, only the press. */\nexport interface ActionControl extends ControlBase {\n readonly kind: 'action'\n}\n\nexport type ModuleControl = NumberControl | BooleanControl | ChoiceControl | ActionControl\n\n/** Something of the module the viewer turns on or off (smoke, flash, casings). */\nexport interface ModuleLayer {\n readonly id: string\n readonly name: string\n /** On unless `false`. */\n readonly default?: boolean\n}\n\n/** Pulled: the room calls read only when it samples (~4 Hz, or on TestRoom.metrics()). */\nexport interface ModuleMetric {\n readonly id: string\n readonly name: string\n readonly unit?: string\n /** Budget: the host shows a bar and warns past it. */\n readonly max?: number\n readonly digits?: number\n read(): number | string\n}\n\nexport type BenchChange =\n | { readonly kind: 'control'; readonly id: string; readonly value: ControlValue }\n | { readonly kind: 'layer'; readonly id: string; readonly on: boolean }\n | { readonly kind: 'action'; readonly id: string }\n\nexport interface ControlPatch {\n readonly hidden?: boolean\n readonly disabled?: boolean\n readonly options?: readonly ChoiceOption[]\n readonly value?: ControlValue\n}\n\n/** What the host lends a module that declares a bench: the current values and the changes. */\nexport interface ModuleBench {\n value(id: string): ControlValue | undefined\n layer(id: string): boolean\n /** Right after attach returns, the host sends one change per control (not actions) and per layer, in declaration order. */\n onChange(listener: (change: BenchChange) => void): () => void\n /** `value` sets without emitting (no echo loops); `options` falls back to the first option if the value is no longer offered. */\n patch(id: string, patch: ControlPatch): void\n /** Move the host camera (a near / middle / far choice). */\n look(eye: Vec3, target: Vec3): void\n /** Shows a sheet on the host's panel, or takes it down with `null`. A host without a sheet area ignores it. */\n sheet?(sheet: BenchSheet | null): void\n}\n\n/**\n * A card of text the module puts on the host's panel (the sheet of an effect, of a weapon): what it is, its numbers, its steps, its colours and,\n * optionally, a strip of segments over time. Names and texts are already translated for the host's locale.\n */\nexport interface BenchSheet {\n readonly kicker?: string\n readonly title: string\n readonly body?: string\n readonly facts?: readonly { readonly label: string; readonly value: string }[]\n readonly steps?: readonly { readonly at: string; readonly text: string }[]\n readonly swatches?: readonly { readonly color: string; readonly name: string }[]\n readonly note?: string\n /** A strip of named segments (seconds), with tick marks and a playhead that follows the module metric `metric`. */\n readonly timeline?: {\n readonly segments: readonly { readonly name: string; readonly start: number; readonly end: number; readonly color: string }[]\n readonly ticks: readonly number[]\n readonly metric: string\n }\n}\n\nexport interface BenchReport {\n readonly ok: boolean\n readonly problems: readonly string[]\n}\n\nexport interface BenchDeclaration {\n readonly controls?: readonly ModuleControl[]\n readonly layers?: readonly ModuleLayer[]\n readonly metrics?: readonly ModuleMetric[]\n}\n\nconst SNAKE_CASE = /^[a-z][a-z0-9_]*$/\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\n\nfunction checkControl(control: ModuleControl, problems: string[]): void {\n const label = `control \"${control.id}\"`\n if (control.kind === 'number') {\n if (!finite(control.min) || !finite(control.max) || control.min >= control.max) problems.push(`${label} needs min < max`)\n if (control.step !== undefined && !(finite(control.step) && control.step > 0)) problems.push(`${label} needs a step > 0`)\n if (!finite(control.default) || control.default < control.min || control.default > control.max) problems.push(`${label} default ${String(control.default)} is outside ${control.min}..${control.max}`)\n return\n }\n if (control.kind !== 'choice') return\n if (control.options.length === 0) {\n problems.push(`${label} needs at least one option`)\n return\n }\n const seen = new Set<string>()\n const groups = new Set<string>()\n let previousGroup: string | undefined\n for (const option of control.options) {\n if (seen.has(option.id)) problems.push(`${label} repeats option \"${option.id}\"`)\n seen.add(option.id)\n if (option.group !== undefined && (typeof option.group !== 'string' || option.group === '')) problems.push(`${label} option \"${option.id}\" has an empty group`)\n else if (option.group !== undefined && option.group !== previousGroup && groups.has(option.group)) problems.push(`${label} group \"${option.group}\" is split: its options must be next to each other`)\n if (typeof option.group === 'string' && option.group !== '') groups.add(option.group)\n previousGroup = option.group\n }\n if (!seen.has(control.default)) problems.push(`${label} default \"${control.default}\" is not among its options`)\n}\n\n/** snake_case ids unique across controls, layers and metrics; min < max; step > 0; default in range; options non-empty, unique, default among them, each group in one run; metric max > 0. */\nexport function checkBench(declared: BenchDeclaration): BenchReport {\n const problems: string[] = []\n const ids = new Set<string>()\n const claim = (kind: string, id: string): void => {\n if (!SNAKE_CASE.test(id)) problems.push(`${kind} id \"${id}\" must be snake_case`)\n if (ids.has(id)) problems.push(`duplicate id \"${id}\" across controls, layers and metrics`)\n ids.add(id)\n }\n for (const control of declared.controls ?? []) {\n claim('control', control.id)\n checkControl(control, problems)\n }\n for (const layer of declared.layers ?? []) claim('layer', layer.id)\n for (const metric of declared.metrics ?? []) {\n claim('metric', metric.id)\n if (metric.max !== undefined && !(finite(metric.max) && metric.max > 0)) problems.push(`metric \"${metric.id}\" needs a max > 0`)\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The weather, light and ground as the world is right now. Written by whoever drives the weather, read by whoever reacts to it. */\nexport interface EnvironmentState {\n /** Rain in mm/h; 0 is dry. */\n readonly rain: number\n /** Wind in m/s along x and z, and its strength (the length of that vector). */\n readonly windX: number\n readonly windZ: number\n readonly windSpeed: number\n /** Strongest gust in m/s. */\n readonly gust: number\n /** Render fog far distance in metres. */\n readonly fogFar: number\n /** How far one can see for gameplay, in metres (not the render fog). */\n readonly visibility: number\n /** 0 is full daylight, 1 is the darkest night. */\n readonly nightFactor: number\n readonly temperatureC: number\n /** Wet ground, 0..1: rises with rain and dries after it stops. */\n readonly wetness: number\n}\n\nexport type EnvironmentKey = keyof EnvironmentState\n\n/** A calm dry day (the normal world, before anything went wrong). */\nexport const DEFAULT_ENVIRONMENT: EnvironmentState = {\n rain: 0,\n windX: 0,\n windZ: 0,\n windSpeed: 0,\n gust: 0,\n fogFar: 220,\n visibility: 220,\n nightFactor: 0,\n temperatureC: 20,\n wetness: 0,\n}\n\n/** The host's environment (`ModuleHost.environment`): a store the weather module writes to and the others read. */\nexport interface EnvironmentBus {\n get(): EnvironmentState\n /** Merges `patch` into the state; listeners hear it only when a value really changed. */\n set(patch: Partial<EnvironmentState>): void\n /** Hears every change with the keys that changed. Returns the unsubscribe function. */\n on(listener: (state: EnvironmentState, changed: readonly EnvironmentKey[]) => void): () => void\n}\n\n/** A plain store that implements the bus; the test room and tests use it, a game may bring its own. */\nexport function createEnvironmentBus(initial: Partial<EnvironmentState> = {}): EnvironmentBus {\n let state: EnvironmentState = { ...DEFAULT_ENVIRONMENT, ...initial }\n const listeners = new Set<(state: EnvironmentState, changed: readonly EnvironmentKey[]) => void>()\n return {\n get: () => state,\n set(patch) {\n const changed = (Object.keys(patch) as EnvironmentKey[]).filter((key) => patch[key] !== undefined && patch[key] !== state[key])\n if (changed.length === 0) return\n const next = { ...state } as { -readonly [Key in EnvironmentKey]: number }\n for (const key of changed) next[key] = patch[key] as number\n state = next\n for (const listener of [...listeners]) listener(state, changed)\n },\n on(listener) {\n listeners.add(listener)\n return () => void listeners.delete(listener)\n },\n }\n}\n"],"mappings":";AAuCO,IAAM,kBAAsD;AAAA,EACjE,WAAW,EAAE,UAAU,CAAC,QAAQ,SAAS,aAAa,UAAU,SAAS,GAAG,UAAU,CAAC,SAAS,QAAQ,GAAG,MAAM,0DAA0D;AAAA,EAC3K,OAAO,EAAE,UAAU,CAAC,QAAQ,SAAS,QAAQ,GAAG,UAAU,CAAC,aAAa,UAAU,SAAS,QAAQ,GAAG,MAAM,0CAA0C;AAAA,EACtJ,OAAO,EAAE,UAAU,CAAC,QAAQ,SAAS,QAAQ,GAAG,UAAU,CAAC,aAAa,UAAU,SAAS,QAAQ,GAAG,MAAM,yBAAyB;AAAA,EACrI,WAAW,EAAE,UAAU,CAAC,QAAQ,SAAS,UAAU,QAAQ,GAAG,UAAU,CAAC,SAAS,QAAQ,GAAG,MAAM,+DAA+D;AAAA,EAClK,MAAM,EAAE,UAAU,CAAC,QAAQ,SAAS,eAAe,QAAQ,GAAG,UAAU,CAAC,YAAY,SAAS,QAAQ,GAAG,MAAM,+EAA4E;AAAA,EAC3L,MAAM,EAAE,UAAU,CAAC,QAAQ,SAAS,eAAe,QAAQ,GAAG,UAAU,CAAC,YAAY,SAAS,QAAQ,GAAG,MAAM,gDAAgD;AACjK;AAGO,IAAM,WAAW,CAAC,QAAuB;AAC9C,QAAM,IAAI,gBAAgB,IAAI,IAAI;AAClC,MAAI,CAAC,EAAG,QAAO,CAAC,wBAAwB,OAAO,IAAI,IAAI,CAAC,GAAG;AAC3D,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,EAAE,UAAU;AAC1B,UAAM,IAAK,IAA2C,CAAC;AACvD,QAAI,MAAM,WAAc,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC,IAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,GAAI,KAAI,KAAK,GAAG,IAAI,IAAI,eAAe,CAAC,GAAG;AAAA,EACxK;AACA,SAAO;AACT;AAUA,IAAM,aAAa,CAAC,GAAY,MAAc,QAAkB;AAC9D,MAAI,OAAO,MAAM,UAAU;AACzB,QAAI,CAAC,OAAO,SAAS,CAAC,EAAG,KAAI,KAAK,GAAG,IAAI,yBAAyB;AAAA,EACpE,WAAW,MAAM,QAAQ,CAAC,EAAG,GAAE,QAAQ,CAAC,GAAG,MAAM,WAAW,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC;AAAA,WAC3E,KAAK,OAAO,MAAM,SAAU,YAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,CAAC,EAAG,YAAW,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG;AAAA,WACrG,OAAO,MAAM,cAAc,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,KAAI,KAAK,GAAG,IAAI,cAAc;AACpH;AAOO,IAAM,oBAAoB,CAAC,MAAwB,UAAwB,CAAC,MAA2C;AAC5H,QAAM,WAAqB,CAAC;AAC5B,QAAM,QAAQ,QAAQ,YAAY,IAAI,IAAI,QAAQ,SAAS,IAAI;AAC/D,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,SAAS;AAAA,EACvB,SAAS,GAAG;AACV,WAAO,EAAE,IAAI,OAAO,UAAU,CAAC,qBAAqB,OAAO,CAAC,CAAC,EAAE,EAAE;AAAA,EACnE;AACA,MAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,WAAW,EAAG,QAAO,EAAE,IAAI,OAAO,UAAU,CAAC,0CAA0C,EAAE;AAC/I,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,KAAK,OAAO;AAC1B,QAAI,IAAI,IAAI,EAAE,EAAE,EAAG,UAAS,KAAK,sBAAsB,EAAE,EAAE,GAAG;AAC9D,QAAI,IAAI,EAAE,EAAE;AACZ,QAAI,SAAS,CAAC,MAAM,IAAI,EAAE,QAAQ,EAAG,UAAS,KAAK,SAAS,EAAE,EAAE,2BAA2B,EAAE,QAAQ,GAAG;AACxG,QAAI,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,YAAY,QAAQ,EAAE,SAAS,EAAE,MAAM,IAAI,EAAG,UAAS,KAAK,SAAS,EAAE,EAAE,sBAAsB;AACxH,QAAI,CAAC,MAAM,QAAQ,EAAE,QAAQ,KAAK,EAAE,SAAS,WAAW,EAAG,UAAS,KAAK,SAAS,EAAE,EAAE,mBAAmB;AAAA,EAC3G;AACA,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,KAAM,KAAK,UAAU,CAAC,GAAmB;AAClD,QAAI,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,EAAG,UAAS,KAAK,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,8BAA8B;AACjH,UAAM,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;AACjC,QAAI,SAAS,IAAI,GAAG,EAAG,UAAS,KAAK,uBAAuB,GAAG,GAAG;AAClE,aAAS,IAAI,GAAG;AAAA,EAClB;AACA,QAAM,aAAa,CAAC,UAAkB;AACpC,QAAI;AACJ,QAAI;AACF,WAAK,KAAK,MAAM;AAAA,IAClB,SAAS,GAAG;AACV,eAAS,KAAK,iBAAiB,KAAK,KAAK,OAAO,CAAC,CAAC,EAAE;AACpD;AAAA,IACF;AACA,UAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG;AACtD,QAAI,SAAS,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,EAAG,UAAS,KAAK,4CAA4C,KAAK,EAAE;AACzG,eAAW,KAAK,GAAG,MAAO,KAAI,EAAE,EAAE,MAAM,GAAI,UAAS,KAAK,SAAS,EAAE,EAAE,gCAAgC,KAAK,EAAE;AAAA,EAChH;AACA,QAAM,cAAc,CAAC,QAAiB,UAAkB;AACtD,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,eAAS,KAAK,GAAG,KAAK,uBAAuB;AAC7C;AAAA,IACF;AACA,UAAM,MAAM;AACZ,QAAI,QAAQ,CAAC,GAAG,MAAM;AACpB,UAAI,EAAE,WAAW,KAAK,GAAI,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,KAAK,EAAE,IAAI,iBAAiB,OAAO,EAAE,MAAM,CAAC,gBAAgB,KAAK,EAAE,GAAG;AACjI,YAAM,OAAQ,EAAwB;AACtC,UAAI,SAAS,UAAa,CAAC,IAAI,IAAI,IAAI,EAAG,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,KAAK,EAAE,IAAI,yBAAyB,IAAI,GAAG;AAAA,IACxH,CAAC;AACD,UAAM,MAAgB,CAAC;AACvB,eAAW,KAAK,UAAU,GAAG;AAC7B,aAAS,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK,KAAK,CAAC,EAAE,CAAC;AACjD,QAAI;AACF,YAAM,IAAI,KAAK,UAAU,GAAG;AAC5B,UAAI,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,MAAM,EAAG,UAAS,KAAK,GAAG,KAAK,0CAA0C;AAAA,IAC3G,SAAS,GAAG;AACV,eAAS,KAAK,GAAG,KAAK,sCAAsC,OAAO,CAAC,CAAC,EAAE;AAAA,IACzE;AAAA,EACF;AACA,aAAW,gBAAgB;AAC3B,QAAM,SAAS,KAAK,MAAM,CAAC;AAC3B,MAAI;AACF,UAAM,MAAW,EAAE,MAAM,aAAa,OAAO,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO,SAAS,CAAC,GAAG,OAAO,SAAS,CAAC,CAAC,GAAG,WAAW,CAAC,GAAG,GAAG,EAAE,GAAG,QAAQ,KAAK,SAAS,EAAE;AAC1J,gBAAY,KAAK,IAAI,GAAG,GAAG,OAAO;AAAA,EACpC,SAAS,GAAG;AACV,aAAS,KAAK,gBAAgB,OAAO,CAAC,CAAC,EAAE;AAAA,EAC3C;AACA,aAAW,eAAe;AAC1B,MAAI,KAAK,QAAQ;AACf,QAAI;AACF,kBAAY,KAAK,OAAO,KAAK,GAAG,UAAU;AAAA,IAC5C,SAAS,GAAG;AACV,eAAS,KAAK,mBAAmB,OAAO,CAAC,CAAC,EAAE;AAAA,IAC9C;AACA,eAAW,cAAc;AAAA,EAC3B;AACA,MAAI;AACF,SAAK,QAAQ;AAAA,EACf,SAAS,GAAG;AACV,aAAS,KAAK,kBAAkB,OAAO,CAAC,CAAC,EAAE;AAAA,EAC7C;AACA,SAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAC/C;;;ACxJO,IAAM,eAAe;AAAA,EAC1B,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,KAAK;AACP;;;ACNA,IAAM,kBAAkB,CAAC,cAAc,UAAU,QAAQ,QAAQ,QAAQ,aAAa,QAAQ,SAAS,MAAM;AAC7G,IAAM,QAAQ,CAAC,MAAM,OAAO,UAAU,QAAQ;AAE9C,IAAM,SAAS,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AACtF,IAAM,WAAW,CAAC,MAAwB,OAAO,CAAC,KAAK,IAAI;AAC3D,IAAM,OAAO,CAAC,MAAwB,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,KAAK,EAAE,MAAM,MAAM;AAC1F,IAAM,cAAc,CAAC,MAAwB,MAAM,UAAc,OAAO,CAAC,KAAK,KAAK;AAEnF,IAAM,QAAQ,CAAC,QAAQ,KAAK,GAAG;AAC/B,IAAM,QAAQ,CAAC,WAAW,OAAO;AAGjC,IAAM,oBAAoB,CAAC,GAAyB,OAAe,WAA+B,aAA6B;AAC7H,QAAM,QAAQ,CAAC,GAAY,SAAuB;AAChD,QAAI,aAAa,CAAC,UAAU,IAAI,CAAW,EAAG,UAAS,KAAK,GAAG,KAAK,IAAI,IAAI,0BAA0B,OAAO,CAAC,CAAC,GAAG;AAAA,EACpH;AACC,GAAC,GAAG,UAAU,CAAC,GAAG,QAAQ,CAAC,GAAG,MAAM;AACnC,UAAM,GAAG,UAAU,SAAS,CAAC,EAAE;AAC/B,QAAI,CAAC,SAAS,GAAG,SAAS,EAAG,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,wCAAwC;AACtG,QAAI,CAAC,MAAM,SAAS,GAAG,KAAK,EAAG,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,0BAA0B,OAAO,GAAG,KAAK,CAAC,GAAG;AAAA,EAC/G,CAAC;AACD,QAAM,IAAI,GAAG;AACb,MAAI,GAAG;AACL,UAAM,EAAE,UAAU,eAAe;AACjC,QAAI,CAAC,SAAS,EAAE,OAAO,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,iEAAiE;AAC1I,QAAI,EAAE,qBAAqB,UAAa,EAAE,OAAO,EAAE,gBAAgB,KAAK,EAAE,oBAAoB,KAAK,EAAE,oBAAoB,GAAI,UAAS,KAAK,GAAG,KAAK,yDAAyD;AAAA,EAC9M;AACA,QAAM,IAAI,GAAG;AACb,MAAI,GAAG;AACL,UAAM,EAAE,QAAQ,cAAc;AAC9B,QAAI,CAAC,KAAK,EAAE,IAAI,KAAK,CAAC,EAAE,KAAK,MAAM,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,mDAAmD;AACvH,QAAI,CAAC,MAAM,SAAS,EAAE,IAAI,EAAG,UAAS,KAAK,GAAG,KAAK,+BAA+B,OAAO,EAAE,IAAI,CAAC,GAAG;AAAA,EACrG;AACA,MAAI,GAAG,UAAU,EAAE,OAAO,SAAS,UAAa,CAAC,SAAS,EAAE,OAAO,IAAI,EAAG,UAAS,KAAK,GAAG,KAAK,0CAA0C;AAC5I;AAQO,IAAM,YAAY,CAAC,MAAwB,UAA4B,CAAC,MAAkB;AAC/F,QAAM,WAAqB,CAAC;AAC5B,QAAM,WAAqB,CAAC;AAC5B,QAAM,YAAY,QAAQ,YAAY,IAAI,IAAI,QAAQ,SAAS,IAAI;AACnE,QAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAAI,KAAK,QAAQ,CAAC;AACzD,MAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,KAAK,MAAM,WAAW,EAAG,UAAS,KAAK,kCAAkC;AACvG,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,OAAO;AACrB,UAAM,KAAK,OAAO,GAAG,MAAM,EAAE;AAC7B,UAAM,QAAQ,SAAS,EAAE;AACzB,QAAI,OAAO,GAAI,UAAS,KAAK,kBAAkB;AAAA,aACtC,IAAI,IAAI,EAAE,EAAG,UAAS,KAAK,sBAAsB,EAAE,GAAG;AAC/D,QAAI,IAAI,EAAE;AACV,QAAI,aAAa,CAAC,UAAU,IAAI,GAAG,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,0BAA0B,OAAO,GAAG,QAAQ,CAAC,GAAG;AAAA,aAC3G,OAAO,GAAG,aAAa,YAAY,EAAE,aAAa,GAAI,UAAS,KAAK,GAAG,KAAK,mBAAmB;AACxG,UAAM,IAAI,GAAG;AACb,QAAI,GAAG,SAAS,OAAO;AACrB,UAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,KAAK,EAAE,KAAK,WAAW,KAAK,CAAC,EAAE,KAAK,MAAM,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,iDAAiD;AAAA,IACvJ,WAAW,GAAG,SAAS,YAAY;AACjC,UAAI,CAAC,SAAS,EAAE,MAAM,KAAK,CAAC,SAAS,EAAE,MAAM,EAAG,UAAS,KAAK,GAAG,KAAK,yDAAyD;AAAA,IACjI,WAAW,GAAG,SAAS,UAAU;AAC/B,UAAI,CAAC,SAAS,EAAE,MAAM,EAAG,UAAS,KAAK,GAAG,KAAK,4CAA4C;AAAA,IAC7F,MAAO,UAAS,KAAK,GAAG,KAAK,uBAAuB;AACpD,QAAI,CAAC,KAAK,GAAG,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,oCAAoC;AAClF,QAAI,GAAG,aAAa,UAAa,CAAC,KAAK,EAAE,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,0BAA0B;AACpG,eAAW,KAAK,CAAC,QAAQ,MAAM,WAAW,EAAY,KAAI,CAAC,YAAY,IAAI,CAAC,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,IAAI,CAAC,+BAA+B;AAC1I,QAAI,GAAG,SAAS,UAAa,EAAE,OAAO,EAAE,IAAI,KAAK,EAAE,QAAQ,KAAK,EAAE,QAAQ,GAAI,UAAS,KAAK,GAAG,KAAK,+BAA+B;AACnI,QAAI,GAAG,WAAW;AAChB,UAAI,CAAC,gBAAgB,SAAS,EAAE,UAAU,IAAI,EAAG,UAAS,KAAK,GAAG,KAAK,mCAAmC,OAAO,EAAE,UAAU,IAAI,CAAC,GAAG;AACrI,UAAI,CAAC,YAAY,EAAE,UAAU,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,kDAAkD;AAAA,IAClH;AACA,QAAI,GAAG,YAAY,CAAC,YAAY,EAAE,SAAS,MAAM,EAAG,UAAS,KAAK,GAAG,KAAK,+CAA+C;AACzH,sBAAkB,GAAG,OAAO,WAAW,QAAQ;AAAA,EACjD;AAEA,QAAM,WAAW,oBAAI,IAAY;AACjC,QAAM,QAAiC,CAAC;AACxC,aAAW,KAAK,MAAM,UAAU,CAAC,GAAG;AAClC,UAAM,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;AAChC,UAAM,QAAQ,UAAU,EAAE;AAC1B,QAAI,SAAS,IAAI,EAAE,EAAG,UAAS,KAAK,uBAAuB,EAAE,GAAG;AAChE,aAAS,IAAI,EAAE;AACf,QAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,6BAA6B,OAAO,EAAE,CAAC,CAAC,GAAG;AACpF,QAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,6BAA6B,OAAO,EAAE,CAAC,CAAC,GAAG;AACpF,QAAI,EAAE,MAAM,EAAE,EAAG,UAAS,KAAK,GAAG,KAAK,gBAAgB,OAAO,EAAE,CAAC,CAAC,aAAa;AAC/E,QAAI,EAAE,UAAU,UAAa,CAAC,MAAM,SAAS,EAAE,KAAK,EAAG,UAAS,KAAK,GAAG,KAAK,0BAA0B,OAAO,EAAE,KAAK,CAAC,GAAG;AACzH,QAAI,CAAC,YAAY,EAAE,QAAQ,EAAG,UAAS,KAAK,GAAG,KAAK,wCAAwC;AAC5F,QAAI,CAAC,YAAY,EAAE,OAAO,EAAG,UAAS,KAAK,GAAG,KAAK,uCAAuC;AAC1F,QAAI,EAAE,UAAU,SAAS,IAAI,IAAI,EAAE,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAG,OAAM,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;AAAA,EAC7F;AAGA,QAAM,aAAa,MAAM,OAAO,CAAC,MAAM,GAAG,aAAa,EAAE,UAAU,SAAS,MAAM;AAClF,MAAI,WAAW,UAAU,KAAK,QAAQ;AACpC,UAAM,WAAW,MAAM,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,SAAS,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;AACtG,UAAM,MAAM,oBAAI,IAAsB;AACtC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO;AAC1B,UAAI,IAAI,GAAG,CAAC,GAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAI,CAAC,CAAC;AACrC,UAAI,IAAI,GAAG,CAAC,GAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAI,CAAC,CAAC;AAAA,IACvC;AACA,UAAM,OAAO,IAAI,IAAI,QAAQ;AAC7B,UAAM,QAAQ,CAAC,GAAG,QAAQ;AAC1B,WAAO,MAAM,QAAQ;AACnB,iBAAW,KAAK,IAAI,IAAI,MAAM,IAAI,CAAE,KAAK,CAAC,GAAG;AAC3C,YAAI,KAAK,IAAI,CAAC,EAAG;AACjB,aAAK,IAAI,CAAC;AACV,cAAM,KAAK,CAAC;AAAA,MACd;AAAA,IACF;AACA,UAAM,OAAO,WAAW,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;AACtE,QAAI,KAAK,OAAQ,UAAS,KAAK,sBAAsB,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,mDAAmD;AAAA,EAC9I;AACA,SAAO,EAAE,IAAI,SAAS,WAAW,GAAG,UAAU,SAAS;AACzD;;;AC9CO,IAAM,YAAY,CAAC,MAAY,OAAa,WAAuB;AACxE,OAAK;AACL,QAAM,KAAK,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC;AACnC,QAAM,KAAK,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC;AACnC,QAAM,KAAK,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC;AACnC,QAAM,WAAW,KAAK,MAAM,IAAI,IAAI,EAAE;AACtC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AAAA,IACpC,WAAW,CAAC,KAAK,UAAU,CAAC,GAAG,KAAK,UAAU,CAAC,GAAG,KAAK,UAAU,CAAC,CAAC;AAAA,IACnE,QAAQ,KAAK,SAAS,QAAQ;AAAA,IAC9B,SAAS,KAAK;AAAA,IACd,GAAI,KAAK,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK;AAAA,IACrD,QAAQ,KAAK,UAAU,KAAK;AAAA,EAC9B;AACF;;;AC1DA,IAAM,UAAU,CAAC,MAAwB,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,CAAC;AAOlI,IAAM,iBAAiB,CAAC,MAAyB,YAAmE;AACzH,QAAM,WAAqB,CAAC;AAC5B,QAAM,QAAQ,IAAI,IAAI,OAAO;AAC7B,QAAM,KAAK,OAAO,MAAM,MAAM,EAAE;AAChC,MAAI,CAAC,oBAAoB,KAAK,EAAE,EAAG,UAAS,KAAK,YAAY,EAAE,sBAAsB;AACrF,QAAM,QAAQ,SAAS,EAAE;AACzB,MAAI,EAAE,OAAO,MAAM,aAAa,YAAY,OAAO,SAAS,KAAK,QAAQ,KAAK,KAAK,WAAW,GAAI,UAAS,KAAK,GAAG,KAAK,mCAAmC;AAC3J,QAAM,MAAM,OAAO,MAAM,aAAa,YAAY,KAAK,WAAW,IAAI,KAAK,WAAW;AACtF,MAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,KAAK,KAAK,KAAK,WAAW,GAAG;AACxD,aAAS,KAAK,GAAG,KAAK,yBAAyB;AAC/C,WAAO,EAAE,IAAI,OAAO,SAAS;AAAA,EAC/B;AACA,MAAI,OAAO;AACX,OAAK,KAAK,QAAQ,CAAC,GAAG,MAAM;AAC1B,QAAI,EAAE,OAAO,EAAE,MAAM,YAAY,OAAO,SAAS,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,EAAE,IAAI,MAAM,KAAM,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC,eAAe,GAAG,EAAE;AAAA,aACtJ,EAAE,IAAI,KAAM,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,uBAAuB,EAAE,CAAC,MAAM,IAAI,GAAG;AAAA,QACtF,QAAO,EAAE;AACd,eAAW,KAAK,CAAC,YAAY,UAAU,EAAY,KAAI,EAAE,CAAC,MAAM,UAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,mBAAmB,CAAC,EAAE;AAClJ,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,EAAE,SAAS,CAAC,CAAC,GAAG;AACvD,UAAI,CAAC,MAAM,IAAI,GAAG,EAAG,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,wBAAwB,GAAG,GAAG;AAClF,iBAAW,KAAK,CAAC,YAAY,UAAU,EAAY,KAAI,OAAO,CAAC,MAAM,UAAa,CAAC,QAAQ,KAAK,CAAC,CAAC,EAAG,UAAS,KAAK,GAAG,KAAK,QAAQ,CAAC,UAAU,GAAG,oBAAoB,CAAC,EAAE;AAAA,IAC1K;AAAA,EACF,CAAC;AACD,MAAI,SAAS;AACZ,GAAC,KAAK,UAAU,CAAC,GAAG,QAAQ,CAAC,GAAG,MAAM;AACrC,QAAI,EAAE,OAAO,EAAE,MAAM,YAAY,OAAO,SAAS,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,EAAE,IAAI,MAAM,KAAM,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC,eAAe,GAAG,EAAE;AAAA,aACxJ,EAAE,IAAI,OAAQ,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,oBAAoB;AAAA,QACvE,UAAS,EAAE;AAChB,QAAI,OAAO,EAAE,SAAS,YAAY,EAAE,SAAS,GAAI,UAAS,KAAK,GAAG,KAAK,UAAU,CAAC,eAAe;AACjG,QAAI;AACF,WAAK,UAAU,EAAE,QAAQ,CAAC,CAAC;AAAA,IAC7B,QAAQ;AACN,eAAS,KAAK,GAAG,KAAK,UAAU,CAAC,mBAAmB;AAAA,IACtD;AAAA,EACF,CAAC;AACD,SAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAC/C;;;AC/EO,IAAM,YAAY;AAAA,EACvB,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,MAAM;AAAA;AAAA,EAEN,MAAM;AAAA;AAAA,EAEN,OAAO;AAAA;AAAA,EAEP,QAAQ;AACV;AAWA,IAAM,eAAe,CAAC,UAAyD;AAC7E,QAAM,YAAY,MAAM,SAAS,SAAY,QAAQ,MAAM,KAAK,SAAS,MAAM;AAC/E,MAAI,MAAM,UAAU,YAAa,QAAO,YAAY,SAAS;AAC7D,SAAO,YAAY,WAAW;AAChC;AAGO,IAAM,oBAAoB,CAAC,UAA+C;AAC/E,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,IACrH,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,UAAU,CAAC,EAAE;AAAA,IACtF,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ;AAAA,IACjD,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM;AAAA,IAC/C,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,SAAS,MAAM,KAAK;AAAA,IACnE,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,SAAS,MAAM,MAAM;AAAA,IACrE,KAAK;AACH,UAAI,MAAM,UAAU,MAAO,QAAO,EAAE,MAAM,UAAU,MAAM,UAAU,MAAM;AAC1E,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,SAAS,MAAM,OAAO,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,IAC5I,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK,UAAU,YAAY,UAAU,WAAW;AAAA,IACrF,KAAK;AACH,UAAI,MAAM,UAAU,MAAO,QAAO,EAAE,MAAM,UAAU,MAAM,UAAU,MAAM;AAC1E,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,IACtH,KAAK;AACH,UAAI,MAAM,UAAU,MAAO,QAAO,EAAE,MAAM,UAAU,MAAM,UAAU,OAAO;AAC3E,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ,GAAI,MAAM,SAAS,SAAY,CAAC,IAAI,EAAE,SAAS,MAAM,KAAK,GAAI,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,IACrL,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,OAAO,MAAM,MAAM;AAAA,IAC5C,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,KAAK;AAAA,IAC9C,KAAK;AACH,UAAI,MAAM,UAAU,QAAS,QAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,UAAU,MAAM,KAAK;AAC/F,aAAO,EAAE,MAAM,UAAU,MAAM,UAAU,KAAK;AAAA,IAChD,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,SAAS,aAAa,KAAK,GAAG,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS,EAAG;AAAA,IACpJ,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ,SAAS,MAAM,KAAK,OAAO,MAAM;AAAA,IAClF;AACE,aAAO;AAAA,EACX;AACF;AAoBO,IAAM,cAAc;AAGpB,IAAM,YAAY,CAAC,MAAyB,aAAwC;AACzF,QAAM,SAAS,KAAK,WAAW,IAAI,WAAW,KAAK,WAAW;AAC9D,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,MAAM,KAAK,KAAK,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,GAAG,IAAI,IAAI,OAAO,EAAE;AAAA,IAC5D,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO,GAAG,MAAM,IAAI,OAAO,EAAE,EAAE,IAAI,CAAC;AAAA,EACnG;AACF;AAEA,IAAM,OAAiC,CAAC,GAAG,GAAG,CAAC;AAC/C,IAAM,MAAM,CAAC,MAAyB,IAAuB,aAA+C;AAAA,GACzG,KAAK,CAAC,KAAK,OAAO,GAAG,CAAC,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM;AAAA,GAClD,KAAK,CAAC,KAAK,OAAO,GAAG,CAAC,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM;AAAA,GAClD,KAAK,CAAC,KAAK,OAAO,GAAG,CAAC,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM;AACrD;AAGO,IAAM,aAAa,CAAC,MAAyB,QAA4B,SAA6F;AAC3K,QAAM,SAAS,KAAK,KAAK,IAAI,CAAC,QAAQ;AACpC,UAAM,OAAO,WAAW,SAAY,MAAM,IAAI,QAAQ,MAAM;AAC5D,WAAO,EAAE,GAAG,IAAI,GAAG,UAAU,MAAM,YAAY,MAAM,UAAU,MAAM,YAAY,KAAK;AAAA,EACxF,CAAC;AACD,QAAM,QAAQ,OAAO,CAAC;AACtB,MAAI,CAAC,SAAS,MAAM,IAAI,EAAG,QAAO,QAAQ,EAAE,GAAG,GAAG,UAAU,MAAM,UAAU,KAAK,CAAC;AAClF,QAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,MAAI,CAAC,KAAM,QAAO,EAAE,UAAU,MAAM,UAAU,KAAK;AACnD,MAAI,QAAQ,KAAK,EAAG,QAAO,EAAE,UAAU,IAAI,KAAK,UAAU,KAAK,UAAU,CAAC,GAAG,UAAU,IAAI,KAAK,UAAU,KAAK,UAAU,CAAC,EAAE;AAC5H,QAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,UAAU,CAAC,OAAO,MAAM,QAAQ,MAAM,KAAK,QAAQ,OAAO,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC;AAClH,QAAM,OAAO,OAAO,KAAK;AACzB,QAAM,KAAK,OAAO,QAAQ,CAAC;AAC3B,MAAI,CAAC,QAAQ,CAAC,GAAI,QAAO,EAAE,UAAU,MAAM,UAAU,KAAK;AAC1D,QAAM,WAAW,GAAG,IAAI,KAAK,KAAK,OAAO,KAAK,MAAM,GAAG,IAAI,KAAK,KAAK;AACrE,SAAO,EAAE,UAAU,IAAI,KAAK,UAAU,GAAG,UAAU,QAAQ,GAAG,UAAU,IAAI,KAAK,UAAU,GAAG,UAAU,QAAQ,EAAE;AACpH;AAGO,IAAM,iBAAiB,CAAC,OAAoB,YAAmE;AACpH,QAAM,QAAQ,CAAC,GAAG,OAAO;AACzB,QAAM,WAAqB,CAAC;AAC5B,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC/C,QAAI,KAAK,OAAO,IAAK,UAAS,KAAK,SAAS,GAAG,iBAAiB,KAAK,EAAE,GAAG;AAC1E,QAAI,EAAE,KAAK,WAAW,GAAI,UAAS,KAAK,SAAS,GAAG,mBAAmB;AACvE,aAAS,KAAK,GAAG,eAAe,MAAM,KAAK,EAAE,QAAQ;AACrD,eAAW,SAAS,KAAK,UAAU,CAAC,EAAG,KAAI,MAAM,SAAS,eAAe,OAAO,MAAM,MAAM,UAAU,SAAU,UAAS,KAAK,SAAS,GAAG,wCAAwC;AAAA,EACpL;AACA,SAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAC/C;AA0BO,IAAM,uBAAuB,CAAC,YAAmD;AACtF,QAAM,EAAE,OAAO,KAAK,QAAQ,IAAI;AAChC,MAAI,YAAwB,CAAC;AAE7B,QAAM,OAAO,CAAC,MAAc,YAA4E;AACtG,UAAM,OAAO,YAAY,UAAa,MAAM,GAAG,IAAI,IAAI,OAAO,EAAE,IAAI,GAAG,IAAI,IAAI,OAAO,KAAK;AAC3F,UAAM,QAAQ,MAAM,IAAI;AACxB,WAAO,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAI;AAAA,EACzC;AAEA,QAAM,SAAS,CAAC,aAA6B;AAC3C,UAAM,OAAO,KAAK,IAAI,SAAS,MAAM,SAAS,KAAK,QAAQ;AAC3D,UAAM,OAAO,WAAW,SAAS,MAAM,QAAW,IAAI;AACtD,QAAI,SAAS,KAAK,KAAK,KAAK,CAAC,QAAQ,IAAI,YAAY,IAAI,QAAQ,EAAG,KAAI,cAAc,IAAI;AAC1F,UAAM,UAAU,IAAI,IAAI,SAAS,KAAK,KAAK,QAAQ,CAAC,QAAQ,OAAO,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;AACzF,eAAW,MAAM,QAAS,KAAI,YAAY,IAAI,WAAW,SAAS,MAAM,IAAI,IAAI,CAAC;AAAA,EACnF;AAEA,QAAM,aAAa,CAAC,UAAoB,SAAuB;AAC7D,UAAM,SAAS,SAAS,KAAK,UAAU,CAAC;AACxC,WAAO,SAAS,QAAQ,OAAO,WAAW,OAAO,SAAS,KAAK,GAAG,KAAK,aAAa,OAAO,MAAM;AAC/F,YAAM,QAAQ,OAAO,SAAS,KAAK;AACnC,eAAS,SAAS;AAClB,UAAI,MAAO,WAAU,OAAO,SAAS,IAAI;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,MAAM;AACV,UAAI,KAAK,SAAS,QAAQ;AACxB,mBAAW,YAAY,WAAW;AAChC,gBAAM,UAAU,SAAS,KAAK,UAAU,CAAC,GAAG,KAAK,CAAC,UAAU,MAAM,SAAS,eAAe,MAAM,MAAM,UAAU,KAAK,KAAK;AAC1H,cAAI,UAAU,SAAS,OAAO,OAAO,EAAG,UAAS,OAAO,OAAO;AAAA,QACjE;AACA;AAAA,MACF;AACA,UAAI,KAAK,SAAS,UAAU;AAC1B,mBAAW,YAAY,WAAW;AAChC,cAAI,SAAS,SAAS,KAAK,QAAQ,CAAC,SAAS,KAAK,WAAW,GAAG,KAAK,IAAI,GAAG,EAAG;AAC/E,qBAAW,UAAU,SAAS,KAAK,QAAQ;AAC3C,mBAAS,OAAO,SAAS,KAAK;AAAA,QAChC;AACA;AAAA,MACF;AACA,YAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,OAAO;AAC1C,UAAI,CAAC,MAAO;AACZ,YAAM,OAAO,KAAK,YAAY,KAAK,WAAW,IAAI,UAAU,MAAM,MAAM,KAAK,QAAQ,IAAI,MAAM;AAC/F,YAAM,WAAW,QAAQ,iBAAiB,KAAK;AAC/C,kBAAY,UAAU,OAAO,CAAC,aAAa,SAAS,SAAS,MAAM,QAAQ,SAAS,QAAQ,CAAC;AAC7F,eAAS,QAAQ,GAAG,SAAS,KAAK,UAAU,IAAI,SAAS;AACvD,kBAAU,KAAK,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,GAAG,OAAO,QAAQ,UAAU,OAAO,EAAE,CAAC;AAAA,MACvF;AAAA,IACF;AAAA,IACA,OAAO,IAAI;AACT,iBAAW,YAAY,WAAW;AAChC,cAAM,UAAU,SAAS,QAAQ,IAAI,KAAK,SAAS,QAAQ;AAC3D,YAAI,SAAS,QAAQ,GAAG;AACtB,mBAAS,QAAQ,KAAK,IAAI,GAAG,SAAS,QAAQ,EAAE;AAChD,cAAI,SAAS,QAAQ,EAAG;AAAA,QAC1B;AACA,iBAAS,QAAQ;AACjB,mBAAW,UAAU,SAAS,IAAI;AAClC,eAAO,QAAQ;AAAA,MACjB;AACA,kBAAY,UAAU,OAAO,CAAC,aAAa,SAAS,QAAQ,KAAK,SAAS,OAAO,SAAS,KAAK,QAAQ;AAAA,IACzG;AAAA,IACA,IAAI,UAAU;AACZ,aAAO,UAAU,IAAI,CAAC,aAAa,SAAS,IAAI;AAAA,IAClD;AAAA,EACF;AACF;;;ACnPA,IAAM,cAAmC,oBAAI,IAAI,CAAC,aAAa,eAAe,WAAW,CAAC;AAE1F,IAAM,gBAAgB,CAAC,UACrB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,KAAK,OAAO,eAAe,KAAK,MAAM,OAAO;AAY3G,IAAM,YAAY,CAAuB,MAAa,UAAqC;AAChG,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAsB,EAAE,GAAI,KAAqB;AACvD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,UAAa,YAAY,IAAI,GAAG,EAAG;AACjD,UAAM,UAAU,OAAO,GAAG;AAC1B,WAAO,GAAG,IAAI,cAAc,OAAO,KAAK,cAAc,KAAK,IAAI,UAAU,SAAS,KAAK,IAAI;AAAA,EAC7F;AACA,SAAO;AACT;AAGO,IAAM,YAAY,CAAuB,SAA4B,UAA8D;AACxI,QAAM,UAAU,oBAAI,IAAoB;AACxC,aAAW,SAAS,CAAC,GAAG,SAAS,GAAI,SAAS,CAAC,CAAE,EAAG,SAAQ,IAAI,MAAM,IAAI,KAAK;AAC/E,SAAO;AACT;AAGO,IAAM,eAAe,CAAuB,SAA8B,YAA+F;AAC9K,aAAW,CAAC,IAAI,KAAK,KAAK,OAAO,QAAQ,WAAW,CAAC,CAAC,GAAG;AACvD,UAAM,QAAQ,QAAQ,IAAI,EAAE;AAC5B,QAAI,MAAO,SAAQ,IAAI,IAAI,UAAU,OAAO,KAAK,CAAC;AAAA,EACpD;AACF;AAEO,IAAM,YAAY,CAAC,SAA+B,QAA6C;AACpG,aAAW,MAAM,OAAO,CAAC,EAAG,SAAQ,OAAO,EAAE;AAC/C;;;ACgFA,IAAM,aAAa;AACnB,IAAMA,UAAS,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AAEtF,SAAS,aAAa,SAAwB,UAA0B;AACtE,QAAM,QAAQ,YAAY,QAAQ,EAAE;AACpC,MAAI,QAAQ,SAAS,UAAU;AAC7B,QAAI,CAACA,QAAO,QAAQ,GAAG,KAAK,CAACA,QAAO,QAAQ,GAAG,KAAK,QAAQ,OAAO,QAAQ,IAAK,UAAS,KAAK,GAAG,KAAK,kBAAkB;AACxH,QAAI,QAAQ,SAAS,UAAa,EAAEA,QAAO,QAAQ,IAAI,KAAK,QAAQ,OAAO,GAAI,UAAS,KAAK,GAAG,KAAK,mBAAmB;AACxH,QAAI,CAACA,QAAO,QAAQ,OAAO,KAAK,QAAQ,UAAU,QAAQ,OAAO,QAAQ,UAAU,QAAQ,IAAK,UAAS,KAAK,GAAG,KAAK,YAAY,OAAO,QAAQ,OAAO,CAAC,eAAe,QAAQ,GAAG,KAAK,QAAQ,GAAG,EAAE;AACrM;AAAA,EACF;AACA,MAAI,QAAQ,SAAS,SAAU;AAC/B,MAAI,QAAQ,QAAQ,WAAW,GAAG;AAChC,aAAS,KAAK,GAAG,KAAK,4BAA4B;AAClD;AAAA,EACF;AACA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI;AACJ,aAAW,UAAU,QAAQ,SAAS;AACpC,QAAI,KAAK,IAAI,OAAO,EAAE,EAAG,UAAS,KAAK,GAAG,KAAK,oBAAoB,OAAO,EAAE,GAAG;AAC/E,SAAK,IAAI,OAAO,EAAE;AAClB,QAAI,OAAO,UAAU,WAAc,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,IAAK,UAAS,KAAK,GAAG,KAAK,YAAY,OAAO,EAAE,sBAAsB;AAAA,aACrJ,OAAO,UAAU,UAAa,OAAO,UAAU,iBAAiB,OAAO,IAAI,OAAO,KAAK,EAAG,UAAS,KAAK,GAAG,KAAK,WAAW,OAAO,KAAK,oDAAoD;AACpM,QAAI,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,GAAI,QAAO,IAAI,OAAO,KAAK;AACpF,oBAAgB,OAAO;AAAA,EACzB;AACA,MAAI,CAAC,KAAK,IAAI,QAAQ,OAAO,EAAG,UAAS,KAAK,GAAG,KAAK,aAAa,QAAQ,OAAO,4BAA4B;AAChH;AAGO,SAAS,WAAW,UAAyC;AAClE,QAAM,WAAqB,CAAC;AAC5B,QAAM,MAAM,oBAAI,IAAY;AAC5B,QAAM,QAAQ,CAAC,MAAc,OAAqB;AAChD,QAAI,CAAC,WAAW,KAAK,EAAE,EAAG,UAAS,KAAK,GAAG,IAAI,QAAQ,EAAE,sBAAsB;AAC/E,QAAI,IAAI,IAAI,EAAE,EAAG,UAAS,KAAK,iBAAiB,EAAE,uCAAuC;AACzF,QAAI,IAAI,EAAE;AAAA,EACZ;AACA,aAAW,WAAW,SAAS,YAAY,CAAC,GAAG;AAC7C,UAAM,WAAW,QAAQ,EAAE;AAC3B,iBAAa,SAAS,QAAQ;AAAA,EAChC;AACA,aAAW,SAAS,SAAS,UAAU,CAAC,EAAG,OAAM,SAAS,MAAM,EAAE;AAClE,aAAW,UAAU,SAAS,WAAW,CAAC,GAAG;AAC3C,UAAM,UAAU,OAAO,EAAE;AACzB,QAAI,OAAO,QAAQ,UAAa,EAAEA,QAAO,OAAO,GAAG,KAAK,OAAO,MAAM,GAAI,UAAS,KAAK,WAAW,OAAO,EAAE,mBAAmB;AAAA,EAChI;AACA,SAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAC/C;;;ACxJO,IAAM,sBAAwC;AAAA,EACnD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,SAAS;AACX;AAYO,SAAS,qBAAqB,UAAqC,CAAC,GAAmB;AAC5F,MAAI,QAA0B,EAAE,GAAG,qBAAqB,GAAG,QAAQ;AACnE,QAAM,YAAY,oBAAI,IAA2E;AACjG,SAAO;AAAA,IACL,KAAK,MAAM;AAAA,IACX,IAAI,OAAO;AACT,YAAM,UAAW,OAAO,KAAK,KAAK,EAAuB,OAAO,CAAC,QAAQ,MAAM,GAAG,MAAM,UAAa,MAAM,GAAG,MAAM,MAAM,GAAG,CAAC;AAC9H,UAAI,QAAQ,WAAW,EAAG;AAC1B,YAAM,OAAO,EAAE,GAAG,MAAM;AACxB,iBAAW,OAAO,QAAS,MAAK,GAAG,IAAI,MAAM,GAAG;AAChD,cAAQ;AACR,iBAAW,YAAY,CAAC,GAAG,SAAS,EAAG,UAAS,OAAO,OAAO;AAAA,IAChE;AAAA,IACA,GAAG,UAAU;AACX,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,IAC7C;AAAA,EACF;AACF;","names":["finite"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adatechnology/game-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "The contracts every game module depends on, and nothing else: destruction (parts, hits, events, DestructibleImpl), weapons (shots), fluids (containers, item uses), item animations and the TestableModule a module hands the test room. Types plus tiny checkers, no runtime dependencies.",
|
|
6
6
|
"keywords": [
|