@adatechnology/game-characters 0.0.0-stage → 0.1.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.
@@ -0,0 +1,1103 @@
1
+ import { Vec3, SegmentKind, BodyRegion, BodySlot, Hit, Quat, DestructibleSpec, ObjectState, DestructibleImpl, DestructionEvent } from '@adatechnology/game-contracts';
2
+
3
+ /** The 17 joints of a body: one per segment, the point the segment hangs from. */
4
+ type JointId = 'hip' | 'spine' | 'neck' | 'head' | 'jaw' | 'shoulderL' | 'shoulderR' | 'elbowL' | 'elbowR' | 'wristL' | 'wristR' | 'hipL' | 'hipR' | 'kneeL' | 'kneeR' | 'ankleL' | 'ankleR';
5
+ declare const JOINT_IDS: readonly JointId[];
6
+ /**
7
+ * Proportions of a body: joint positions in metres for a body of `heightMetres`, standing, feet on y = 0, facing +z, left side on +x.
8
+ * `segmentRadius` is the radius of the capsule a segment is hit through.
9
+ */
10
+ type BodySpec = {
11
+ readonly id: string;
12
+ readonly heightMetres: number;
13
+ readonly joints: Readonly<Record<JointId, Vec3>>;
14
+ readonly segmentRadius: Readonly<Record<SegmentKind, number>>;
15
+ };
16
+ /** The Elenco style guide body (1.80 m, shoulders 0.61 m at 1.475 m, head about 1/6 of the height): the default. */
17
+ declare const GUIDE: BodySpec;
18
+ /**
19
+ * The game's crowd body (G 65336 `J`, shoulders ±0.24 m), kept for the goldens of the instanced path. `J` has no spine, head, jaw, wrist or
20
+ * ankle: spine and head sit on the hip and neck bones (bones 0 and 1 of the game), the wrist and ankle one forearm and one shin below.
21
+ */
22
+ declare const TEAM_GAME: BodySpec;
23
+ /** A body spec from `base` (default `GUIDE`) with the given fields replaced; `joints` and `segmentRadius` merge key by key. */
24
+ declare const createBodySpec: (patch?: Partial<BodySpec>, base?: BodySpec) => BodySpec;
25
+
26
+ type SegmentSide = 'L' | 'R';
27
+ type LimbKind = 'upperArm' | 'forearm' | 'hand' | 'thigh' | 'shin' | 'foot';
28
+ type SegmentId = 'pelvis' | 'torso' | 'neck' | 'head' | 'jaw' | `${LimbKind}${SegmentSide}`;
29
+ /** One segment of the tree: the joint it hangs from, its parent, the crowd region it belongs to and the item slots that cover it. */
30
+ type SegmentNode = {
31
+ readonly id: SegmentId;
32
+ readonly kind: SegmentKind;
33
+ readonly side?: SegmentSide;
34
+ readonly joint: JointId;
35
+ readonly parent?: SegmentId;
36
+ readonly region: BodyRegion;
37
+ readonly slots: readonly BodySlot[];
38
+ };
39
+ /** Numbers of a segment kind (the Arsenal table, A4): hit points, damage multiplier and bleed rate. */
40
+ type SegmentNumbers = {
41
+ readonly hitPoints: number;
42
+ readonly multiplier: number;
43
+ readonly bleed: number;
44
+ };
45
+ /** The 17 segments (11 kinds, limbs on both sides), parents before children. Neck and jaw have no item slot: a helmet does not cover them. */
46
+ declare const SEGMENT_TREE: readonly SegmentNode[];
47
+ declare const SEGMENT_IDS: readonly SegmentId[];
48
+ declare const segmentNode: (id: SegmentId) => SegmentNode;
49
+ declare const isSegmentId: (value: string) => value is SegmentId;
50
+ /** Every segment that hangs below `id` (children, grandchildren...), in tree order: what falls with it when it is severed. */
51
+ declare const segmentsBelow: (id: SegmentId) => SegmentId[];
52
+ /** The Arsenal numbers per segment kind (hit points, `MULT`, `BLEED`). */
53
+ declare const SEGMENT_NUMBERS: Readonly<Record<SegmentKind, SegmentNumbers>>;
54
+
55
+ type BallisticClass = 'pistol' | 'rifle' | 'buckshot';
56
+ /** A `Hit` plus what a body may also use. A plain `Hit` works: every extra field is optional. */
57
+ type BodyHit = Hit & {
58
+ /** Damage points the caller already resolved (a game's own table): the points step is skipped. */
59
+ readonly points?: number;
60
+ /** snake_case calibre id (`9x19`): looks the calibre up in the kit's calibre table. */
61
+ readonly caliberId?: string;
62
+ /** Metres from the shooter (the skull range of a shotgun, the head blown off at close range). */
63
+ readonly distance?: number;
64
+ /** Projectiles in the load (a shotgun: 9). */
65
+ readonly pellets?: number;
66
+ /** `false`: the caller resolved the part without a surface point, so no wound opens at `point` (default true). */
67
+ readonly woundAtPoint?: boolean;
68
+ };
69
+ /** A calibre as the body model sees it. Every field is optional; a game's own table (`team-game`) fills them all. */
70
+ type CaliberEntry = {
71
+ readonly id: string;
72
+ /** Damage points of a fresh hit (overrides `points = 3.5·E^0.37`). */
73
+ readonly points?: number;
74
+ /** Penetration points (the `team-game` model). */
75
+ readonly penetration?: number;
76
+ readonly class?: BallisticClass;
77
+ /** Metres within which a head hit is lethal; 1 means always (the game's `cranio`). */
78
+ readonly skullRange?: number;
79
+ readonly pellets?: number;
80
+ /** Metres of full damage; at twice the range the damage is halved (the `team-game` model). */
81
+ readonly effectiveRangeMetres?: number;
82
+ };
83
+ /** A protection item on the body (`ItemDef.protection` of `game-items` maps onto it). */
84
+ type ProtectionLayer = {
85
+ readonly id: string;
86
+ readonly slots: readonly BodySlot[];
87
+ /** Against projectiles: `resistance` in penetration units; a stopped projectile does `blunt` of its damage, one that goes through `pass` (default `blunt`). */
88
+ readonly ballistic?: {
89
+ readonly resistance: number;
90
+ readonly blunt: number;
91
+ readonly pass?: number;
92
+ readonly consumable?: boolean;
93
+ };
94
+ /** Factor on fire damage (a turnout coat: 0.25). */
95
+ readonly fire?: number;
96
+ /** Factor on arm and leg damage (a turnout coat: 0.8). */
97
+ readonly limbs?: number;
98
+ /** 0..1 against a bite and a scratch (the layered T3 formula). */
99
+ readonly bite?: number;
100
+ readonly scratch?: number;
101
+ };
102
+ /** What a projectile is, for the protection and the skull: the force P of the `physical` model, the class and range of a calibre table. */
103
+ type ProjectileInfo = {
104
+ readonly force: number;
105
+ readonly class: BallisticClass;
106
+ readonly skullRange: number;
107
+ readonly pellets: number;
108
+ readonly distance: number;
109
+ };
110
+ /** How hard a projectile pushes and what stops it. `physical` (P = √(E·m/A)) is the default; `team-game` replays the game's points. */
111
+ type PenetrationModel = {
112
+ readonly id: string;
113
+ /** A protection layer stops the projectile (the projectile then does the layer's `blunt` share). */
114
+ stops(layer: ProtectionLayer, projectile: ProjectileInfo): boolean;
115
+ /** A head hit is lethal when the skull is pierced and the points reach the head-kill threshold. */
116
+ piercesSkull(projectile: ProjectileInfo, headResistance: number): boolean;
117
+ };
118
+ /** Numbers of the segment pipeline (the defaults are the design's, from the game, the Elenco and the Arsenal). */
119
+ type SeverityRules = {
120
+ readonly severAtCut: number;
121
+ readonly severAtDamageFraction: number;
122
+ readonly destroyAtDamageMultiple: number;
123
+ readonly limpAtHpFraction: number;
124
+ readonly splitAtDamage: number;
125
+ readonly headKillDamage: number;
126
+ readonly jawChance: number;
127
+ readonly jawDamage: number;
128
+ readonly headBitDamage: number;
129
+ readonly headBitShotgunMetres: number;
130
+ readonly stunAtPoints: number;
131
+ /** Share of a limb hit that also comes off vitality (Arsenal 0.3, game 0.5 for the crowd regions). */
132
+ readonly limbVitalityShare: number;
133
+ readonly regionLimbVitalityShare: number;
134
+ };
135
+ type BlastRules = {
136
+ /** Least points a segment must take from a blast to be hit at all. */
137
+ readonly minimumPoints: number;
138
+ /** A blast this strong on the head blows it off. */
139
+ readonly headDestroyPoints: number;
140
+ /** A blast this strong on a segment destroys it when it reaches 0. */
141
+ readonly bigPoints: number;
142
+ };
143
+ type DeathRule = 'vitality-or-head' | 'head-only';
144
+ type SegmentRuleId = 'kit' | 'arsenal' | 'elenco';
145
+ type DamageRules = {
146
+ readonly deathRule: DeathRule;
147
+ /** The segment rule: `kit` (default), or a team page replayed exactly (`arsenal`, `elenco`). */
148
+ readonly segmentRule: SegmentRuleId;
149
+ readonly severity: SeverityRules;
150
+ readonly blast: BlastRules;
151
+ /** Default `cut` by damage type and projectile class. */
152
+ readonly defaultCut: {
153
+ readonly slash: number;
154
+ readonly blunt: number;
155
+ readonly explosive: number;
156
+ readonly ballistic: Readonly<Record<BallisticClass, number>>;
157
+ };
158
+ /** `points = coefficient · E^exponent` (E in joules). */
159
+ readonly points: {
160
+ readonly coefficient: number;
161
+ readonly exponent: number;
162
+ };
163
+ /** Random spread of the points of a blow: ±spread (the Elenco: 0.15). 0 = no draw. */
164
+ readonly spread: number;
165
+ /** Per segment kind: hit points, damage multiplier and bleed rate. */
166
+ readonly segments: Readonly<Record<SegmentKind, {
167
+ readonly hitPoints: number;
168
+ readonly multiplier: number;
169
+ readonly bleed: number;
170
+ }>>;
171
+ /** Fire on a body: points per second of exposure. */
172
+ readonly burnPointsPerSecond: number;
173
+ };
174
+ /** What the last hit did, for a HUD (hit marker, headshot) and for tests. */
175
+ type HitReport = {
176
+ readonly part?: string;
177
+ readonly points: number;
178
+ readonly killed: boolean;
179
+ readonly headshot: boolean;
180
+ /** The projectile went through (the `physical` model): `exitEnergy` is what is left of it. */
181
+ readonly through: boolean;
182
+ readonly exitEnergy: number;
183
+ /** A protection layer absorbed the hit completely (a helmet). */
184
+ readonly absorbedBy?: string;
185
+ };
186
+
187
+ /** The kit's rules: Arsenal numbers per segment (A4), the design's severity, the game's head rule, death by vitality or head. */
188
+ declare const DEFAULT_DAMAGE_RULES: DamageRules;
189
+ /** The Galeria 3D do Arsenal, replayed: its `damage`, `sever`, `explodeSeg`, `splitTorso`, `legLost` and `grenadeAt`. */
190
+ declare const ARSENAL_RULES: DamageRules;
191
+ /** The Elenco, replayed: its own hit points, no multiplier, ±15 % per blow and its cut rule (`hit`, `sever`, `breakBone`); a torso at 0 kills (a human). */
192
+ declare const ELENCO_RULES: DamageRules;
193
+ declare const DAMAGE_PRESETS: {
194
+ readonly kit: DamageRules;
195
+ readonly arsenal: DamageRules;
196
+ readonly elenco: DamageRules;
197
+ };
198
+ /** The protection presets that reproduce the game's `ROUPAS` (tools/characters-catalog/decisions.json `protectionPresets`). */
199
+ declare const PROTECTION_PRESETS: readonly ProtectionLayer[];
200
+ /** The game's calibre table (`CALIBRES`) under the kit's calibre ids, for the `team-game` model. */
201
+ declare const TEAM_GAME_CALIBERS: readonly CaliberEntry[];
202
+ /** The game's calibre keys (`9x19`, `12ga`, `762x39`, `308`) to the kit's calibre ids. */
203
+ declare const TEAM_GAME_CALIBER_KEYS: Readonly<Record<string, string>>;
204
+
205
+ type AgeGroup = 'child' | 'young' | 'adult' | 'elder';
206
+ /** `segments`: the 17 segments (near, rich path); `regions`: the 6 crowd regions of the game (far, instanced path). */
207
+ type Detail = 'segments' | 'regions';
208
+ /** `ok` untouched, `wounded` hit, `broken` bone broken (still attached), `severed` cut off at its joint, `destroyed` blown to pieces. */
209
+ type SegmentStatus = 'ok' | 'wounded' | 'broken' | 'severed' | 'destroyed';
210
+ type SegmentState = {
211
+ readonly id: SegmentId;
212
+ readonly kind: SegmentKind;
213
+ readonly side?: SegmentSide;
214
+ readonly region: BodyRegion;
215
+ hp: number;
216
+ readonly max: number;
217
+ status: SegmentStatus;
218
+ /** Still on the body (false once it or a segment above it is severed or destroyed). */
219
+ attached: boolean;
220
+ };
221
+ /** Damage multipliers of a body type per body part (a zombie type's `mult`); 1 by default. */
222
+ type BodyMultipliers = {
223
+ readonly head: number;
224
+ readonly torso: number;
225
+ readonly arms: number;
226
+ readonly legs: number;
227
+ };
228
+ /** What makes one character. Everything but `seed` and `kind` is optional; a preset (`kind`, or `preset`) fills what the spec leaves out. */
229
+ type CharacterSpec = {
230
+ readonly seed: number;
231
+ /** `person`, or a preset id (a profession, an undead type...). */
232
+ readonly kind: string;
233
+ readonly id?: string;
234
+ /** Preset to start from (default: the preset whose id is `kind`, when there is one). */
235
+ readonly preset?: string;
236
+ readonly sex?: 'male' | 'female';
237
+ readonly age?: AgeGroup;
238
+ /** Height in metres (default the body spec's). */
239
+ readonly height?: number;
240
+ /** Width factor (default 1). */
241
+ readonly build?: number;
242
+ /** Outfit id (content packs define them). */
243
+ readonly outfit?: string;
244
+ /** Ids of the protection layers worn (`vest_police`, `helmet_combat`...). */
245
+ readonly protection?: readonly string[];
246
+ /** Free data for content packs (an undead type, a profession...). The engine never reads it. */
247
+ readonly traits?: Readonly<Record<string, unknown>>;
248
+ /** Starting vitality (default 100). */
249
+ readonly vitality?: number;
250
+ readonly multipliers?: Partial<BodyMultipliers>;
251
+ /** Extra damage taken by arms and legs (the age of a zombie: `membro`); default 1. */
252
+ readonly limbFactor?: number;
253
+ /** Scales every bleed rate (the age of a zombie: `sangue`); default 1. */
254
+ readonly bloodFactor?: number;
255
+ /** How many bodies the torso counts as for penetration (a brute: 2); default 1. */
256
+ readonly bodyCount?: number;
257
+ /** Seconds a heavy hit stuns (default 0.45). */
258
+ readonly stunSeconds?: number;
259
+ readonly detail?: Detail;
260
+ /** Starts crawling (a crawler). */
261
+ readonly crawling?: boolean;
262
+ /**
263
+ * A random stream to draw from instead of the per-hit stream (`seed ^ hit counter`). For replaying a recorded game: the goldens pass the
264
+ * team's own stream here.
265
+ */
266
+ readonly random?: () => number;
267
+ };
268
+ /** A named starting point for specs (a profession, a force, an undead type). */
269
+ type CharacterPreset = {
270
+ readonly id: string;
271
+ } & Partial<Omit<CharacterSpec, 'seed' | 'id' | 'preset' | 'random'>>;
272
+ /** Content added by a pack (people, uniformed, undead...). */
273
+ type CharacterPack = {
274
+ readonly id: string;
275
+ readonly presets?: readonly CharacterPreset[];
276
+ /** Protection layers by id (vests, helmets, coats). */
277
+ readonly protection?: readonly ProtectionLayer[];
278
+ /** Calibre entries by id (a game's own points table). */
279
+ readonly calibers?: readonly CaliberEntry[];
280
+ };
281
+ /** Damage rules: a preset (`kit` by default, or a team page replayed: `arsenal`, `elenco`) with fields replaced. */
282
+ type DamageOptions = Partial<Omit<DamageRules, 'severity' | 'blast' | 'segments'>> & {
283
+ readonly preset?: keyof typeof DAMAGE_PRESETS;
284
+ readonly severity?: Partial<SeverityRules>;
285
+ readonly blast?: Partial<BlastRules>;
286
+ readonly segments?: Partial<DamageRules['segments']>;
287
+ };
288
+ type CorpseOptions = {
289
+ readonly max: number;
290
+ readonly seconds: number;
291
+ };
292
+ type CharactersOptions = {
293
+ readonly packs?: readonly CharacterPack[];
294
+ /** Fields to change in presets, by id. */
295
+ readonly patch?: Readonly<Record<string, Partial<CharacterPreset>>>;
296
+ /** Preset ids to remove. */
297
+ readonly remove?: readonly string[];
298
+ /** Proportions (default `GUIDE`); `joints` and `segmentRadius` merge key by key. */
299
+ readonly body?: Partial<BodySpec>;
300
+ /** Corpse cap and lifetime (default 400 bodies, 900 s). */
301
+ readonly corpses?: Partial<CorpseOptions>;
302
+ readonly damage?: DamageOptions;
303
+ /** `physical` (P = √(E·m/A), default), `team-game` (the game's points; for the goldens) or a model of your own. */
304
+ readonly penetration?: PenetrationModel | 'physical' | 'team-game';
305
+ /** Protection layers added to (or replacing, by id) the presets `vest_police`, `vest_military`, `helmet_combat`, `turnout_gear`. */
306
+ readonly protection?: readonly ProtectionLayer[];
307
+ /** Calibre entries (points, class, skull range...) by calibre id. */
308
+ readonly calibers?: readonly CaliberEntry[];
309
+ };
310
+
311
+ type CorpseEntry<TData> = {
312
+ /** Seconds since it fell. */
313
+ age: number;
314
+ readonly data: TData;
315
+ };
316
+ type CorpseLedger<TData> = {
317
+ readonly entries: readonly CorpseEntry<TData>[];
318
+ readonly count: number;
319
+ /** Adds a corpse; when the ledger is full the oldest one is replaced. Returns the replaced data, if any. */
320
+ add(data: TData): TData | undefined;
321
+ /** Ages every corpse by `dt` seconds and removes the ones past their life (swap with the last, like the game). Returns the removed data. */
322
+ update(dt: number): TData[];
323
+ clear(): void;
324
+ };
325
+ /** The bodies left on the ground: capped (400) and gone after a lifetime (900 s); generic over what each one carries. */
326
+ declare const createCorpseLedger: <TData>(options: CorpseOptions) => CorpseLedger<TData>;
327
+
328
+ /** The regions that keep their own hit points in the crowd model (the torso has none: torso hits go to vitality). */
329
+ type LimbRegion = Exclude<BodyRegion, 'torso'>;
330
+ declare const LIMB_REGIONS: readonly LimbRegion[];
331
+ /** The game's region numbers (`SEG`): hit points of the head, of an arm and of a leg. */
332
+ declare const REGION_HIT_POINTS: Readonly<Record<LimbRegion, number>>;
333
+ /** The segments that make up each region, and the one that names it in events (the first). */
334
+ declare const REGION_SEGMENTS: Readonly<Record<BodyRegion, readonly SegmentId[]>>;
335
+ declare const BODY_REGIONS: readonly BodyRegion[];
336
+ declare const isBodyRegion: (value: string) => value is BodyRegion;
337
+
338
+ /** The bones of the game's instanced body (G 65336): 0 torso and hip, 1 head, 2/3 arms, 4/5 thighs, 6 eyes, 7/8 forearms, 9/10 shins, 11 jaw. */
339
+ declare const GAME_BONES: {
340
+ readonly torso: 0;
341
+ readonly head: 1;
342
+ readonly upperArmL: 2;
343
+ readonly upperArmR: 3;
344
+ readonly thighL: 4;
345
+ readonly thighR: 5;
346
+ readonly eyes: 6;
347
+ readonly forearmL: 7;
348
+ readonly forearmR: 8;
349
+ readonly shinL: 9;
350
+ readonly shinR: 10;
351
+ readonly jaw: 11;
352
+ };
353
+ declare const JOINT_PARENTS: Readonly<Record<JointId, JointId | undefined>>;
354
+ /** The joints of one body, scaled to its height and build. */
355
+ type Rig = {
356
+ readonly joints: Readonly<Record<JointId, Vec3>>;
357
+ readonly parents: Readonly<Record<JointId, JointId | undefined>>;
358
+ readonly gameBones: typeof GAME_BONES;
359
+ /** Height in metres of this body. */
360
+ readonly height: number;
361
+ };
362
+ type CreateRigParams = {
363
+ readonly body: BodySpec;
364
+ readonly height: number;
365
+ readonly build: number;
366
+ };
367
+ /** Joints of `body` scaled to `height` metres (vertical and depth) and to `build` (width). */
368
+ declare const createRig: ({ body, height, build }: CreateRigParams) => Rig;
369
+
370
+ /** A spec with every default filled in. */
371
+ type ResolvedCharacterSpec = CharacterSpec & {
372
+ readonly id: string;
373
+ readonly height: number;
374
+ readonly build: number;
375
+ readonly vitality: number;
376
+ readonly multipliers: BodyMultipliers;
377
+ readonly limbFactor: number;
378
+ readonly bloodFactor: number;
379
+ readonly bodyCount: number;
380
+ readonly stunSeconds: number;
381
+ readonly protection: readonly string[];
382
+ readonly detail: Detail;
383
+ };
384
+ type Character = {
385
+ readonly id: string;
386
+ readonly spec: ResolvedCharacterSpec;
387
+ readonly rig: Rig;
388
+ /** Live view of the 17 segments. */
389
+ readonly segments: readonly SegmentState[];
390
+ readonly detail: Detail;
391
+ /** Hit points of the 5 crowd regions (head, arms, legs). */
392
+ regionHitPoints(): Readonly<Record<LimbRegion, number>>;
393
+ /** Swaps the detail level: segments to regions sums each region (lossless); regions to segments spreads the region's share (lossy). */
394
+ setDetail(detail: Detail): void;
395
+ setTransform(position: Vec3, rotation?: Quat): void;
396
+ describe(): DestructibleSpec;
397
+ state(): ObjectState;
398
+ reset(): void;
399
+ };
400
+
401
+ /** A bleeding wound: `rate` in drops per second (the Arsenal's unit) fading to 0 over `duration` seconds; `spurt` pulses like an artery. */
402
+ type Bleeder = {
403
+ readonly part: SegmentId;
404
+ readonly rate: number;
405
+ readonly duration: number;
406
+ readonly spurt: boolean;
407
+ age: number;
408
+ /** Whole seconds of this bleeder already reported as `leak` events. */
409
+ reported: number;
410
+ };
411
+ /** Volume of one drop (a 4 mm drop of the Arsenal's blood): `leak` events carry litres per second. */
412
+ declare const BLOOD_LITERS_PER_DROP = 0.00027;
413
+ declare const bleedRateAt: (bleeder: Bleeder) => number;
414
+
415
+ /** A character with the damage model: a `DestructibleImpl` a destruction world can host next to its own objects. */
416
+ type DamageCharacter = Character & DestructibleImpl & {
417
+ readonly alive: boolean;
418
+ /** The sides that limp, in the order they started. */
419
+ readonly limping: readonly SegmentSide[];
420
+ /** Seconds of stagger left from a heavy hit. */
421
+ readonly stunSeconds: number;
422
+ /** What the last `hit` did (points, kill, headshot, through). */
423
+ readonly lastHit?: HitReport;
424
+ /** The bleeding wounds, oldest first. */
425
+ bleeders(): readonly Readonly<Bleeder>[];
426
+ /** 0..1 protection of the layers over a segment against a bite or a scratch (the layered T3 formula, capped at 90 %). */
427
+ protectionAgainst(attack: 'bite' | 'scratch', part: SegmentId): number;
428
+ /** Ids of the protection layers worn and not used up (a helmet that took its shot is gone). */
429
+ intactProtection(): readonly string[];
430
+ /** Penetration resistance R of the path through a part (a segment or a region id), with the protection worn over it. */
431
+ resistanceOf(part: string): number;
432
+ hit(hit: Hit | BodyHit): DestructionEvent[];
433
+ /** Ages bleeders (a `leak` per second each) and the stun. */
434
+ update(dt: number): DestructionEvent[];
435
+ serialize(): unknown;
436
+ load(data: unknown): void;
437
+ };
438
+
439
+ /** One body in the line of fire, `distance` metres from the muzzle. */
440
+ type LineTarget = {
441
+ readonly character: DamageCharacter;
442
+ readonly part: string;
443
+ readonly distance: number;
444
+ readonly point?: Vec3;
445
+ };
446
+ /** What the line needs from a shot (a `Shot` of game-weapons fits). */
447
+ type LineShot = {
448
+ readonly energyAt: (distance: number) => number;
449
+ /** Projectile grams. */
450
+ readonly mass: number;
451
+ /** Diameter in mm. */
452
+ readonly caliber: number;
453
+ readonly caliberId?: string;
454
+ readonly source?: string;
455
+ };
456
+ type LineHit = {
457
+ readonly index: number;
458
+ readonly points: number;
459
+ readonly energy: number;
460
+ readonly killed: boolean;
461
+ readonly headshot: boolean;
462
+ readonly events: DestructionEvent[];
463
+ };
464
+ type LineResult = {
465
+ readonly hits: LineHit[]; /** 1-based index of the last body hit (0: none). */
466
+ readonly stoppedAt: number;
467
+ };
468
+ type PenetrateLineParams = {
469
+ readonly model: 'physical' | 'team-game';
470
+ readonly shot: LineShot;
471
+ readonly targets: readonly LineTarget[];
472
+ readonly calibers: ReadonlyMap<string, CaliberEntry>;
473
+ };
474
+ /** A shot through a line of bodies, nearest first. Each body takes the hit through its own damage model. */
475
+ declare const penetrateLine: (params: PenetrateLineParams) => LineResult;
476
+
477
+ declare const DEFAULT_CORPSES: CorpseOptions;
478
+ /** The base preset every spec can start from: an unnamed adult person. */
479
+ declare const PERSON_PRESET: CharacterPreset;
480
+ type CharacterKit = {
481
+ readonly body: BodySpec;
482
+ readonly presets: ReadonlyMap<string, CharacterPreset>;
483
+ readonly corpses: CorpseOptions;
484
+ readonly damage: DamageRules;
485
+ readonly penetration: PenetrationModel;
486
+ readonly protection: ReadonlyMap<string, ProtectionLayer>;
487
+ readonly calibers: ReadonlyMap<string, CaliberEntry>;
488
+ /** A new character: the preset (`spec.preset`, else the one named `spec.kind`) under the spec. */
489
+ create(spec: CharacterSpec): DamageCharacter;
490
+ /** The bodies left on the ground, capped and timed by the kit's `corpses` option. */
491
+ createCorpses<TData>(): CorpseLedger<TData>;
492
+ /** A shot through a line of bodies with the kit's penetration model and calibre table. */
493
+ penetrateLine(shot: LineShot, targets: readonly LineTarget[]): LineResult;
494
+ };
495
+ /** Fills a spec from its preset: what the spec says wins; `multipliers` merge field by field. */
496
+ declare const applyPreset: (spec: CharacterSpec, presets: ReadonlyMap<string, CharacterPreset>) => CharacterSpec;
497
+ /** The rules of a preset (`kit` by default) with the given fields replaced; `severity`, `blast` and `segments` merge key by key. */
498
+ declare const resolveDamageRules: (options?: DamageOptions) => DamageRules;
499
+ declare const createCharacters: (options?: CharactersOptions) => CharacterKit;
500
+
501
+ /** The material id the parts of a body carry (a destruction world without it skips its own material rules: the body is a `DestructibleImpl`). */
502
+ declare const BODY_MATERIAL = "flesh";
503
+ type DescribeBodyParams = {
504
+ readonly rig: Rig;
505
+ readonly body: BodySpec;
506
+ readonly build: number;
507
+ readonly name?: string;
508
+ };
509
+ /** The body as destruction parts (one per segment, in the body's local space) and the joints between them. */
510
+ declare const describeBody: ({ rig, body, build, name }: DescribeBodyParams) => DestructibleSpec;
511
+
512
+ type Entry = {
513
+ readonly id: string;
514
+ };
515
+ /** The shipped entries with the ones from `extra` added or, when the id exists, replaced. */
516
+ declare const mergeById: <TEntry extends Entry>(shipped: readonly TEntry[], extra: readonly TEntry[] | undefined) => Map<string, TEntry>;
517
+ /** Applies `patch` by id (a shallow merge); a patch for an id that does not exist is ignored. */
518
+ declare const applyPatches: <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, Partial<TEntry>>> | undefined) => void;
519
+ declare const removeIds: (entries: Map<string, unknown>, ids: readonly string[] | undefined) => void;
520
+
521
+ /** Cycles per second at `speed` m/s (the game's `walkCad`). */
522
+ declare const walkCadence: (speed: number) => number;
523
+ /** Hip swing in radians at `speed` m/s for a body `height` times the base height: the stride that makes the foot meet the ground (the game's `walkGait`). */
524
+ declare const walkGait: (speed: number, height?: number) => number;
525
+ type StrideParams = {
526
+ readonly speed: number;
527
+ readonly height?: number;
528
+ /** Default true: cadence and swing come from the speed, so the feet do not slide. False keeps the profile's own numbers (the Bestiário preset). */
529
+ readonly strideFromSpeed?: boolean;
530
+ };
531
+ type Stride = {
532
+ /** Cycles per second. */
533
+ readonly frequency: number;
534
+ /** Thigh swing, radians. */
535
+ readonly swing: number;
536
+ /** Knee bend, radians. */
537
+ readonly kneeBend: number;
538
+ };
539
+ /** The gait numbers for a speed: from the ground-matching stride, or `fallback` (the profile) when `strideFromSpeed` is false. */
540
+ declare const strideFor: ({ speed, height, strideFromSpeed }: StrideParams, fallback: Stride) => Stride;
541
+
542
+ var cascade = {
543
+ hip: 16,
544
+ spine: 11,
545
+ shoulder: 9,
546
+ elbow: 7,
547
+ leg: 14,
548
+ knee: 11,
549
+ head: 6
550
+ };
551
+ var moves = {
552
+ idle: {
553
+ gait: 0,
554
+ freq: 0,
555
+ amp: 0,
556
+ knee: 0,
557
+ bob: 0,
558
+ lean: 0,
559
+ arm: 0,
560
+ elb: 0.12,
561
+ crouch: 0,
562
+ aim: 0,
563
+ turn: 0,
564
+ grip: 0.35
565
+ },
566
+ walk: {
567
+ gait: 1,
568
+ freq: 5.6,
569
+ amp: 0.42,
570
+ knee: 0.85,
571
+ bob: 0.035,
572
+ lean: 0.05,
573
+ arm: 0.42,
574
+ elb: 0.3,
575
+ crouch: 0,
576
+ aim: 0,
577
+ turn: 0,
578
+ grip: 0.4
579
+ },
580
+ run: {
581
+ gait: 1,
582
+ freq: 9.2,
583
+ amp: 0.72,
584
+ knee: 1.5,
585
+ bob: 0.06,
586
+ lean: 0.3,
587
+ arm: 0.85,
588
+ elb: 1.35,
589
+ crouch: 0,
590
+ aim: 0,
591
+ turn: 0,
592
+ grip: 1.1
593
+ },
594
+ crouch: {
595
+ gait: 1,
596
+ freq: 3.8,
597
+ amp: 0.3,
598
+ knee: 0.6,
599
+ bob: 0.02,
600
+ lean: 0.42,
601
+ arm: 0.15,
602
+ elb: 0.7,
603
+ crouch: 1,
604
+ aim: 0,
605
+ turn: 0,
606
+ grip: 0.8
607
+ },
608
+ aim: {
609
+ gait: 0,
610
+ freq: 0,
611
+ amp: 0,
612
+ knee: 0,
613
+ bob: 0,
614
+ lean: 0.06,
615
+ arm: 0,
616
+ elb: 0.2,
617
+ crouch: 0.25,
618
+ aim: 1,
619
+ turn: 0,
620
+ grip: 1.2
621
+ },
622
+ turn: {
623
+ gait: 0.5,
624
+ freq: 4.2,
625
+ amp: 0.14,
626
+ knee: 0.7,
627
+ bob: 0.012,
628
+ lean: 0.03,
629
+ arm: 0.12,
630
+ elb: 0.25,
631
+ crouch: 0,
632
+ aim: 0,
633
+ turn: 1,
634
+ grip: 0.4
635
+ }
636
+ };
637
+ var gait = {
638
+ cascade: cascade,
639
+ moves: moves};
640
+
641
+ /** How a body type walks: offsets over its rest pose (the Bestiário's `GAIT`). `f` cycles per second, `a` thigh swing, `k` knee bend, `arm` arm swing. */
642
+ type GaitProfile = {
643
+ readonly f: number;
644
+ readonly a?: number;
645
+ readonly k?: number;
646
+ readonly arm?: number;
647
+ readonly bob?: number;
648
+ readonly sway?: number;
649
+ readonly drag?: boolean;
650
+ readonly crawl?: boolean;
651
+ readonly idle?: boolean;
652
+ readonly eat?: boolean;
653
+ readonly leap?: boolean;
654
+ readonly listen?: boolean;
655
+ readonly stalk?: boolean;
656
+ readonly sniff?: boolean;
657
+ };
658
+ type JointAngles = {
659
+ readonly shoulder: number;
660
+ readonly elbow: number;
661
+ };
662
+ type LegAngles = {
663
+ readonly hip: number;
664
+ readonly knee: number;
665
+ readonly lost?: boolean;
666
+ };
667
+ /**
668
+ * The pose the gait swings around. `arms[0]` is the right arm and `arms[1]` the left; `legs[0]` is the left leg and `legs[1]` the right
669
+ * (the order the Bestiário builds them in). `hipY` is the rest height of the hips.
670
+ */
671
+ type RestPose = {
672
+ readonly hipY: number;
673
+ readonly lean: number;
674
+ readonly headX: number;
675
+ readonly arms: readonly [JointAngles, JointAngles];
676
+ readonly legs: readonly [LegAngles, LegAngles];
677
+ };
678
+ /** What a gait sets this frame; a field the gait does not write stays `undefined` and the joint keeps what it had. */
679
+ type GaitSample = {
680
+ hipY?: number;
681
+ upper: {
682
+ x?: number;
683
+ y?: number;
684
+ z?: number;
685
+ };
686
+ head: {
687
+ x?: number;
688
+ y?: number;
689
+ };
690
+ arms: {
691
+ shoulder?: number;
692
+ elbow?: number;
693
+ }[];
694
+ legs: {
695
+ hip?: number;
696
+ knee?: number;
697
+ }[];
698
+ };
699
+ declare const emptySample: () => GaitSample;
700
+ type CascadeJoint = keyof typeof gait.cascade;
701
+ /** Cascade rates per joint (1/s): each joint reaches its target with a delay, from the hips to the head (Bestiário 12.3). */
702
+ declare const CASCADE: Readonly<Record<CascadeJoint, number>>;
703
+ /** The 8 poses of the instanced body, in the order of the game's `iB.w`. */
704
+ declare const INSTANCE_POSES: readonly ["walk", "crawl", "fallen", "attack", "scream", "crouch", "leap", "eat"];
705
+ type InstancePose = (typeof INSTANCE_POSES)[number];
706
+ /** `iB.w` of the instanced body: the pose plus 16 times the ground height in millimetres (a pile of bodies, a jump). */
707
+ declare const encodePoseHeight: (pose: InstancePose, height?: number) => number;
708
+ declare const decodePoseHeight: (encoded: number) => {
709
+ readonly pose: InstancePose;
710
+ readonly height: number;
711
+ };
712
+
713
+ type AnimateProfileParams = {
714
+ readonly profile?: GaitProfile;
715
+ readonly rest: RestPose;
716
+ /** Seconds. */
717
+ readonly time: number;
718
+ /** Per-body phase offset, radians (the page's `rig.ph`). */
719
+ readonly phase: number;
720
+ /** With `strideFromSpeed`, the speed and height that set cadence and swing. Without it the profile's own numbers are used. */
721
+ readonly stride?: StrideParams;
722
+ };
723
+ /** Joint targets of one gait at `time` (the Bestiário's `animate0`), as numbers; the rig applies the ones that are set. */
724
+ declare const animateProfile: ({ profile, rest, time, phase, stride }: AnimateProfileParams) => GaitSample;
725
+
726
+ /** One joint moving toward its target: it covers `dt · rate` of the distance each step (never more than all of it). */
727
+ declare const stepCascade: (current: number, target: number, dt: number, joint: CascadeJoint, rates?: Readonly<Record<CascadeJoint, number>>) => number;
728
+ /** Between two poses by joint name: a joint `from` does not name starts at the target (the Bestiário's `lerpPose`). */
729
+ declare const lerpPose: <TPose extends Readonly<Record<string, number>>>(from: Partial<TPose>, to: TPose, amount: number) => TPose;
730
+
731
+ type MoveId = keyof typeof gait.moves;
732
+ type MoveBlend = {
733
+ gait: number;
734
+ freq: number;
735
+ amp: number;
736
+ knee: number;
737
+ bob: number;
738
+ lean: number;
739
+ arm: number;
740
+ elb: number;
741
+ crouch: number;
742
+ aim: number;
743
+ turn: number;
744
+ grip: number;
745
+ };
746
+ /** The human movement states the Elenco blends every frame. */
747
+ declare const MOVES: Readonly<Record<MoveId, Readonly<MoveBlend>>>;
748
+ declare const MOVE_BLEND_RATE = 4;
749
+ declare const moveBlendOf: (move: MoveId) => MoveBlend;
750
+ /** Moves `current` toward the target move: each number covers `dt · 4` of the distance (never past it). Mutates and returns `current`. */
751
+ declare const blendMoves: (current: MoveBlend, target: MoveId | Readonly<MoveBlend>, dt: number) => MoveBlend;
752
+ type HumanArm = {
753
+ readonly side: 1 | -1;
754
+ readonly hold?: boolean;
755
+ };
756
+ type HumanPoseParams = {
757
+ readonly moves: Readonly<MoveBlend>;
758
+ /** Seconds since the start. */
759
+ readonly time: number;
760
+ /** The character's own phase offset. */
761
+ readonly phase: number;
762
+ /** `phase + offset`: the walking cycle's angle, advanced by `dt · freq`. */
763
+ readonly cycle: number;
764
+ /** The character's resting hunch plus the stage's. */
765
+ readonly hunch: number;
766
+ /** The infection stage's arm raise (0 healthy .. 1 reanimated). */
767
+ readonly armRaise: number;
768
+ /** Stage index: 3 and up adds a spine tremor, 4 is the reanimated body. */
769
+ readonly stage: number;
770
+ /** The `prefers-reduced-motion` mode: no idle motion, no gait. */
771
+ readonly reduced?: boolean;
772
+ readonly skirt?: boolean;
773
+ /** [right, left] as built: side and whether the hand holds an item. */
774
+ readonly arms: readonly [HumanArm, HumanArm];
775
+ /** [left, right] leg sides. */
776
+ readonly legSides: readonly [1 | -1, 1 | -1];
777
+ };
778
+ type HumanPose = {
779
+ readonly chestScaleY: number;
780
+ readonly hip: {
781
+ readonly x: number;
782
+ readonly y: number;
783
+ readonly zRotation: number;
784
+ };
785
+ readonly upper: {
786
+ readonly x: number;
787
+ readonly y: number;
788
+ readonly z: number;
789
+ };
790
+ readonly head: {
791
+ readonly x: number;
792
+ readonly y: number;
793
+ readonly z: number;
794
+ };
795
+ readonly legs: readonly {
796
+ readonly hip: number;
797
+ readonly knee: number;
798
+ readonly hipZ: number;
799
+ }[];
800
+ readonly arms: readonly {
801
+ readonly shoulder: number;
802
+ readonly shoulderZ: number;
803
+ readonly elbow: number;
804
+ readonly wrist: number;
805
+ readonly grip: number;
806
+ }[];
807
+ };
808
+ /** The blended walk of the Elenco for one frame (hips, spine, head, legs, arms), as joint targets. Pure: the caller keeps the phase and the blend. */
809
+ declare const humanPose: (params: HumanPoseParams) => HumanPose;
810
+ /** A leg that cannot carry weight (limp): the swing shrinks to 30%, the knee straightens, the spine leans toward the good leg. */
811
+ declare const LIMP: {
812
+ readonly swing: 0.3;
813
+ readonly knee: 0.05;
814
+ readonly lean: 0.07;
815
+ };
816
+
817
+ /** Prone crawl on the arms (the Elenco's leg-less body): joint targets at `time`; `figTilt` is the body's tilt target, which the caller approaches at `dt · 4`. */
818
+ type CrawlPose = {
819
+ readonly figTilt: number;
820
+ readonly hipY: number;
821
+ readonly hipZRotation: number;
822
+ readonly upper: {
823
+ readonly x: number;
824
+ readonly y: number;
825
+ };
826
+ readonly headX: number;
827
+ readonly arms: readonly {
828
+ readonly shoulder: number;
829
+ readonly shoulderZ: number;
830
+ readonly elbow: number;
831
+ }[];
832
+ readonly legs: readonly {
833
+ readonly hip: number;
834
+ readonly knee: number;
835
+ }[];
836
+ };
837
+ declare const CRAWL_TILT_RATE = 4;
838
+ declare const DEAD_TILT_RATE = 3;
839
+ declare const crawlPose: (time: number, phase: number, sides: readonly [1 | -1, 1 | -1]) => CrawlPose;
840
+ type DeadPose = {
841
+ readonly figTilt: number;
842
+ readonly hipY: number;
843
+ readonly headY: number;
844
+ readonly arms: readonly {
845
+ readonly shoulder: number;
846
+ readonly shoulderZ: number;
847
+ readonly elbow: number;
848
+ }[];
849
+ readonly legs: readonly {
850
+ readonly hip: number;
851
+ readonly hipZ: number;
852
+ readonly knee: number;
853
+ }[];
854
+ };
855
+ /** A body on the ground (the Elenco's death pose); the caller approaches `figTilt` at `dt · 3`. */
856
+ declare const deadPose: (armSides: readonly [1 | -1, 1 | -1]) => DeadPose;
857
+
858
+ type ConditionPhase = {
859
+ readonly id: string;
860
+ /** When the phase starts, in the timeline's units (hours, or seconds on an accelerated clock). The first phase starts at 0. */
861
+ readonly startsAt: number;
862
+ /** Numbers the phase drives (skin tint, hunch, arm raise, ...), by name. */
863
+ readonly effects?: Readonly<Record<string, number>>;
864
+ };
865
+ type ConditionOptions = {
866
+ readonly phases: readonly ConditionPhase[];
867
+ /** Timeline units per real second: 1 on a clock already in seconds (the accelerated preset), 1/3600 for hours. Default 1. */
868
+ readonly unitsPerSecond?: number;
869
+ /** Interpolate effects toward the next phase instead of stepping. Default false. */
870
+ readonly blend?: boolean;
871
+ };
872
+ type ConditionSample = {
873
+ readonly index: number;
874
+ readonly phase: ConditionPhase;
875
+ /** 0..1 inside the current phase (1 on the last one). */
876
+ readonly progress: number;
877
+ readonly effects: Readonly<Record<string, number>>;
878
+ readonly isFinal: boolean;
879
+ };
880
+ type ConditionTimeline = {
881
+ readonly elapsed: number;
882
+ /** Advances the clock by `seconds` of real time. */
883
+ advance(seconds: number): ConditionSample;
884
+ /** Jumps the clock to `units` of the timeline. */
885
+ seek(units: number): ConditionSample;
886
+ sample(): ConditionSample;
887
+ reset(): void;
888
+ };
889
+ /** A condition that moves through ordered phases over time (an infection: well, fever, delirium, reanimated). Generic: phases and effects are data. */
890
+ declare const createConditionTimeline: (options: ConditionOptions) => ConditionTimeline;
891
+
892
+ type BuildClass = 'lean' | 'average' | 'heavy';
893
+ /** What `zSpawn` draws for a body: height, width, how it builds up and the speed jitters. Every field comes from the stream, in the game's order. */
894
+ type BodyVariance = {
895
+ readonly height: number;
896
+ readonly build: number;
897
+ readonly buildClass: BuildClass;
898
+ /** Multiplies the chase speed: the build factor times a 0.92 .. 1.08 jitter. */
899
+ readonly speedFactor: number;
900
+ /** Multiplies the wander speed: 0.8 .. 1.2. */
901
+ readonly wanderFactor: number;
902
+ /** Multiplies the health: 0.9 lean, 1.15 heavy. */
903
+ readonly healthFactor: number;
904
+ };
905
+ type VarianceScale = {
906
+ readonly height?: number;
907
+ readonly build?: number;
908
+ };
909
+ /**
910
+ * One body's variance from `random`, drawing exactly what the game's `zSpawn` draws after the type, age and outfit picks:
911
+ * three draws for the height (a gaussian), one for the build band plus one inside it, one for the speed and one for the wander speed.
912
+ * `scale` is the type's own scale (a brute's 1.42) and `build` its width factor.
913
+ */
914
+ declare const drawBodyVariance: (random: () => number, scale?: VarianceScale) => BodyVariance;
915
+ /** Variants baked per type (the game's `nv`): 4 walkers, 3 runners, 2 crawlers and brutes, 1 for the rest. */
916
+ declare const variantCountFor: (typeId: string) => number;
917
+ /** The seed of variant `variant` of the type at `typeIndex` (the game's ZVAR rule: `7000 + c·101 + v·13`). */
918
+ declare const variantSeed: (typeIndex: number, variant: number) => number;
919
+ /** The decay each variant is baked at: 1, 2, 1, 2. */
920
+ declare const variantDecay: (variant: number) => number;
921
+
922
+ /** The colour and style pools of the Bestiário v17 builder, in the order its random stream picks from them. */
923
+ declare const SKIN_TONES: readonly [15845797, 14724224, 13012066, 11039050, 9067066, 6175526];
924
+ declare const TOP_COLORS: readonly [4878230, 9321275, 6113850, 5208643, 7040888, 4153146, 5917294, 9071162, 12104354, 2894896, 10111530, 3104099, 13216074, 8007242, 14210248, 3820138];
925
+ declare const PANTS_COLORS: readonly [2896704, 2040360, 4866096, 5922662, 3820138, 6969920, 2767402];
926
+ declare const SLEEVES: readonly ["short", "short", "long", "tank"];
927
+ declare const HAIR_STYLES_FEMALE: readonly ["long", "long", "bun", "short", "afro", "bald"];
928
+ declare const HAIR_STYLES_MALE: readonly ["short", "short", "buzz", "bald", "afro", "long"];
929
+ declare const CAP_COLORS: readonly [9321275, 2894896, 3820138, 13216074];
930
+ declare const BAG_COLORS: readonly [3820090, 5909028, 2896704];
931
+ declare const HAIR_COLORS: readonly [1709072, 2760214, 3877920, 6965802, 11569744, 8006170];
932
+ declare const SHOE_COLORS: readonly [1908514, 3811866, 12104872, 4856346];
933
+ /** Uniform bodies (the crowd's baked variants): skin by decay, shirt by type index. */
934
+ declare const DECAY_SKIN: readonly [13482920, 9740420, 8090698, 9206878];
935
+ declare const UNIFORM_SHIRTS: readonly [4878230, 9321275, 6113850, 5208643, 7040888, 4153146, 5917294, 9071162];
936
+ declare const UNIFORM_PANTS = 2896704;
937
+ declare const UNIFORM_SHOES = 1908514;
938
+ declare const SKIN_DECAY_BLEND: readonly [0.45, 0.65, 0.8, 0.88];
939
+ declare const GRAY = 4868678;
940
+ declare const RED = 8000012;
941
+ declare const BLOOD_COLORS: readonly [10361618, 7214096, 4860440, 1840658];
942
+ declare const OLD_HAIR = 9078656;
943
+
944
+ declare const palette_BAG_COLORS: typeof BAG_COLORS;
945
+ declare const palette_BLOOD_COLORS: typeof BLOOD_COLORS;
946
+ declare const palette_CAP_COLORS: typeof CAP_COLORS;
947
+ declare const palette_DECAY_SKIN: typeof DECAY_SKIN;
948
+ declare const palette_GRAY: typeof GRAY;
949
+ declare const palette_HAIR_COLORS: typeof HAIR_COLORS;
950
+ declare const palette_HAIR_STYLES_FEMALE: typeof HAIR_STYLES_FEMALE;
951
+ declare const palette_HAIR_STYLES_MALE: typeof HAIR_STYLES_MALE;
952
+ declare const palette_OLD_HAIR: typeof OLD_HAIR;
953
+ declare const palette_PANTS_COLORS: typeof PANTS_COLORS;
954
+ declare const palette_RED: typeof RED;
955
+ declare const palette_SHOE_COLORS: typeof SHOE_COLORS;
956
+ declare const palette_SKIN_DECAY_BLEND: typeof SKIN_DECAY_BLEND;
957
+ declare const palette_SKIN_TONES: typeof SKIN_TONES;
958
+ declare const palette_SLEEVES: typeof SLEEVES;
959
+ declare const palette_TOP_COLORS: typeof TOP_COLORS;
960
+ declare const palette_UNIFORM_PANTS: typeof UNIFORM_PANTS;
961
+ declare const palette_UNIFORM_SHIRTS: typeof UNIFORM_SHIRTS;
962
+ declare const palette_UNIFORM_SHOES: typeof UNIFORM_SHOES;
963
+ declare namespace palette {
964
+ export { palette_BAG_COLORS as BAG_COLORS, palette_BLOOD_COLORS as BLOOD_COLORS, palette_CAP_COLORS as CAP_COLORS, palette_DECAY_SKIN as DECAY_SKIN, palette_GRAY as GRAY, palette_HAIR_COLORS as HAIR_COLORS, palette_HAIR_STYLES_FEMALE as HAIR_STYLES_FEMALE, palette_HAIR_STYLES_MALE as HAIR_STYLES_MALE, palette_OLD_HAIR as OLD_HAIR, palette_PANTS_COLORS as PANTS_COLORS, palette_RED as RED, palette_SHOE_COLORS as SHOE_COLORS, palette_SKIN_DECAY_BLEND as SKIN_DECAY_BLEND, palette_SKIN_TONES as SKIN_TONES, palette_SLEEVES as SLEEVES, palette_TOP_COLORS as TOP_COLORS, palette_UNIFORM_PANTS as UNIFORM_PANTS, palette_UNIFORM_SHIRTS as UNIFORM_SHIRTS, palette_UNIFORM_SHOES as UNIFORM_SHOES };
965
+ }
966
+
967
+ type HairStyle = (typeof HAIR_STYLES_FEMALE)[number] | (typeof HAIR_STYLES_MALE)[number];
968
+ type OutfitLook = {
969
+ readonly cloth?: number;
970
+ readonly pants?: number;
971
+ readonly hat?: string;
972
+ };
973
+ type AppearanceParams = {
974
+ readonly seed: number;
975
+ /** The look of an outfit (police, military ...): its colours replace the draws, so the stream does not spend them. */
976
+ readonly outfit?: OutfitLook;
977
+ /** A baked crowd variant: no random look, fixed skin, shirt by `typeIndex`, short hair. */
978
+ readonly uniform?: boolean;
979
+ readonly typeIndex?: number;
980
+ readonly clothIndex?: number;
981
+ /** Forces the sex; unset draws it (42% female). */
982
+ readonly female?: boolean;
983
+ /** False for bodies that are never female (a brute): the draw is skipped. */
984
+ readonly allowFemale?: boolean;
985
+ readonly tone?: number;
986
+ readonly hairStyle?: HairStyle;
987
+ readonly hairColor?: number;
988
+ /** The type's own look: wet bodies have long hair, charred ones are bald. */
989
+ readonly wet?: boolean;
990
+ readonly charred?: boolean;
991
+ /** A living person: no bag, no tear. */
992
+ readonly human?: boolean;
993
+ /** 0 fresh-dead, 1 normal, 2 old (grey hair). */
994
+ readonly life?: number;
995
+ };
996
+ type AppearanceTraits = {
997
+ readonly female: boolean;
998
+ readonly tone: number;
999
+ readonly topColor: number;
1000
+ readonly pantsColor: number;
1001
+ readonly sleeve: string;
1002
+ readonly hair: string;
1003
+ readonly headWidth: number;
1004
+ readonly headHeight: number;
1005
+ readonly jaw: number;
1006
+ readonly nose: number;
1007
+ readonly beard: boolean;
1008
+ readonly glasses: boolean;
1009
+ readonly cap: boolean;
1010
+ readonly capColor: number;
1011
+ readonly bag: boolean;
1012
+ readonly bagColor: number;
1013
+ readonly torn: boolean;
1014
+ readonly apron: boolean;
1015
+ readonly hairColor: number;
1016
+ readonly shoeColor: number;
1017
+ };
1018
+ type DrawnAppearance = {
1019
+ readonly traits: AppearanceTraits;
1020
+ /** The stream, positioned after the look: the body builder keeps drawing from it (the hair of a decrepit body). */
1021
+ readonly random: () => number;
1022
+ };
1023
+ /** The seed of the blood stream (the same drips on every level of detail). */
1024
+ declare const BLOOD_SEED_OFFSET = 99;
1025
+ declare const createBloodStream: (seed: number) => (() => number);
1026
+ /**
1027
+ * A person's look from a seed, drawing in exactly the order the Bestiário v17 builder draws from its `VR` stream, including the draws a
1028
+ * short-circuit skips (no beard roll for a woman, no cap roll under a hat). Returns the stream so the builder can continue from it.
1029
+ */
1030
+ declare const drawAppearance: (params: AppearanceParams) => DrawnAppearance;
1031
+
1032
+ /** P = √(E·m/A) against the resistance of the path; a protection layer stops a projectile whose force is not above its resistance. */
1033
+ declare const PHYSICAL_PENETRATION: PenetrationModel;
1034
+ /** The game's rule: pistol-class rounds are stopped by a vest or a helmet; the skull is pierced within the calibre's `cranio` range. */
1035
+ declare const TEAM_GAME_PENETRATION: PenetrationModel;
1036
+ declare const PENETRATION_MODELS: {
1037
+ readonly physical: PenetrationModel;
1038
+ readonly 'team-game': PenetrationModel;
1039
+ };
1040
+ /** Resistance of a torso path: flesh 22 cm × 0.25 + bone 2 cm × 1.0 = 7.5. */
1041
+ declare const TORSO_RESISTANCE: number;
1042
+ type ResistanceParams = {
1043
+ readonly kind: SegmentKind;
1044
+ readonly bodyCount: number;
1045
+ readonly protection: number;
1046
+ };
1047
+ /** R of the path through a segment: torso 7.5 (× the body count of a brute), head 0.7×, limbs 0.5×, plus the protection worn over it. */
1048
+ declare const segmentResistance: ({ kind, bodyCount, protection }: ResistanceParams) => number;
1049
+ /** Share of the energy a projectile of force `force` keeps after a path of resistance `resistance`: P/(P+R). */
1050
+ declare const energyKept: (force: number, resistance: number) => number;
1051
+ /** Penetration points a region costs in the game (`custo`): head 0.7, torso 1 (a brute 2), limb 0.5. */
1052
+ declare const teamGameCost: (region: BodyRegion, bodyCount: number) => number;
1053
+ /** The game's falloff: full damage up to the effective range, then linear down to half at twice the range, never below half. */
1054
+ declare const teamGameFalloff: (distance: number, range: number) => number;
1055
+
1056
+ /** Deterministic mulberry32 stream in [0, 1). */
1057
+ declare const createRng: (seed: number) => (() => number);
1058
+ /** Damage points of a blow from its energy in joules: `coefficient · E^exponent` (9×19 → 35, 7.62×39 → 59, .308 → 71.7). */
1059
+ declare const pointsFromEnergy: (energy: number, rules: Pick<DamageRules, "points">) => number;
1060
+ /** The caller's points, else the calibre table's, else the energy formula. */
1061
+ declare const resolvePoints: (hit: BodyHit, caliber: CaliberEntry | undefined, rules: Pick<DamageRules, "points">) => number;
1062
+ /** One draw of ±`spread` on the points (none when the spread is 0, so the stream is not touched). */
1063
+ declare const spreadPoints: (points: number, spread: number, random: () => number) => number;
1064
+ /** P = √(E·m/A): E in joules, m in grams, A the cross-section in mm². */
1065
+ declare const penetrationForce: (energy: number, grams: number, diameterMm: number) => number;
1066
+ /** What a ballistic hit is (force, class, skull range, pellets, distance), from the hit and the calibre table. */
1067
+ declare const projectileOf: (hit: BodyHit, caliber: CaliberEntry | undefined) => ProjectileInfo;
1068
+ /** `hit.cut`, else the default of its damage type (a bullet by class: pistol 0.15, buckshot 0.75, rifle 0.45). */
1069
+ declare const cutOf: (hit: BodyHit, projectile: ProjectileInfo | undefined, rules: Pick<DamageRules, "defaultCut">) => number;
1070
+
1071
+ /** The layered protection of T3: `1 − Π(1 − value·condition)`, at most `cap`. */
1072
+ declare const LAYERED_PROTECTION_CAP = 0.9;
1073
+ type LayeredProtectionParams = {
1074
+ readonly layers: readonly ProtectionLayer[];
1075
+ readonly attack: 'bite' | 'scratch';
1076
+ /** 0..1 condition per layer id (worn, torn); 1 by default. */
1077
+ readonly conditions?: Readonly<Record<string, number>>;
1078
+ };
1079
+ declare const layeredProtection: ({ layers, attack, conditions }: LayeredProtectionParams) => number;
1080
+ /** The layers worn that cover any of `slots` and are not used up. */
1081
+ declare const layersOn: (worn: readonly ProtectionLayer[], slots: readonly BodySlot[], consumed: ReadonlySet<string>) => ProtectionLayer[];
1082
+ /** What the layers over a segment do to one blow. */
1083
+ type ProtectionOutcome = {
1084
+ /** A consumable layer took the whole projectile (a helmet): no damage, and the layer is used up. */
1085
+ readonly absorbedBy?: string;
1086
+ /** Share of the points that gets through the ballistic layers. */
1087
+ readonly ballistic: number;
1088
+ /** Penetration resistance the layers add (the `physical` model). */
1089
+ readonly resistance: number;
1090
+ /** Factor on limb damage (a turnout coat). */
1091
+ readonly limbs: number;
1092
+ /** Factor on fire damage. */
1093
+ readonly fire: number;
1094
+ };
1095
+ type ProtectParams = {
1096
+ readonly layers: readonly ProtectionLayer[];
1097
+ readonly projectile?: ProjectileInfo;
1098
+ readonly model: PenetrationModel;
1099
+ };
1100
+ /** A stopped projectile does the layer's `blunt` share; one that goes through, `pass` (default `blunt`). Non-projectiles ignore ballistic layers. */
1101
+ declare const protect: ({ layers, projectile, model }: ProtectParams) => ProtectionOutcome;
1102
+
1103
+ export { ARSENAL_RULES, type AgeGroup, type AnimateProfileParams, palette as AppearancePalette, type AppearanceParams, type AppearanceTraits, BLOOD_LITERS_PER_DROP, BLOOD_SEED_OFFSET, BODY_MATERIAL, BODY_REGIONS, type BallisticClass, type BlastRules, type Bleeder, type BodyHit, type BodyMultipliers, type BodySpec, type BodyVariance, type BuildClass, CASCADE, CRAWL_TILT_RATE, type CaliberEntry, type CascadeJoint, type Character, type CharacterKit, type CharacterPack, type CharacterPreset, type CharacterSpec, type CharactersOptions, type ConditionOptions, type ConditionPhase, type ConditionSample, type ConditionTimeline, type CorpseEntry, type CorpseLedger, type CorpseOptions, type CrawlPose, type CreateRigParams, DAMAGE_PRESETS, DEAD_TILT_RATE, DEFAULT_CORPSES, DEFAULT_DAMAGE_RULES, type DamageCharacter, type DamageOptions, type DamageRules, type DeadPose, type DeathRule, type Detail, type DrawnAppearance, ELENCO_RULES, GAME_BONES, GUIDE, type GaitProfile, type GaitSample, type HairStyle, type HitReport, type HumanArm, type HumanPose, type HumanPoseParams, INSTANCE_POSES, type InstancePose, JOINT_IDS, JOINT_PARENTS, type JointAngles, type JointId, LAYERED_PROTECTION_CAP, LIMB_REGIONS, LIMP, type LegAngles, type LimbRegion, type LineHit, type LineResult, type LineShot, type LineTarget, MOVES, MOVE_BLEND_RATE, type MoveBlend, type MoveId, type OutfitLook, PENETRATION_MODELS, PERSON_PRESET, PHYSICAL_PENETRATION, PROTECTION_PRESETS, type PenetrateLineParams, type PenetrationModel, type ProjectileInfo, type ProtectionLayer, type ProtectionOutcome, REGION_HIT_POINTS, REGION_SEGMENTS, type ResolvedCharacterSpec, type RestPose, type Rig, SEGMENT_IDS, SEGMENT_NUMBERS, SEGMENT_TREE, type SegmentId, type SegmentNode, type SegmentNumbers, type SegmentRuleId, type SegmentSide, type SegmentState, type SegmentStatus, type SeverityRules, type Stride, type StrideParams, TEAM_GAME, TEAM_GAME_CALIBERS, TEAM_GAME_CALIBER_KEYS, TEAM_GAME_PENETRATION, TORSO_RESISTANCE, type VarianceScale, animateProfile, applyPatches, applyPreset, bleedRateAt, blendMoves, crawlPose, createBloodStream, createBodySpec, createCharacters, createConditionTimeline, createCorpseLedger, createRig, createRng, cutOf, deadPose, decodePoseHeight, describeBody, drawAppearance, drawBodyVariance, emptySample, encodePoseHeight, energyKept, humanPose, isBodyRegion, isSegmentId, layeredProtection, layersOn, lerpPose, mergeById, moveBlendOf, penetrateLine, penetrationForce, pointsFromEnergy, projectileOf, protect, removeIds, resolveDamageRules, resolvePoints, segmentNode, segmentResistance, segmentsBelow, spreadPoints, stepCascade, strideFor, teamGameCost, teamGameFalloff, variantCountFor, variantDecay, variantSeed, walkCadence, walkGait };