@adatechnology/game-characters-three 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,876 @@
1
+ import * as THREE_NS from 'three';
2
+ import { BodySpec, CascadeJoint, RestPose, StrideParams, GaitSample, lerpPose, GaitProfile, SegmentId, CharacterKit, CharacterPack, CharacterSpec, DamageCharacter, BodyHit } from '@adatechnology/game-characters';
3
+ import { Vec3 as Vec3$1, DestructionEvent, TestableModule } from '@adatechnology/game-contracts';
4
+
5
+ /** The three.js namespace the host passes in: the package never imports three itself, so one script tag works. */
6
+ type Three = typeof THREE_NS;
7
+
8
+ /**
9
+ * The vertex-shader chunk of the instanced body, carried byte for byte from the game (G 65400 `bodyChunk`): the pose math of the 12 bones on the GPU.
10
+ * Per-instance attributes: iA = (x, z, yaw, phase) · iB = (height, build, stride, pose + 16 × ground height in mm) · iC = (missing-parts mask, infection 0..1,
11
+ * arms 0 human / 1 zombie / 2+ armed, skin tone) · iCol = shirt colour · iEye = eye colour (0 = human). The mask bits are `MISSING_BITS`.
12
+ * A test compares its sha-256 with the one recorded from the game.
13
+ */
14
+ declare const BODY_CHUNK = "\n attribute float bone; attribute vec4 vcol; attribute vec3 pivot; attribute vec3 ppivot;\n attribute vec4 iA; attribute vec4 iB; attribute vec4 iC; attribute vec3 iCol; attribute vec3 iEye;\n uniform float uTime;\n varying vec3 vTint; varying float vGlow;\n mat3 rX(float a){ float c=cos(a), s=sin(a); return mat3(1.,0.,0., 0.,c,s, 0.,-s,c); }\n mat3 rY(float a){ float c=cos(a), s=sin(a); return mat3(c,0.,-s, 0.,1.,0., s,0.,c); }\n mat3 rZ(float a){ float c=cos(a), s=sin(a); return mat3(c,s,0., -s,c,0., 0.,0.,1.); }\n bool miss(float bit){ if (iC.z > 1.5) return false; return mod(floor(iC.x / bit), 2.) > 0.5; }\n vec3 bodyAnim(vec3 p, inout vec3 nrm){\n float ph = iA.w*6.2831, gait = iB.z, pose = iB.w - floor(iB.w/16.)*16., zomb = iC.z;\n float side = (bone==2.||bone==4.||bone==7.||bone==9.) ? -1. : 1.;\n // cascata: cada junta atrasa em rela\u00E7\u00E3o ao quadril (Besti\u00E1rio 12.3)\n float lag = bone==1.||bone==6.||bone==11. ? 0.55 : (bone>=7.&&bone<=8. ? 0.35 : (bone==2.||bone==3. ? 0.2 : 0.));\n float sw = sin(ph - lag);\n mat3 Rp = mat3(1.), Rc = mat3(1.);\n bool isArm = bone==2.||bone==3., isFore = bone==7.||bone==8., isThigh = bone==4.||bone==5., isShin = bone==9.||bone==10.;\n float legS = (bone==4.||bone==9.) ? 1. : -1.;\n float walkS = sin(ph) * legS;\n if (isThigh || isShin) Rp = rX(-walkS*0.5*gait);\n if (isShin) Rc = rX(max(0., cos(ph + (legS>0.?0.:3.1416)))*0.85*gait);\n if (pose==6.) { if (isThigh) Rp = rX(-1.1); if (isShin) Rc = rX(1.4); }\n if (pose==5.) { if (isThigh) Rp = rX(-0.95 - walkS*0.3*gait); if (isShin) Rc = rX(1.9); } // agachado: joelho dobra, o p\u00E9 fica no ch\u00E3o\n if (pose==7.) { if (isThigh) Rp = rX(-1.15); if (isShin) Rc = rX(2.3); } // ajoelhado comendo\n if (pose==2. && isShin && fract(iA.w*3.)*legS > 0.3) Rc = rX(fract(iA.w*3.)*1.3); // corpo com um joelho dobrado\n float armS = sin(ph) * (side>0. ? 1. : -1.);\n if (isArm || isFore) {\n // zumbi: bra\u00E7os ca\u00EDdos vagando; levanta (iC.z = 1 + 0,4 \u00D7 alcance) quando percebe algu\u00E9m ou vai agarrar\n float raise = clamp((zomb - 1.)/0.4, 0., 1.);\n float fwd = zomb > 0.5 && zomb < 1.5 ? mix(-0.12 + armS*0.3*gait + 0.05*sin(uTime*1.3+iA.w), -1.35 + armS*0.18 + 0.1*sin(uTime*2.+iA.w), raise) : armS*0.45*gait;\n if (pose==3.) fwd = -1.55 + 0.35*sin(uTime*9.+iA.w);\n if (pose==4.) fwd = -2.6;\n if (pose==6.) fwd = -2.0;\n if (pose==2.) fwd = (fract(iA.w*5.) > 0.5 && side > 0.) ? -2.8 : -0.35 + 0.3*sin(iA.w*5. + side);\n if (pose==1.) fwd = -2.6 + 0.5*sin(ph)*side; // puxa o corpo com os bra\u00E7os\n if (pose==7.) fwd = -0.9 + 0.3*sin(uTime*6.+iA.w);\n Rp = rX(fwd) * rZ(side*(pose==2. ? 0.5 + 0.3*sin(iA.w*3.) : 0.08));\n // armado (iC.z = 2 + pitch codificado): as duas m\u00E3os na arma, bra\u00E7os seguem a mira\n if (zomb > 1.5 && pose != 2.) { float aim = (zomb - 2.)*3. - 1.; Rp = rX(-1.42 + aim*0.85 + 0.02*sin(ph)*gait) * rZ(side < 0. ? 0.36 : -0.54); } // arma na m\u00E3o direita de verdade (o osso 'D' fica em +x, que \u00E9 a esquerda do boneco virado para +z); a esquerda encontra na empunhadura\n }\n if (isFore) Rc = rX(zomb > 1.5 && pose != 2. ? (side < 0. ? -0.12 : -0.04) : zomb > 0.5 ? mix(-0.3 - max(0., armS)*0.3*gait, -0.15, clamp((zomb - 1.)/0.4, 0., 1.)) : -0.35 - max(0., armS)*0.4*gait);\n if (bone==11.) Rc = rX(pose==3.||pose==4. ? 0.35 + 0.2*sin(uTime*14.) : 0.05);\n vec3 q = p; vec3 n = nrm;\n if (isFore || isShin) { q = Rc*(q - pivot) + pivot; n = Rc*n; q = Rp*(q - ppivot) + ppivot; n = Rp*n; }\n else if (isArm || isThigh || bone==11.) { q = Rp*(q - pivot) + pivot; n = Rp*n; q = Rc*(q - pivot) + pivot; n = Rc*n; }\n // humano armado andando de lado: quadril e pernas giram para a dire\u00E7\u00E3o do passo (iC.x leva o \u00E2ngulo)\n if (zomb > 1.5 && (isThigh || isShin)) { mat3 hy = rY((iC.x - 0.5)*10.); q = hy*(q - vec3(0.,0.9,0.)) + vec3(0.,0.9,0.); n = hy*n; }\n // partes faltando: colapsa no piv\u00F4\n if ((bone==1.||bone==6.||bone==11.) && miss(1.)) q = pivot;\n if (bone==11. && miss(512.)) q = pivot;\n if ((bone==2.||bone==7.) && miss(2.)) q = pivot; if ((bone==3.||bone==8.) && miss(4.)) q = pivot;\n if ((bone==4.||bone==9.) && miss(8.)) q = pivot; if ((bone==5.||bone==10.) && miss(16.)) q = pivot;\n if (bone==7. && miss(32.)) q = pivot; if (bone==8. && miss(64.)) q = pivot;\n if (bone==9. && miss(128.)) q = pivot; if (bone==10. && miss(256.)) q = pivot;\n // corpo: inclina\u00E7\u00E3o, balan\u00E7o, agachar, rastejar, ca\u00EDdo\n bool upper = !(isThigh || isShin);\n float zz = zomb > 0.5 && zomb < 1.5 ? 1. : 0.;\n float lean = zz > 0.5 ? 0.12 + 0.3*min(1., gait) : 0.04*gait;\n if (pose==3.) lean += 0.15;\n if (pose==5.) lean += 0.25;\n if (pose==7.) lean += 0.8;\n if (upper) { q -= vec3(0.,0.95,0.); q = rX(lean)*q; n = rX(lean)*n; q += vec3(0.,0.95,0.); }\n vec3 hp = rX(lean)*(pivot - vec3(0.,0.95,0.)) + vec3(0.,0.95,0.); // pesco\u00E7o j\u00E1 inclinado: a cabe\u00E7a n\u00E3o descola\n if (bone==1.||bone==6.||bone==11.) { q -= hp; mat3 h = rY(sin(uTime*0.7+iA.w)*0.25*zz) * rZ(0.18*zz*sin(iA.w*3.)); q = h*q; n = h*n; q += hp; }\n q.y += abs(sin(ph))*0.035*gait - (pose==5. ? 0.35 : pose==7. ? 0.5 : 0.);\n if (pose==1. && (bone==1.||bone==6.||bone==11.)) { mat3 hc = rX(-1.25); q = hc*(q - hp) + hp; n = hc*n; } // rastejante levanta a cabe\u00E7a\n if (pose==1.) { mat3 c = rX(1.42); q = c*(q - vec3(0.,0.95,0.)) + vec3(0.,0.2,0.); n = c*n; }\n if (pose==2.) { float flip = step(0.5, fract(iA.w*1.7)); float fk = clamp(gait, 0., 1.); fk = fk*fk*(3.-2.*fk); /* queda animada: passada leva o progresso */ mat3 c = rX((-1.5 + flip*3.0)*fk) * rZ(sin(iA.w*7.)*0.3*fk); /* de costas ou de bru\u00E7os */ q = c*(q - vec3(0.,0.95,0.)) + mix(vec3(0.,0.95,0.), vec3(0.,0.16,-0.6 + flip*1.2), fk); n = c*n; }\n nrm = n;\n return q;\n }\n";
15
+ type BodyChunkOptions = {
16
+ /** Height of the hip joint (the trunk's pivot), metres. The game's 0.95 is the default. */
17
+ readonly hipHeight?: number;
18
+ };
19
+ /** The chunk with the hip joint set (the only joint the shader holds as a constant); without options it is `BODY_CHUNK` itself. */
20
+ declare const createBodyChunk: ({ hipHeight }?: BodyChunkOptions) => string;
21
+
22
+ type Vec3 = readonly [number, number, number];
23
+ /**
24
+ * The joints the instanced body hangs from, by the game's names. `E` is the side with the negative x (bones 2, 4, 7 and 9), `D` the positive one
25
+ * (bones 3, 5, 8 and 10); the shader's `side` depends on it, so the sides are told apart by the sign of x, whatever a spec calls them.
26
+ */
27
+ type BodyJoints = {
28
+ readonly hip: Vec3;
29
+ readonly neck: Vec3;
30
+ readonly shoulderE: Vec3;
31
+ readonly shoulderD: Vec3;
32
+ readonly elbowE: Vec3;
33
+ readonly elbowD: Vec3;
34
+ readonly thighE: Vec3;
35
+ readonly thighD: Vec3;
36
+ readonly kneeE: Vec3;
37
+ readonly kneeD: Vec3;
38
+ };
39
+ declare const jointsOf: (body: BodySpec) => BodyJoints;
40
+ type Geometry = THREE_NS.BufferGeometry;
41
+ type BuildInstancedBodyParams = {
42
+ /** Radial segments of the torso: 16, 10 or 5 for the three levels of detail. */
43
+ readonly segments: number;
44
+ /** Proportions (default `TEAM_GAME`, the joints the game's body was drawn with). */
45
+ readonly body?: BodySpec;
46
+ };
47
+ /**
48
+ * The crowd body as one merged geometry, torso, head, hair, jaw, eyes, arms and legs tagged with the bone, pivot and material the shader reads
49
+ * (G 65367 `buildBody`). The joints come from the spec, so a different `body` moves the limbs; the parts' own sizes follow the segment lengths.
50
+ */
51
+ declare const buildInstancedBody: (three: Three, { segments, body }: BuildInstancedBodyParams) => Geometry;
52
+
53
+ type BodyMaterialParams = {
54
+ /** The depth material for shadows (true) or the lit body (false). */
55
+ readonly depth?: boolean;
56
+ /** The shared clock the shader reads (`uTime`): `{ value }` the host advances once per frame. */
57
+ readonly time: {
58
+ value: number;
59
+ };
60
+ readonly chunk?: BodyChunkOptions;
61
+ };
62
+ /**
63
+ * The instanced body's material: the pose chunk in the vertex shader and the per-instance tints (skin by tone and infection, shirt, pants, hair,
64
+ * glowing eyes) in the fragment shader (G 65473 `bodyMaterial`).
65
+ */
66
+ declare const createBodyMaterial: (three: Three, { depth, time, chunk }: BodyMaterialParams) => THREE_NS.Material;
67
+
68
+ type BodyBatchOptions = {
69
+ /** Instances the batch can hold. */
70
+ readonly max: number;
71
+ readonly castShadow?: boolean;
72
+ readonly time: {
73
+ value: number;
74
+ };
75
+ };
76
+ /** One instance: the 12 numbers of the shader's `iA`, `iB` and `iC`, the shirt colour and the eye colour (G 65509 `push`). */
77
+ type BodyInstance = {
78
+ readonly a: readonly [number, number, number, number];
79
+ readonly b: readonly [number, number, number, number];
80
+ readonly c: readonly [number, number, number, number];
81
+ readonly color: {
82
+ readonly r: number;
83
+ readonly g: number;
84
+ readonly b: number;
85
+ };
86
+ readonly eye?: {
87
+ readonly r: number;
88
+ readonly g: number;
89
+ readonly b: number;
90
+ };
91
+ /** Ground height (a pile of bodies, a jump); packed into `b[3]` next to the pose. */
92
+ readonly y?: number;
93
+ };
94
+ type BodyBatch = {
95
+ readonly mesh: THREE_NS.InstancedMesh;
96
+ readonly max: number;
97
+ readonly count: number;
98
+ begin(): void;
99
+ push(instance: BodyInstance): void;
100
+ end(): void;
101
+ };
102
+ /** A batch of bodies drawn in one call (zombies, survivors, corpses). `begin`, `push` per body each frame, then `end` (G 65509 `BodyBatch`). */
103
+ declare const createBodyBatch: (three: Three, geometry: THREE_NS.BufferGeometry, options: BodyBatchOptions) => BodyBatch;
104
+
105
+ /** Radial segments of the three instanced body geometries (G 65541 `BODY_LOD`): close, middle and far. */
106
+ declare const LOD_SEGMENTS: readonly [16, 10, 5];
107
+ /** Detail factors the rich builder bakes at for the same three levels (G ZVAR). */
108
+ declare const LOD_BAKE: readonly [1, 0.5, 0.28];
109
+ /** Distances in metres where the middle and the far level start (G `LOD_D`). */
110
+ declare const LOD_DISTANCES: readonly [18, 55];
111
+ /** Instances each level's batch holds for baked variants (G ZVAR). */
112
+ declare const LOD_BATCH_CAPACITY: readonly [160, 700, 2600];
113
+ /** The far level casts no shadow. */
114
+ declare const LOD_CASTS_SHADOW: readonly [true, true, false];
115
+ /** 0, 1 or 2 for a body `distance` metres from the camera. */
116
+ declare const lodAt: (distance: number, distances?: readonly [number, number]) => 0 | 1 | 2;
117
+
118
+ /** The pose a body is baked in: every joint at rest, so the shader's animation starts from a neutral body (G 66017 `BAKE_NEUTRO`). */
119
+ declare const BAKE_NEUTRAL_POSE: {
120
+ readonly lean: 0;
121
+ readonly armL: 0;
122
+ readonly armR: 0;
123
+ readonly elbL: 0;
124
+ readonly elbR: 0;
125
+ readonly legL: 0;
126
+ readonly legR: 0;
127
+ readonly kneeL: 0;
128
+ readonly kneeR: 0;
129
+ readonly headX: 0;
130
+ readonly headZ: 0;
131
+ readonly drop: 0;
132
+ readonly armOut: 0;
133
+ readonly neck: 0;
134
+ readonly headFwd: 0;
135
+ };
136
+ type BakeSpec = {
137
+ readonly type?: string;
138
+ readonly decay?: number;
139
+ readonly seed?: number;
140
+ readonly pose?: Readonly<Record<string, number>>;
141
+ /** A detailed survivor to bake instead of a zombie body. */
142
+ readonly retrato?: unknown;
143
+ readonly [key: string]: unknown;
144
+ };
145
+ /** The rich builders the bake reads, injected: the Bestiário v17 builder and (optionally) the survivor builder. */
146
+ type BakeBuilders = {
147
+ /** Builds a body at the level of detail `lod` (1, 0.5, 0.28); `spec.pose` already carries the neutral pose. */
148
+ buildBest(spec: BakeSpec, lod: number): THREE_NS.Object3D;
149
+ buildPortrait?(portrait: unknown, lod: number): THREE_NS.Object3D;
150
+ /** The material of the shirt, which the baked parts mark as material 1. */
151
+ topMaterial(): THREE_NS.Material | null;
152
+ };
153
+ type BakedBody = {
154
+ readonly geo: THREE_NS.BufferGeometry;
155
+ readonly s: number;
156
+ readonly bulk: number;
157
+ readonly verts: number;
158
+ };
159
+ /**
160
+ * Bakes a rich body into one indexable geometry, each vertex with its bone, pivot, parent pivot and colour, so the instanced shader can animate it
161
+ * (G 66019 `bakeBody`). Blood stays a vivid wine red at any decay; eyes are enlarged far away so they read.
162
+ */
163
+ declare const bakeBody: (three: Three, builders: BakeBuilders, spec: BakeSpec, lod: number) => BakedBody;
164
+
165
+ type VariantType = {
166
+ readonly id: string;
167
+ /** The name the rich builder looks the type up by (the Bestiário's `nome`). */
168
+ readonly name: string;
169
+ readonly index: number;
170
+ };
171
+ type BodyVariant = {
172
+ readonly typeId: string;
173
+ readonly variant: number;
174
+ readonly seed: number;
175
+ readonly decay: number;
176
+ readonly s: number;
177
+ readonly bulk: number;
178
+ /** Vertex count per level of detail. */
179
+ readonly verts: readonly number[];
180
+ readonly geometries: readonly THREE_NS.BufferGeometry[];
181
+ };
182
+ /**
183
+ * The baked variants of each type (G ZVAR): `variantCountFor(type)` bodies per type, each at decay 1, 2, 1, 2 and the seed `7000 + c·101 + v·13`,
184
+ * baked at the three levels of detail so a horde is a few draw calls.
185
+ */
186
+ declare const bakeVariants: (three: Three, builders: BakeBuilders, types: readonly VariantType[]) => {
187
+ readonly variants: BodyVariant[];
188
+ readonly byType: ReadonlyMap<string, readonly number[]>;
189
+ };
190
+ /** The three batches of a variant (one per level of detail), sized for a horde. */
191
+ declare const createVariantBatches: (three: Three, variant: BodyVariant, time: {
192
+ value: number;
193
+ }) => readonly BodyBatch[];
194
+
195
+ /** Where a limb comes from: arms leave at chest height with the sleeve's colour, legs low and long with the trousers'. */
196
+ type LimbLaunch = {
197
+ readonly x: number;
198
+ readonly z: number;
199
+ readonly kind: 'arm' | 'leg';
200
+ /** The sleeve colour of the owner (arms). */
201
+ readonly color: number;
202
+ /** The blood of the owner's age: colour, dried colour and whether it bleeds at all (the oldest dead do not: they spill dust). */
203
+ readonly blood: {
204
+ readonly color: number;
205
+ readonly dried: number;
206
+ readonly bleeds: boolean;
207
+ };
208
+ };
209
+ type FlyingLimb = {
210
+ x: number;
211
+ y: number;
212
+ z: number;
213
+ vx: number;
214
+ vy: number;
215
+ vz: number;
216
+ rx: number;
217
+ rz: number;
218
+ w: number;
219
+ t: number;
220
+ rest: boolean;
221
+ len: number;
222
+ col: number;
223
+ cor: number;
224
+ seco: number;
225
+ splat: boolean;
226
+ };
227
+ /** The fluids bridge: where a limb bleeds when it leaves and where it splats when it lands. The host decides what that draws. */
228
+ type LimbEffects = {
229
+ onLaunch?(limb: FlyingLimb, kind: 'arm' | 'leg'): void;
230
+ onSplat?(splat: {
231
+ readonly x: number;
232
+ readonly z: number;
233
+ readonly radius: number;
234
+ readonly rotation: number;
235
+ readonly color: number;
236
+ readonly dried: number;
237
+ }): void;
238
+ };
239
+ type FlyingLimbsOptions = {
240
+ /** Pieces alive at once (the game's 40 per player); the oldest is reused. */
241
+ readonly max?: number;
242
+ readonly random: () => number;
243
+ readonly effects?: LimbEffects;
244
+ };
245
+ type FlyingLimbs = {
246
+ readonly mesh: THREE_NS.InstancedMesh;
247
+ readonly limbs: readonly FlyingLimb[];
248
+ launch(launch: LimbLaunch): FlyingLimb;
249
+ update(dt: number): void;
250
+ };
251
+ /** Torn-off pieces that fly, bounce and stay on the ground, as one instanced mesh of capsules (G 67953 `fxLimb`, 68040 update). */
252
+ declare const createFlyingLimbs: (three: Three, { max, random, effects }: FlyingLimbsOptions) => FlyingLimbs;
253
+
254
+ type Euler3 = {
255
+ x: number;
256
+ y: number;
257
+ z: number;
258
+ };
259
+ type Node3 = {
260
+ rotation: Euler3;
261
+ position: {
262
+ x: number;
263
+ y: number;
264
+ z: number;
265
+ };
266
+ };
267
+ /** A limb of the rich body, as the Bestiário builds it: the group at the joint, the one below it (elbow, knee) and the rest angles. */
268
+ type RigArm = {
269
+ g: Node3;
270
+ el: Node3 & {
271
+ userData: Record<string, unknown>;
272
+ };
273
+ base: number;
274
+ eBase: number;
275
+ };
276
+ type RigLeg = {
277
+ g: Node3;
278
+ kn: Node3;
279
+ base: number;
280
+ kBase: number;
281
+ lost?: boolean;
282
+ };
283
+ /** What `RichBodyBuilder.build` leaves in `root.userData.rig` (the page's `rig`). */
284
+ type RichRig = {
285
+ readonly type: string;
286
+ readonly fig: Node3;
287
+ readonly upper: Node3;
288
+ readonly headG: Node3;
289
+ readonly arms: RigArm[];
290
+ readonly legs: RigLeg[];
291
+ readonly dangles: {
292
+ g: Node3;
293
+ el: Node3;
294
+ }[];
295
+ readonly flaps: (Node3 & {
296
+ userData: Record<string, unknown>;
297
+ })[];
298
+ readonly ph: number;
299
+ readonly dropBase: number;
300
+ readonly leanBase: number;
301
+ readonly hxBase: number;
302
+ /** Cascade state, made on the first call of `cascade`. */
303
+ cur?: Map<readonly [Node3 | Euler3, string, CascadeJoint], number>;
304
+ jl?: (readonly [Node3 | Euler3, string, CascadeJoint])[];
305
+ };
306
+ /** The names a pose may set (the Bestiário's `setPose`). All are optional: a joint a pose does not name keeps what it had. */
307
+ type PoseTargets = Partial<Record<'lean' | 'twist' | 'side' | 'drop' | 'headX' | 'headY' | 'headZ' | 'armR' | 'armL' | 'elbR' | 'elbL' | 'armRz' | 'armLz' | 'legL' | 'legR' | 'kneeL' | 'kneeR', number>>;
308
+ type RigAnimatorOptions = {
309
+ /** Gait profile by the type name the rig carries (`rig.type`); a type that is not here walks like the first type of the Bestiário (the slow one). */
310
+ readonly gaits?: Readonly<Record<string, GaitProfile>>;
311
+ /** Cascade rates per joint. Default: the engine's `CASCADE`. */
312
+ readonly cascade?: Readonly<Record<CascadeJoint, number>>;
313
+ };
314
+ type RigAnimator = {
315
+ /** The pose the gait swings around (legs `lost` stay out of the swing). */
316
+ restOf(rig: RichRig): RestPose;
317
+ /** The Bestiário's `animate0`: sets the joint rotations of one gait at `time`. `stride` ties cadence and swing to the ground speed. */
318
+ animate0(root: THREE_NS.Object3D, time: number, stride?: StrideParams): GaitSample | undefined;
319
+ /** `animate0` plus the bits that hang loose: the dangling forearm and the flaps of torn flesh. */
320
+ animate(root: THREE_NS.Object3D, time: number, stride?: StrideParams): void;
321
+ /** Moves every joint a fraction toward the value the animation left on it, from the hips to the head (the Bestiário's `cascade`). */
322
+ cascade(rig: RichRig, dt: number): void;
323
+ setPose(rig: RichRig, targets: PoseTargets): void;
324
+ lerpPose: typeof lerpPose;
325
+ };
326
+ /**
327
+ * The rich body's animator: the Bestiário's `animate0`, `animate`, `cascade`, `setPose` and `lerpPose` over the rig the best builder leaves in
328
+ * `root.userData.rig`. The gait maths is the engine's (`animateProfile`, pure CPU); this only reads the rest pose off the rig and writes the result back.
329
+ */
330
+ declare const createRigAnimator: (options?: RigAnimatorOptions) => RigAnimator;
331
+
332
+ type BestTypeCard = {
333
+ /** The name `spec.type` looks the type up by (the Bestiário's `nome`). */
334
+ readonly name: string;
335
+ /** Eye colour as a number (0xRRGGBB). */
336
+ readonly eye: number;
337
+ };
338
+ type BestBuilderOptions = {
339
+ readonly types: readonly BestTypeCard[];
340
+ /** Draws a phase or a seed when the spec has none. Default `Math.random`. */
341
+ readonly random?: () => number;
342
+ };
343
+ type BestBuilder = {
344
+ build(spec: Record<string, any>): any;
345
+ TYPES: Record<string, Record<string, number | boolean>>;
346
+ /** Level of detail: 1 full, 0.5, 0.28 (segments and sphere resolution scale with it). */
347
+ setLOD(factor: number): void;
348
+ /** The shirt material of the last body built (the bake marks it as material 1). */
349
+ topM(): any;
350
+ };
351
+ declare const createBestBuilder: (THREE: Three, { types, random }: BestBuilderOptions) => BestBuilder;
352
+
353
+ type PersonBuilderOptions = {
354
+ /** The stream for stains and phases. The page shared one `rng(77)` across the cast, built in order; default the same. */
355
+ readonly random?: () => number;
356
+ readonly createCanvas?: () => HTMLCanvasElement;
357
+ /** 0 is the highest level of detail (fingers drawn); anything above skips the fingers. Default 0. */
358
+ readonly detail?: number;
359
+ /** `prefers-reduced-motion`: no idle motion in `pose`. */
360
+ readonly reducedMotion?: boolean;
361
+ /** Eye colour of a zombified body by category (default the page's: lento, corredor, brutamonte). */
362
+ readonly zombieEyes?: Readonly<Record<string, number>>;
363
+ };
364
+ type PersonBuilder = {
365
+ build(spec: Record<string, any>, index?: number): any;
366
+ pose(character: any, zombie: boolean, walk: number, time: number): void;
367
+ zombify(character: any, category?: string): void;
368
+ buildPerson(person: Record<string, any>, zombie: boolean): {
369
+ root: any;
370
+ chars: any[];
371
+ };
372
+ };
373
+ declare const createPersonBuilder: (THREE: Three, options?: PersonBuilderOptions) => PersonBuilder;
374
+
375
+ type ExtraContext = {
376
+ THREE: any;
377
+ PI: number;
378
+ U: any;
379
+ Hd: any;
380
+ root: any;
381
+ add: any;
382
+ Bx: any;
383
+ cmat: any;
384
+ ring: any;
385
+ torsoGeo: any;
386
+ legs: any[];
387
+ arms: any[];
388
+ fem: boolean;
389
+ topM: any;
390
+ m: any;
391
+ e: any[];
392
+ k: string;
393
+ sp: any;
394
+ splat: any;
395
+ R: () => number;
396
+ ball: any;
397
+ cap: any;
398
+ hc: any;
399
+ headG: any;
400
+ fig: any;
401
+ upper: any;
402
+ skinM: any;
403
+ };
404
+ /** Clothing and gear by id (`sp.ex` entries `[id, colour?]`), each drawn onto the body parts it names. */
405
+ declare const EXTRAS: Record<string, (context: ExtraContext) => void>;
406
+
407
+ type FaceContext = {
408
+ THREE: any;
409
+ PI: number;
410
+ Hd: any;
411
+ add: any;
412
+ Bx: any;
413
+ cmat: any;
414
+ hc: any;
415
+ sp: any;
416
+ f: string[];
417
+ scaleHex: (hex: number, factor: number) => number;
418
+ };
419
+ /** Face features in the order the page draws them (the order is part of the scene hash): `ids` are the `sp.face` entries that switch one on. */
420
+ declare const FACE_FEATURES: {
421
+ ids: string[];
422
+ apply: ({ Bx, Hd, THREE, add, cmat, f }: FaceContext) => void;
423
+ }[];
424
+
425
+ type HatContext = {
426
+ THREE: any;
427
+ PI: number;
428
+ Hd: any;
429
+ add: any;
430
+ Bx: any;
431
+ cmat: any;
432
+ cap: any;
433
+ c: any;
434
+ k: string;
435
+ sp: any;
436
+ };
437
+ /** Hats by kind (`sp.hat.k`); `c` is the hat's material. */
438
+ declare const HATS: {
439
+ kinds: string[];
440
+ apply: ({ Bx, Hd, PI, add, c, cap, k }: HatContext) => void;
441
+ }[];
442
+
443
+ type HandItemContext = {
444
+ THREE: any;
445
+ PI: number;
446
+ add: any;
447
+ Bx: any;
448
+ cmat: any;
449
+ g: any;
450
+ metal: any;
451
+ dark: any;
452
+ woodM: any;
453
+ };
454
+ /** Items held in a hand by kind (`sp.hand`, `sp.lhand`); `g` is the group on the hand's mount. */
455
+ declare const HAND_ITEMS: Record<string, (context: HandItemContext) => void>;
456
+
457
+ /** A soft round spot, white in the middle and clear at the edge: the sprite of an eye that glows. Needs a canvas; without one, returns no texture. */
458
+ declare const createGlowTexture: (three: Three, createCanvas?: () => {
459
+ width: number;
460
+ height: number;
461
+ getContext(kind: "2d"): unknown;
462
+ }) => THREE_NS.Texture | null;
463
+ type EyeOptions = {
464
+ readonly color: number;
465
+ /** Radius of the eyeball. */
466
+ readonly radius?: number;
467
+ /** Size of the additive glow sprite; 0 draws no glow. Default five times the radius, as the Bestiário does. */
468
+ readonly glow?: number;
469
+ readonly glowOpacity?: number;
470
+ readonly texture?: THREE_NS.Texture | null;
471
+ };
472
+ /** One glowing eye: an unlit ball (emissive by itself) and an additive sprite on top. Add it to the head group at the socket. */
473
+ declare const createEye: (three: Three, { color, radius, glow, glowOpacity, texture }: EyeOptions) => THREE_NS.Group;
474
+ type Eyes = {
475
+ /** The eyeballs: the meshes the builder marked `userData.eye`. They are what a test layer can ask for with `isolate`. */
476
+ readonly markable: readonly THREE_NS.Mesh[];
477
+ /** The additive sprites next to them. */
478
+ readonly glows: readonly THREE_NS.Sprite[];
479
+ /** Brightness 0..1 of both the ball colour and the glow. */
480
+ setBrightness(level: number): void;
481
+ /** A quarter of the light: a zombie that has not noticed anyone. */
482
+ dim(): void;
483
+ /** Dark: the eyes of a dead body. */
484
+ off(): void;
485
+ /** Back to the colour the body was built with. */
486
+ on(): void;
487
+ /** Hides everything that is not an eye (the test area's "eyes only" layer); `false` shows it again. */
488
+ isolate(only: boolean): void;
489
+ };
490
+ /** Finds the eyes of a body (a built zombie or a person with `zombify`) and gives them a dimmer: ball colour and additive glow move together. */
491
+ declare const findEyes: (three: Three, root: THREE_NS.Object3D) => Eyes;
492
+
493
+ /** The numbers of the Elenco's swinging arm (`pends`, line 593). */
494
+ declare const PENDULUM: {
495
+ /** Spring toward the pose of the body: `-26 (angle + base)`. */
496
+ readonly spring: 26;
497
+ /** Damping: `-1.8 v`. */
498
+ readonly damping: 1.8;
499
+ /** Angular speed the blow gives the arm. */
500
+ readonly kick: 6;
501
+ /** Sway added while the body walks: `sin(2 phase) * 9`. */
502
+ readonly walkSway: 9;
503
+ /** Sideways swing of the node: `sin(3 angle) * 0.12`; a shoulder also keeps its side * 0.12. */
504
+ readonly sideSwing: 0.12;
505
+ /** The forearm of a broken shoulder hangs at this angle. */
506
+ readonly forearmHang: -0.05;
507
+ };
508
+ type Pendulum = {
509
+ /** The group that swings (the upper arm of a broken shoulder, the forearm of a broken elbow). */
510
+ readonly node: {
511
+ rotation: {
512
+ x: number;
513
+ z: number;
514
+ };
515
+ };
516
+ readonly kind: 'shoulder' | 'elbow';
517
+ /** The side of the arm, +1 or -1. */
518
+ readonly side: number;
519
+ /** The forearm of a broken shoulder, pulled to `forearmHang`. */
520
+ readonly forearm?: {
521
+ rotation: {
522
+ x: number;
523
+ };
524
+ };
525
+ a: number;
526
+ v: number;
527
+ };
528
+ declare const createPendulum: (node: Pendulum["node"] & {
529
+ rotation: {
530
+ x: number;
531
+ };
532
+ }, kind: Pendulum["kind"], side: number, forearm?: Pendulum["forearm"]) => Pendulum;
533
+ /** One step: `base` is the angle the body carries the arm at (the spine and figure lean, plus the shoulder for an elbow); `walkPhase` sways it while walking. */
534
+ declare const stepPendulum: (pendulum: Pendulum, base: number, dt: number, walkPhase?: number) => void;
535
+
536
+ /** The Elenco's `blobR`: the radii of a ragged round outline (`n` points), from a seeded rough shape. `random` is the stream the page called `R`. */
537
+ declare const blobRadii: (n: number, rough: number, seed: number, random: () => number) => number[];
538
+ /** The Elenco's `blobGeo`: a flat shape from those radii, stretched by `rx` and `ry`. */
539
+ declare const blobGeometry: (three: Three, radii: readonly number[], rx: number, ry: number) => THREE_NS.ShapeGeometry;
540
+ type WoundMaterials = {
541
+ readonly wound: THREE_NS.MeshStandardMaterial;
542
+ readonly bone: THREE_NS.MeshStandardMaterial;
543
+ readonly meat: THREE_NS.MeshStandardMaterial;
544
+ readonly fat: THREE_NS.MeshStandardMaterial;
545
+ readonly rim: THREE_NS.MeshStandardMaterial;
546
+ /** Arsenal `M.hole`, `M.bruise` and `M.flesh`: the dark entry hole, the bruise around it and torn flesh. */
547
+ readonly hole: THREE_NS.MeshStandardMaterial;
548
+ readonly bruise: THREE_NS.MeshStandardMaterial;
549
+ readonly flesh: THREE_NS.MeshStandardMaterial;
550
+ readonly dispose: () => void;
551
+ };
552
+ /** The Elenco's wound materials (`woundM`, `boneM`, `meatM`, `fatM`, `rimM`). */
553
+ declare const createWoundMaterials: (three: Three) => WoundMaterials;
554
+ /**
555
+ * The cut face of a limb (Elenco `makeCut`): a ragged rim, the meat, a ring of fat, the bone with a splintered tip, torn flaps and two strands. `up` faces
556
+ * the cut upward (the flying piece), otherwise downward (the stump). Returns the strands' tips, where the stump drips.
557
+ */
558
+ declare const makeCut: (three: Three, materials: WoundMaterials, random: () => number, group: THREE_NS.Object3D, radius: number, up: boolean) => THREE_NS.Object3D[];
559
+ /** Elenco `makeStump`: the cut face left on the body where the piece left (`flip` turns it over for a head). */
560
+ declare const makeStump: (three: Three, materials: WoundMaterials, random: () => number, parent: THREE_NS.Object3D, position: THREE_NS.Vector3, radius: number, flip?: boolean) => {
561
+ group: THREE_NS.Group;
562
+ strands: THREE_NS.Object3D[];
563
+ };
564
+ /** Elenco `ringWound`: the dark ring and three torn strips of a broken bone that stays in the limb. */
565
+ declare const ringWound: (three: Three, materials: WoundMaterials, parent: THREE_NS.Object3D, radius: number, y?: number) => THREE_NS.Group;
566
+
567
+ type FlyingPiece = {
568
+ readonly object: THREE_NS.Object3D;
569
+ readonly velocity: THREE_NS.Vector3;
570
+ readonly spin: THREE_NS.Vector3;
571
+ rest: boolean;
572
+ /** Whatever the caller attached (the segment id the piece came from). */
573
+ readonly tag?: string;
574
+ };
575
+ type FlyingPiecesOptions = {
576
+ readonly random: () => number;
577
+ /** Height of the ground under a point. Default 0. */
578
+ readonly groundAt?: (x: number, z: number) => number;
579
+ /** Called once when a piece comes to rest: where a pool should start. */
580
+ readonly onRest?: (piece: FlyingPiece, point: THREE_NS.Vector3) => void;
581
+ /** Pieces kept; the oldest is removed beyond it. Default 40 (the game's number). */
582
+ readonly max?: number;
583
+ };
584
+ type FlyingPieces = {
585
+ readonly pieces: readonly FlyingPiece[];
586
+ /** Throws a copy of `source` (in world space, from where it is now) away from the body (Elenco `fly`). `direction` is the side or the blow, in world space. */
587
+ launch(source: THREE_NS.Object3D, scene: THREE_NS.Object3D, direction: THREE_NS.Vector3, dress?: (clone: THREE_NS.Object3D) => void, tag?: string): FlyingPiece;
588
+ /** Adds an already-built object to the simulation with a given velocity (Arsenal `particle`). */
589
+ throwObject(object: THREE_NS.Object3D, velocity: THREE_NS.Vector3, spin: THREE_NS.Vector3, tag?: string): FlyingPiece;
590
+ update(dt: number): void;
591
+ clear(): void;
592
+ };
593
+ /** The torn-off pieces of the rich bodies: they fly, spin, bounce and lie on the ground (Elenco `fly` and the `FLY` step of `updateFx`). */
594
+ declare const createFlyingPieces: (three: Three, { random, groundAt, onRest, max }: FlyingPiecesOptions) => FlyingPieces;
595
+
596
+ /** Arsenal `starGeo`: the ragged star of an exit wound, `points` spikes. */
597
+ declare const starGeometry: (three: Three, random: () => number, radius: number, points: number) => THREE_NS.ShapeGeometry;
598
+ /** Arsenal `woundDecal`: a bruise with a rim and a dark hole, laid on a segment at a world point with the surface normal. */
599
+ declare const woundDecal: (three: Three, materials: WoundMaterials, segment: THREE_NS.Object3D, point: THREE_NS.Vector3, normal: THREE_NS.Vector3, radius: number) => THREE_NS.Mesh;
600
+ /**
601
+ * Arsenal `exitWound`: finds where the bullet left the segment (a ray back along the shot from 0.6 m beyond) and lays a torn star there. Returns the
602
+ * world point and the surface normal, or undefined when the ray missed (the part was too small).
603
+ */
604
+ declare const exitWound: (three: Three, materials: WoundMaterials, random: () => number, segment: THREE_NS.Object3D, point: THREE_NS.Vector3, direction: THREE_NS.Vector3, damage: number) => {
605
+ point: THREE_NS.Vector3;
606
+ normal: THREE_NS.Vector3;
607
+ radius: number;
608
+ } | undefined;
609
+ /**
610
+ * Arsenal `gouge`: a blow of 55 or more tears chunks out (7 from 150, 4 from 55, 2 below), leaves a crater with a flesh rim and, on a limb, a splinter
611
+ * of bone. `fling` receives each chunk with its velocity and spin. Returns the crater.
612
+ */
613
+ declare const gouge: (three: Three, materials: WoundMaterials, random: () => number, segment: THREE_NS.Object3D, segmentKind: string, point: THREE_NS.Vector3, normal: THREE_NS.Vector3, direction: THREE_NS.Vector3, damage: number, fling: (chunk: THREE_NS.Object3D, velocity: THREE_NS.Vector3, spin: THREE_NS.Vector3) => void) => THREE_NS.Mesh;
614
+ /** Arsenal `explodeSeg`: 14 chunks the size of the part, thrown from its centre along the blow. */
615
+ declare const explodeChunks: (three: Three, materials: WoundMaterials, random: () => number, box: THREE_NS.Box3, direction: THREE_NS.Vector3, fling: (chunk: THREE_NS.Object3D, velocity: THREE_NS.Vector3, spin: THREE_NS.Vector3) => void) => THREE_NS.Vector3;
616
+
617
+ /** The group that stands for one segment of the engine's tree on a drawn body, with what the wounds need to cut it off. */
618
+ type WoundNode = {
619
+ /** The group that leaves (its children go with it). */
620
+ readonly node: THREE_NS.Object3D;
621
+ /** Radius of the cut face. */
622
+ readonly radius: number;
623
+ /** For an arm: which one in `rig.arms`, and the joint that swings when the bone breaks. */
624
+ readonly arm?: {
625
+ readonly index: number;
626
+ readonly joint: 'shoulder' | 'elbow';
627
+ };
628
+ readonly leg?: number;
629
+ /** The cut face of a head faces the other way. */
630
+ readonly flip?: boolean;
631
+ };
632
+ type WoundBody = {
633
+ readonly root: THREE_NS.Object3D;
634
+ readonly rig: RichRig;
635
+ readonly nodes: Partial<Record<SegmentId, WoundNode>>;
636
+ };
637
+ /**
638
+ * Maps the rich body the best builder makes onto the engine's segments. The left side is +x, as the engine has it. The drawn body has no hand or foot
639
+ * group of its own: they ride with the forearm and the shin, so a hand or foot that is lost takes its parent's node only when the parent is lost too.
640
+ */
641
+ declare const woundBodyOf: (root: THREE_NS.Object3D) => WoundBody | undefined;
642
+
643
+ /** The part of a fluid world the bridge needs; `FluidWorld` from `@adatechnology/game-fluids` has it. The package does not import the fluids packages. */
644
+ type BloodSink = {
645
+ splash(liquid: string, point: Vec3$1, liters: number, velocity?: Vec3$1): void;
646
+ };
647
+ type FluidsBridgeOptions = {
648
+ readonly sink: BloodSink;
649
+ /** Id of the blood in the fluid world. Default `blood`. */
650
+ readonly liquid?: string;
651
+ /** Metres per second a pressurised leak (a cut artery) throws its drops. Default 1.4. */
652
+ readonly spurtSpeed?: number;
653
+ };
654
+ type FluidsBridge = {
655
+ /** Reads a body's events: `leak` (bleeding) becomes blood on the surface below. Other events are ignored. */
656
+ handle(event: DestructionEvent): void;
657
+ /** The blood a cut, a burst or a split spills at once, at the world point the piece left from. */
658
+ burst(change: 'severed' | 'destroyed' | 'split', point: Vec3$1): void;
659
+ /** Blood from the engine's own bleeders: `litersPerSecond` over `dt` seconds at `point`. */
660
+ bleed(point: Vec3$1, litersPerSecond: number, dt: number, direction?: Vec3$1, pressurized?: boolean): void;
661
+ };
662
+ /**
663
+ * Connects the wounds to the fluids packages, with the fluid world injected: the body keeps its own bleeders (rate, duration, spurt) and this turns them
664
+ * into blood, so the stains, pools and drying are the fluids' business and the character code draws none.
665
+ */
666
+ declare const createFluidsBridge: ({ sink, liquid, spurtSpeed }: FluidsBridgeOptions) => FluidsBridge;
667
+
668
+ type WoundsOptions = {
669
+ readonly random: () => number;
670
+ /** Where the flying pieces land. Default: the parent of the body. */
671
+ readonly scene: THREE_NS.Object3D;
672
+ readonly groundAt?: (x: number, z: number) => number;
673
+ /** The fluids bridge, when the game has the fluids packages. Without it the wounds draw no blood on the ground. */
674
+ readonly bridge?: FluidsBridge;
675
+ readonly materials?: WoundMaterials;
676
+ /** Pieces alive at once. Default 40. */
677
+ readonly maxPieces?: number;
678
+ };
679
+ type Wounds = {
680
+ readonly materials: WoundMaterials;
681
+ readonly flying: FlyingPieces;
682
+ /** Starts watching a body. Returns the same body for chaining. */
683
+ track(body: WoundBody): WoundBody;
684
+ untrack(body: WoundBody): void;
685
+ /** Reads an event of the body (a `body`, `hole` or `leak` event of the engine) and draws what it says. Events of other objects are the caller's to filter. */
686
+ handle(body: WoundBody, event: DestructionEvent): void;
687
+ /** Sets the walk phase the swinging arms sway to; `undefined` when the body stands. */
688
+ setWalkPhase(body: WoundBody, phase: number | undefined): void;
689
+ /** Which segments are gone from the drawn body. */
690
+ goneOf(body: WoundBody): ReadonlySet<SegmentId>;
691
+ /** Steps the swinging arms and the flying pieces. Call it after the animation of the frame; `applyLimp` then bends the legs that are hurt. */
692
+ update(dt: number): void;
693
+ applyLimp(body: WoundBody): void;
694
+ dispose(): void;
695
+ };
696
+ /**
697
+ * Wounds of the rich body (`near` level of the damage model): a cut leaves a stump and a piece that flies, a broken bone leaves a ring wound and a
698
+ * swinging arm or a limping leg, a part destroyed bursts into chunks, a torso hit hard splits in two, and a bullet leaves an entry decal and, when it
699
+ * went through, a torn exit. The rule (what is cut, when) is the engine's; this draws the Elenco and Arsenal dynamics for it, with the random stream
700
+ * injected so a scene can be repeated.
701
+ */
702
+ declare const createWounds: (three: Three, options: WoundsOptions) => Wounds;
703
+
704
+ /** A crowd on the instanced path: variants baked on demand from the rich builder, one batch per variant and level of detail, members walking a field. */
705
+ type Crowd = {
706
+ readonly group: THREE_NS.Group;
707
+ spawn(count: number): void;
708
+ clear(): void;
709
+ update(dt: number, camera: THREE_NS.Camera): void;
710
+ readonly count: number;
711
+ readonly bakeMs: number;
712
+ readonly drawCalls: number;
713
+ readonly triangles: number;
714
+ /** Triangles of one body at each level of detail (the budget the benchmark checks). */
715
+ trianglesPerLevel(typeId: string): readonly number[];
716
+ };
717
+
718
+ /** One undead type as the area needs it: the content pack's own record has all of these fields. */
719
+ type UndeadCard = {
720
+ readonly id: string;
721
+ readonly name: Readonly<Record<string, string>>;
722
+ readonly eye: {
723
+ readonly color: string;
724
+ };
725
+ readonly gait: Readonly<Record<string, number | boolean>>;
726
+ };
727
+ /** A profession of the people pack (look, accessories, what it holds). */
728
+ type ProfessionCard = {
729
+ readonly id: string;
730
+ readonly name: Readonly<Record<string, string>>;
731
+ readonly female?: boolean;
732
+ readonly look: {
733
+ readonly skin: string;
734
+ readonly hair: {
735
+ readonly style: string;
736
+ readonly color: string;
737
+ };
738
+ readonly hat?: {
739
+ readonly kind: string;
740
+ readonly color: string;
741
+ };
742
+ readonly face?: readonly string[];
743
+ readonly top: {
744
+ readonly color: string;
745
+ readonly sleeve: string;
746
+ readonly plaid?: string;
747
+ };
748
+ readonly pants: {
749
+ readonly color: string;
750
+ };
751
+ readonly shoes: string;
752
+ };
753
+ readonly accessories: readonly {
754
+ readonly id: string;
755
+ readonly color?: string;
756
+ }[];
757
+ readonly hand?: string;
758
+ readonly leftHand?: string;
759
+ };
760
+ /** The part of the undead engine the area uses (`createUndead(kit)` has all of it). */
761
+ type UndeadLike = {
762
+ create(spec: Pick<CharacterSpec, 'seed'> & Partial<CharacterSpec>, options?: {
763
+ readonly type?: string;
764
+ }): DamageCharacter;
765
+ turn(person: DamageCharacter, options?: {
766
+ readonly type?: string;
767
+ }): DamageCharacter;
768
+ traitsOf(character: DamageCharacter): {
769
+ readonly type: string;
770
+ readonly speeds: Readonly<Record<string, number>>;
771
+ } | undefined;
772
+ speedFor(traits: {
773
+ readonly type: string;
774
+ readonly speeds: Readonly<Record<string, number>>;
775
+ }, context: {
776
+ readonly moment: string;
777
+ readonly limp?: boolean;
778
+ readonly crawling?: boolean;
779
+ }): number;
780
+ onDeath?(character: DamageCharacter): readonly {
781
+ readonly kind: string;
782
+ }[];
783
+ infect?(wound: 'bite' | 'scratch' | 'laceration' | undefined, random: () => number): {
784
+ advance(seconds: number): unknown;
785
+ readonly reanimated: boolean;
786
+ } | undefined;
787
+ };
788
+ type CharactersTestOptions = {
789
+ /** `en` or `pt-BR` (default). */
790
+ readonly locale?: string;
791
+ /** Builds the kit the area uses; called again when the bench changes the penetration model or the death rule. Default `createCharacters` with `packs`. */
792
+ readonly createKit?: (options: {
793
+ readonly penetration: 'physical' | 'team-game';
794
+ readonly deathRule: 'vitality-or-head' | 'head-only';
795
+ }) => CharacterKit;
796
+ /** Packs for the default kit (people, uniformed, undead). */
797
+ readonly packs?: readonly CharacterPack[];
798
+ readonly undeadTypes?: readonly UndeadCard[];
799
+ readonly createUndead?: (kit: CharacterKit) => UndeadLike;
800
+ readonly professions?: readonly ProfessionCard[];
801
+ /** Where the blood of the wounds goes (the room's fluid world: `room.fluids`). Without it the wounds draw no blood on the ground. */
802
+ readonly fluids?: BloodSink;
803
+ /** Seed of the draws of the area (default 1). */
804
+ readonly seed?: number;
805
+ /** Crowd types to bake for the instanced field, by undead id (default walker and runner). */
806
+ readonly crowdTypes?: readonly string[];
807
+ /** Triangles a baked body may have at each level of detail (default 2500, 800, 250, the Bancada de Hordas budget). Given explicitly, every level is a hard check. */
808
+ readonly triangleBudget?: readonly number[];
809
+ };
810
+
811
+ /** What a scenario needs of the room: the pass/fail line and time. The test room's `ScenarioContext` has both. */
812
+ type AreaContext = {
813
+ expect(condition: boolean, label: string, data?: Record<string, unknown>): void;
814
+ step(seconds: number): void;
815
+ };
816
+
817
+ declare const CHARACTERS_AREA_ID = "characters_area";
818
+ declare const ZOMBIE_ITEM_PREFIX = "zombie_";
819
+ declare const PERSON_ITEM_PREFIX = "person_";
820
+ /**
821
+ * The characters test area as a `TestableModule`: the undead types and the professions as bodies the room can shoot (each one a `DestructibleImpl` of the damage model, drawn with the
822
+ * rich builders, wounds, eyes and gait), a shooting lane, a crowd on the instanced path, and a bench with the damage rules, the hits, the protection, the moment and the layers.
823
+ * Everything comes in by option: the packs of content (people, undead) are the host page's, so this package knows no game.
824
+ */
825
+ declare function createCharactersTestModule(options?: CharactersTestOptions): TestableModule<AreaContext> & {
826
+ readonly entries: ReadonlyMap<string, unknown>;
827
+ readonly crowd: Crowd | null;
828
+ };
829
+
830
+ /**
831
+ * What the "Hit" control of the bench does to the body: typical loads, in joules and millimetres, as the arsenal's calibre table gives them.
832
+ * `caliberId` is used only when the kit's table knows it (the `team-game` model).
833
+ */
834
+ type HitPreset = {
835
+ readonly id: string;
836
+ readonly hit: Omit<BodyHit, 'point' | 'direction'>;
837
+ readonly range?: number;
838
+ };
839
+ declare const HIT_PRESETS: readonly HitPreset[];
840
+
841
+ /**
842
+ * The material the parts of a living body carry (`BODY_MATERIAL` of the engine), as a destruction world needs it declared: pass it in the world's material list
843
+ * (`worldOptions.materials` of the test room) before loading the characters area. The body model owns the damage; the world only needs the density and thermal data
844
+ * to raycast, blast and burn a body like any other object. Numbers are those of soft tissue (about 1050 kg/m3).
845
+ */
846
+ declare const BODY_MATERIAL_DEF: {
847
+ readonly id: "flesh";
848
+ readonly density: 1050;
849
+ readonly hpPerKg: 15;
850
+ readonly penetration: 1.2;
851
+ readonly ductility: 0.8;
852
+ readonly fracture: "tear";
853
+ readonly fragmentSize: readonly [0.03, 0.1];
854
+ readonly damageScale: {
855
+ readonly slash: 1.6;
856
+ };
857
+ readonly hitDebris: "drips";
858
+ readonly breakDebris: "drips";
859
+ readonly thermal: {
860
+ readonly ignitionC: 260;
861
+ readonly flameC: 700;
862
+ readonly fuelFraction: 0.25;
863
+ readonly burnRate: 0.004;
864
+ readonly conductivity: 0.5;
865
+ readonly heatCapacity: 3.5;
866
+ readonly smoke: "grey";
867
+ };
868
+ readonly tags: readonly ["organic", "body"];
869
+ readonly compressive: 1000;
870
+ };
871
+ /** Puts the body material in a destruction world that does not know it yet (`room.world`); a world that has it is left alone. Call it before `room.loadModule`. */
872
+ declare const registerBodyMaterial: (world: {
873
+ readonly materials: Map<string, unknown>;
874
+ }) => void;
875
+
876
+ export { type AreaContext, BAKE_NEUTRAL_POSE, BODY_CHUNK, BODY_MATERIAL_DEF, type BakeBuilders, type BakeSpec, type BakedBody, type BestBuilder, type BestBuilderOptions, type BestTypeCard, type BloodSink, type BodyBatch, type BodyBatchOptions, type BodyChunkOptions, type BodyInstance, type BodyJoints, type BodyMaterialParams, type BodyVariant, type BuildInstancedBodyParams, CHARACTERS_AREA_ID, type CharactersTestOptions, EXTRAS, type EyeOptions, type Eyes, FACE_FEATURES, type FluidsBridge, type FluidsBridgeOptions, type FlyingLimb, type FlyingLimbs, type FlyingLimbsOptions, type FlyingPiece, type FlyingPieces, type FlyingPiecesOptions, HAND_ITEMS, HATS, HIT_PRESETS, type HitPreset, LOD_BAKE, LOD_BATCH_CAPACITY, LOD_CASTS_SHADOW, LOD_DISTANCES, LOD_SEGMENTS, type LimbEffects, type LimbLaunch, PENDULUM, PERSON_ITEM_PREFIX, type Pendulum, type PersonBuilder, type PersonBuilderOptions, type PoseTargets, type ProfessionCard, type RichRig, type RigAnimator, type RigAnimatorOptions, type Three, type UndeadCard, type UndeadLike, type VariantType, type WoundBody, type WoundMaterials, type WoundNode, type Wounds, type WoundsOptions, ZOMBIE_ITEM_PREFIX, bakeBody, bakeVariants, blobGeometry, blobRadii, buildInstancedBody, createBestBuilder, createBodyBatch, createBodyChunk, createBodyMaterial, createCharactersTestModule, createEye, createFluidsBridge, createFlyingLimbs, createFlyingPieces, createGlowTexture, createPendulum, createPersonBuilder, createRigAnimator, createVariantBatches, createWoundMaterials, createWounds, exitWound, explodeChunks, findEyes, gouge, jointsOf, lodAt, makeCut, makeStump, registerBodyMaterial, ringWound, starGeometry, stepPendulum, woundBodyOf, woundDecal };