@idosgames/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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -2
  3. package/dist/PartsManager-CAVBKvp6.d.ts +589 -0
  4. package/dist/PartsManager-Cn3MU-VS.d.cts +589 -0
  5. package/dist/chunk-64D567CX.js +1 -0
  6. package/dist/chunk-BU66AZ2Z.js +1 -0
  7. package/dist/chunk-CGF2TQVK.js +1 -0
  8. package/dist/chunk-OF4BH3JZ.js +2 -0
  9. package/dist/chunk-WKHHFTK2.js +1 -0
  10. package/dist/chunk-Z5I76WRJ.js +1 -0
  11. package/dist/contract-DhZuKNIf.d.cts +24 -0
  12. package/dist/contract-DhZuKNIf.d.ts +24 -0
  13. package/dist/gltf/index.cjs +1 -0
  14. package/dist/gltf/index.d.cts +150 -0
  15. package/dist/gltf/index.d.ts +150 -0
  16. package/dist/gltf/index.js +1 -0
  17. package/dist/image-D5NaQeFz.d.cts +8 -0
  18. package/dist/image-D5NaQeFz.d.ts +8 -0
  19. package/dist/index.cjs +1 -0
  20. package/dist/index.d.cts +65 -0
  21. package/dist/index.d.ts +65 -0
  22. package/dist/index.js +1 -0
  23. package/dist/rig2d/editor/index.cjs +81 -0
  24. package/dist/rig2d/editor/index.d.cts +25 -0
  25. package/dist/rig2d/editor/index.d.ts +25 -0
  26. package/dist/rig2d/editor/index.js +81 -0
  27. package/dist/rig2d/index.cjs +17 -0
  28. package/dist/rig2d/index.d.cts +190 -0
  29. package/dist/rig2d/index.d.ts +190 -0
  30. package/dist/rig2d/index.js +17 -0
  31. package/dist/rig2d/phaser/index.cjs +2 -0
  32. package/dist/rig2d/phaser/index.d.cts +106 -0
  33. package/dist/rig2d/phaser/index.d.ts +106 -0
  34. package/dist/rig2d/phaser/index.js +1 -0
  35. package/dist/rig2d/three/index.cjs +2 -0
  36. package/dist/rig2d/three/index.d.cts +63 -0
  37. package/dist/rig2d/three/index.d.ts +63 -0
  38. package/dist/rig2d/three/index.js +1 -0
  39. package/dist/thumbnail-BhGEEXux.d.cts +49 -0
  40. package/dist/thumbnail-Bp-jK70T.d.ts +49 -0
  41. package/dist/voxel/index.cjs +54 -0
  42. package/dist/voxel/index.d.cts +972 -0
  43. package/dist/voxel/index.d.ts +972 -0
  44. package/dist/voxel/index.js +54 -0
  45. package/package.json +101 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 iDos Games
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # @idosgames/characters
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Characters of the iDosGames platform in one standard for games and videos.
4
+
5
+ | Subpath | What | Peers |
6
+ | --- | --- | --- |
7
+ | `@idosgames/characters` | the actor contract (emotions, mouth visemes, poses, sockets, style families), lip-sync from text, language, seeded random | — |
8
+ | `@idosgames/characters/voxel` | voxel characters: `CharacterSpec` recipes (skeleton template, boxes on bones, palette, pixel faces), the model, animator, emotions, emotes, `VoxelActor` | `three` |
9
+ | `@idosgames/characters/rig2d` | layered 2D characters on a skeleton with the full wardrobe (clothes, hair, weapons, shields, helmets…) in vector art, the canvas renderer, AI recipes (`parseRig2dRecipe`) | — |
10
+ | `@idosgames/characters/rig2d/three` | the 2D character in a three.js scene (`Rig2dView`) | `three` |
11
+ | `@idosgames/characters/rig2d/phaser` | the 2D character on Phaser | `phaser` |
12
+ | `@idosgames/characters/rig2d/editor` | the 2D character editor | `react`, `phaser` |
13
+ | `@idosgames/characters/gltf` | GLB characters on the `humanoid-v1` skeleton: loading, the contract check, the shared clip library, face shape keys | `three` |
14
+
15
+ Every actor takes the same commands — `setEmotion`, `setViseme`, a pose, a prop on a socket — and
16
+ moves only by `update(dt)`, so a video stepped by its own clock renders the same frame every time.
17
+
18
+ ```ts
19
+ import { layoutSpeech, visemeAt } from "@idosgames/characters";
20
+ import { createCatalog, heroRig, HERO_ANIMATIONS } from "@idosgames/characters/rig2d";
21
+ import { Rig2dView } from "@idosgames/characters/rig2d/three";
22
+
23
+ const hero = new Rig2dView({ rig: heroRig, catalog: createCatalog(), seed: 7 });
24
+ scene.add(hero.root);
25
+ await hero.ready();
26
+ hero.play(HERO_ANIMATIONS.talk);
27
+ hero.setEmotion("happy");
28
+
29
+ const speech = layoutSpeech("Ты опять съел мой обед?", 2.1);
30
+ // each frame: hero.setViseme(visemeAt(speech.visemes, t)); hero.update(1 / 30);
31
+ ```
32
+
33
+ Why a package and not a module: it is a data format with its runtime that several catalog modules
34
+ share (voxelcraft, idle-rpg, video-studio). A project extends it with data — its own recipes and part
35
+ packs — never by copying its source. See the "Package or module" rule in the repository's CLAUDE.md.
@@ -0,0 +1,589 @@
1
+ import { E as Emotion, V as Viseme } from './contract-DhZuKNIf.js';
2
+
3
+ /** A kind of part — "Hair", "Hat", "Weapon". Declared by a rig (rigs/{rig}/parts.ts). */
4
+ type PartTypeId = string;
5
+ /** A bone or slot by its name in the skeleton data ("head", "hand_far"). */
6
+ type BoneId = string;
7
+ /** What a colour paints — "Skin", "Hair", "Beard", "Brow". Declared by a rig. */
8
+ type ColorTargetId = string;
9
+ /** An image of the art, by the path attachments name it ("top/top_12", "arm_l"). */
10
+ type ArtId = string;
11
+ /** A colour as "#rrggbb". */
12
+ type Hex = string;
13
+ declare const WHITE: Hex;
14
+ /** "#rrggbb" → 0xrrggbb (what Phaser's tint takes). Anything unreadable is white. */
15
+ declare function hexToNumber(hex: Hex): number;
16
+ declare function numberToHex(color: number): Hex;
17
+ /** An RGBA colour, 0..1 per channel (slot and attachment colours). */
18
+ interface Color {
19
+ r: number;
20
+ g: number;
21
+ b: number;
22
+ a: number;
23
+ }
24
+ /** "rrggbbaa" (or "rrggbb") → Color. */
25
+ declare function colorOf(hex: string | undefined): Color;
26
+
27
+ interface ArtDef {
28
+ /** SVG markup (no <svg> wrapper), in the image's px: 0..width × 0..height, y down. */
29
+ svg: string;
30
+ /** The art paints part of itself with `currentColor` (an iris): this is its colour unless a colour
31
+ * target drawn into the art (ColorTargetDef.into "art") gives another. */
32
+ paint?: string;
33
+ }
34
+ /** How an image is drawn this time: the colour of its painted part, the light on it. */
35
+ interface ArtStyle {
36
+ /** The colour of its `currentColor` part (an iris); omitted — art.paint. */
37
+ paint?: string | null;
38
+ /** Towards the light in its pixels (core/shading); omitted — not shaded. */
39
+ light?: {
40
+ x: number;
41
+ y: number;
42
+ } | null;
43
+ }
44
+ interface ArtPack {
45
+ id: string;
46
+ art: Readonly<Record<ArtId, ArtDef>>;
47
+ }
48
+ declare class ArtCatalog {
49
+ private readonly art;
50
+ constructor(packs?: readonly ArtPack[]);
51
+ /** Adds a pack; an image already present is replaced (a later pack overrides). */
52
+ register(pack: ArtPack): this;
53
+ get(path: ArtId): ArtDef | undefined;
54
+ has(path: ArtId): boolean;
55
+ ids(): ArtId[];
56
+ }
57
+
58
+ declare const LOOK_VERSION = 1;
59
+ interface CharacterLook {
60
+ v: typeof LOOK_VERSION;
61
+ /** Type → part id; null — taken off. A missing type is left as it is. */
62
+ parts?: Partial<Record<PartTypeId, string | null>>;
63
+ colors?: Partial<Record<ColorTargetId, Hex>>;
64
+ }
65
+ /** Later looks win, type by type and colour by colour (a sword over a class's default weapon). */
66
+ declare function mergeLooks(...looks: ReadonlyArray<CharacterLook | null | undefined>): CharacterLook;
67
+ /** A look from untrusted JSON (custom data, a pasted preset); null when it is not one. */
68
+ declare function parseLook(value: unknown): CharacterLook | null;
69
+
70
+ interface Constraint {
71
+ readonly data: ConstraintData;
72
+ setupPose(): void;
73
+ sort(sk: Skeleton): void;
74
+ update(sk: Skeleton): void;
75
+ }
76
+
77
+ declare class BonePose {
78
+ readonly bone: Bone;
79
+ x: number;
80
+ y: number;
81
+ rotation: number;
82
+ scaleX: number;
83
+ scaleY: number;
84
+ shearX: number;
85
+ shearY: number;
86
+ a: number;
87
+ b: number;
88
+ c: number;
89
+ d: number;
90
+ worldX: number;
91
+ worldY: number;
92
+ /** == the skeleton's update counter: the world is current / the local must be re-derived. */
93
+ world: number;
94
+ local: number;
95
+ constructor(bone: Bone);
96
+ copyLocal(from: BonePose): void;
97
+ update(sk: Skeleton): void;
98
+ updateWorldTransform(sk: Skeleton): void;
99
+ /** World → local (after a constraint moved the world matrix). */
100
+ updateLocalTransform(sk: Skeleton): void;
101
+ private set5;
102
+ /** The local pose is about to be edited (IK): the world becomes stale. */
103
+ modifyLocal(sk: Skeleton): void;
104
+ /** The world is about to be edited (a transform constraint): the local is re-derived on demand. */
105
+ modifyWorld(sk: Skeleton): void;
106
+ private resetWorld;
107
+ }
108
+ declare class Bone {
109
+ readonly data: BoneData;
110
+ readonly pose: BonePose;
111
+ readonly applied: BonePose;
112
+ parent: Bone | null;
113
+ readonly children: Bone[];
114
+ sorted: boolean;
115
+ constructor(data: BoneData);
116
+ setupPose(): void;
117
+ }
118
+ declare class Slot {
119
+ readonly data: SlotData;
120
+ readonly bone: Bone;
121
+ readonly color: Color;
122
+ /** The key of the shown attachment (what timelines switch), null — none. */
123
+ attachmentKey: string | null;
124
+ attachment: Attachment | null;
125
+ constructor(data: SlotData, bone: Bone);
126
+ }
127
+ declare class Skeleton {
128
+ readonly data: SkeletonData;
129
+ readonly bones: Bone[];
130
+ readonly slots: Slot[];
131
+ readonly constraints: Constraint[];
132
+ /** The skins worn, combined: a later skin's attachment wins for the same slot and key. */
133
+ private skins;
134
+ scaleX: number;
135
+ scaleY: number;
136
+ x: number;
137
+ y: number;
138
+ updateId: number;
139
+ private cache;
140
+ constructor(data: SkeletonData);
141
+ bone(name: string): Bone;
142
+ slot(name: string): Slot;
143
+ setSkins(skins: readonly Skin[]): void;
144
+ getAttachment(slotIndex: number, key: string): Attachment | null;
145
+ /** Re-reads every slot's attachment from its key (after a skin or key change). */
146
+ resolveAttachments(): void;
147
+ /** Bones, constraints and slots back to the setup pose (what animation then writes over). */
148
+ setupPose(): void;
149
+ updateWorldTransform(): void;
150
+ /** The update order: parents before children, constraints where they read and write. */
151
+ private updateCache;
152
+ sortBone(bone: Bone): void;
153
+ sortReset(bones: readonly Bone[]): void;
154
+ pushConstraint(c: Constraint): void;
155
+ }
156
+
157
+ interface AnimationEventKey {
158
+ time: number;
159
+ name: string;
160
+ }
161
+ interface Timeline {
162
+ /** The bone or slot it writes (for overlays that take only some timelines). */
163
+ readonly bone?: number;
164
+ readonly slot?: number;
165
+ apply(sk: Skeleton, time: number): void;
166
+ }
167
+ /** Which bones and slots (by index) an overlay takes from an animation. */
168
+ interface TimelineFilter {
169
+ bones: ReadonlySet<number>;
170
+ slots: ReadonlySet<number>;
171
+ }
172
+ declare class Animation {
173
+ readonly name: string;
174
+ readonly duration: number;
175
+ private readonly timelines;
176
+ readonly events: AnimationEventKey[];
177
+ constructor(name: string, duration: number, timelines: Timeline[], events: AnimationEventKey[]);
178
+ /** Writes the animation at `time` into the skeleton's pose (call after skeleton.setupPose()). */
179
+ apply(sk: Skeleton, time: number): void;
180
+ /** Only the timelines of the filter's bones and slots (an overlay, e.g. the blink). */
181
+ applyOnly(sk: Skeleton, time: number, filter: TimelineFilter): void;
182
+ /** Does it animate any of the filter's bones or slots. */
183
+ touches(filter: TimelineFilter): boolean;
184
+ }
185
+
186
+ interface BoneData {
187
+ index: number;
188
+ name: string;
189
+ parent: BoneData | null;
190
+ children: BoneData[];
191
+ length: number;
192
+ x: number;
193
+ y: number;
194
+ rotation: number;
195
+ scaleX: number;
196
+ scaleY: number;
197
+ }
198
+ interface SlotData {
199
+ index: number;
200
+ name: string;
201
+ bone: BoneData;
202
+ /** The attachment key shown in the setup pose, null — none. */
203
+ attachment: string | null;
204
+ color: Color;
205
+ }
206
+ type TransformProp = "rotate" | "x" | "y" | "scaleX" | "scaleY" | "shearY";
207
+ interface ToProperty {
208
+ prop: TransformProp;
209
+ offset: number;
210
+ max: number;
211
+ scale: number;
212
+ }
213
+ interface FromProperty {
214
+ prop: TransformProp;
215
+ offset: number;
216
+ to: ToProperty[];
217
+ }
218
+ interface IkData {
219
+ type: "ik";
220
+ index: number;
221
+ name: string;
222
+ bones: BoneData[];
223
+ target: BoneData;
224
+ mix: number;
225
+ bendDirection: 1 | -1;
226
+ }
227
+ interface TransformData {
228
+ type: "transform";
229
+ index: number;
230
+ name: string;
231
+ source: BoneData;
232
+ bones: BoneData[];
233
+ offsets: Record<TransformProp, number>;
234
+ mix: Record<TransformProp, number>;
235
+ properties: FromProperty[];
236
+ }
237
+ type ConstraintData = IkData | TransformData;
238
+ interface RegionAttachment {
239
+ type: "region";
240
+ /** The key slots and timelines use ("top", "weapon_shot"). */
241
+ key: string;
242
+ /** The image it draws ("top/top_12") — the art is looked up by it. */
243
+ path: string;
244
+ x: number;
245
+ y: number;
246
+ rotation: number;
247
+ scaleX: number;
248
+ scaleY: number;
249
+ width: number;
250
+ height: number;
251
+ color: Color;
252
+ }
253
+ interface MeshAttachment {
254
+ type: "mesh";
255
+ key: string;
256
+ path: string;
257
+ /** u, v per vertex, 0..1 over the image, v down. */
258
+ uvs: Float32Array;
259
+ triangles: Uint16Array;
260
+ /** Plain: x, y per vertex in the slot bone's space. Weighted: per vertex n, then n × (bone, x, y, weight). */
261
+ vertices: Float32Array;
262
+ weighted: boolean;
263
+ vertexCount: number;
264
+ width: number;
265
+ height: number;
266
+ color: Color;
267
+ }
268
+ type Attachment = RegionAttachment | MeshAttachment;
269
+ interface Skin {
270
+ name: string;
271
+ /** Per slot index: attachment key → attachment. */
272
+ attachments: Map<number, Map<string, Attachment>>;
273
+ }
274
+ interface SkeletonData {
275
+ width: number;
276
+ height: number;
277
+ x: number;
278
+ y: number;
279
+ bones: BoneData[];
280
+ slots: SlotData[];
281
+ constraints: ConstraintData[];
282
+ skins: Map<string, Skin>;
283
+ /** Skin names in the order they were given (the order ‹ › lists them). */
284
+ skinOrder: string[];
285
+ animations: Map<string, Animation>;
286
+ events: string[];
287
+ bone(name: string): BoneData;
288
+ slot(name: string): SlotData;
289
+ }
290
+ type Json = any;
291
+ declare function readSkeletonData(json: Json): SkeletonData;
292
+ /** Every image the skins use, with its size (the attachment's width × height) — what art is drawn on. */
293
+ declare function imageSizes(data: SkeletonData): ReadonlyMap<string, {
294
+ w: number;
295
+ h: number;
296
+ }>;
297
+ /** The images a skin draws. */
298
+ declare function skinImages(data: SkeletonData, skinName: string): string[];
299
+
300
+ interface PlayOptions {
301
+ /** Default true (loops). */
302
+ loop?: boolean;
303
+ speed?: number;
304
+ /** Cross-fade seconds; default the rig's mix. 0 — cut. */
305
+ mix?: number;
306
+ /** Start this many seconds in. */
307
+ from?: number;
308
+ }
309
+ interface AnimationEvent {
310
+ /** An event of the animation ("Attack", "Step", "Hit", "Die") or "end" when a non-looping one finishes. */
311
+ name: string;
312
+ animation: string;
313
+ }
314
+ type Listener$1 = (e: AnimationEvent) => void;
315
+ interface BlinkOptions {
316
+ /** The animation the blink is cut from, and the window of it. */
317
+ animation: string;
318
+ from: number;
319
+ to: number;
320
+ /** The bones and slots it takes (by name): the eyes. */
321
+ bones: readonly string[];
322
+ slots: readonly string[];
323
+ /** Seconds between blinks, a random pick in [min, max]. */
324
+ every: readonly [number, number];
325
+ }
326
+ declare class Animator {
327
+ private readonly skeleton;
328
+ private readonly data;
329
+ readonly defaultMix: number;
330
+ private track;
331
+ private previous;
332
+ private mixTime;
333
+ private mixDuration;
334
+ private readonly listeners;
335
+ private readonly snapshot;
336
+ private readonly slotSnapshot;
337
+ private readonly ikSnapshot;
338
+ private blink;
339
+ constructor(skeleton: Skeleton, data: SkeletonData, defaultMix?: number);
340
+ /** Turns the blink overlay on (null — off). */
341
+ setBlink(options: BlinkOptions | null): void;
342
+ /** Blink now (the overlay must be on). */
343
+ blinkNow(): void;
344
+ /** Is the overlay mid-blink. */
345
+ blinking(): boolean;
346
+ /** The source of chance for the blink (default Math.random); a video passes a seeded one so a
347
+ * frame is the same on every render. */
348
+ setRandom(random: () => number): void;
349
+ private random;
350
+ private nextBlink;
351
+ names(): string[];
352
+ has(name: string): boolean;
353
+ play(name: string, options?: PlayOptions): boolean;
354
+ current(): string | null;
355
+ /** Seconds into the current animation. */
356
+ time(): number;
357
+ duration(): number;
358
+ finished(): boolean;
359
+ setSpeed(speed: number): void;
360
+ /** An event by name, "end", or "*" for all. Returns the unsubscribe. */
361
+ on(name: string, listener: Listener$1): () => void;
362
+ /** Advances by dt seconds, fires the events passed, and writes the pose into the skeleton. */
363
+ update(dt: number): void;
364
+ /** Rebuilds the pose for the current times (no time passes). */
365
+ pose(): void;
366
+ private advanceBlink;
367
+ private local;
368
+ /** Events in [from, to) of the track's time, unrolled over loops. */
369
+ private collect;
370
+ private save;
371
+ /** current = saved + (current − saved) × alpha; rotation the short way round. */
372
+ private blendFromSaved;
373
+ }
374
+
375
+ interface RuleView {
376
+ /** The worn skin of a type (null — none), after the player's choice and the hidden flag. */
377
+ worn(type: PartTypeId): string | null;
378
+ /** Replaces what a type contributes to the character (null — nothing). */
379
+ setWorn(type: PartTypeId, skin: string | null): void;
380
+ /** Does the data have this skin. */
381
+ exists(skin: string): boolean;
382
+ }
383
+ interface PartsRule {
384
+ id: string;
385
+ apply(view: RuleView): void;
386
+ }
387
+ /**
388
+ * Under a covering part a part is replaced by its variant of the same number: hair under a
389
+ * hat becomes its tucked-in twin. The variant is found by name — "hair/5" → "hair_under/5".
390
+ */
391
+ declare function swapUnder(options: {
392
+ type: PartTypeId;
393
+ under: PartTypeId;
394
+ variant: PartTypeId;
395
+ /** Turns the type's skin name into the variant's. */
396
+ rename: (skin: string) => string;
397
+ }): PartsRule;
398
+ /** A part that takes both hands puts the other hand's part away while it is worn (a bow, a musket:
399
+ * no shield). The other part stays chosen — it is back as soon as a one-handed part is. */
400
+ declare function bothHands(options: {
401
+ type: PartTypeId;
402
+ other: PartTypeId;
403
+ skins: ReadonlySet<string>;
404
+ }): PartsRule;
405
+
406
+ interface ShadingDef {
407
+ /** Towards the light, in the character's world (y up) — (-0.5, 1) is from the upper left. */
408
+ light: readonly [number, number];
409
+ /** Images whose path starts with one of these are not shaded (the face). */
410
+ skip?: readonly string[];
411
+ }
412
+
413
+ interface PartTypeDef {
414
+ id: PartTypeId;
415
+ label: string;
416
+ /** Its skins are the data's skins whose name starts with this ("hair/"). */
417
+ skinPrefix: string;
418
+ /** Can be taken off — ‹ › then pass through "none". */
419
+ canHide: boolean;
420
+ /** The colour that paints it (multiplied over its slots), when it takes one. */
421
+ colorTarget?: ColorTargetId;
422
+ /** Owned by the rules (hat hair): never chosen directly, not part of a look. */
423
+ auto?: boolean;
424
+ }
425
+ interface ColorTargetDef {
426
+ id: ColorTargetId;
427
+ label: string;
428
+ /** Slots whose name starts with one of these take the colour. */
429
+ slotPrefixes: readonly string[];
430
+ /** How it paints: "tint" (default) multiplies over the whole slot; "art" is drawn into the image
431
+ * where the art says `currentColor` — an iris takes the eye colour, the whites stay white. */
432
+ into?: "tint" | "art";
433
+ }
434
+ /** Faces a character makes by itself: an animation puts one on, its
435
+ * skins replace the chosen ones of those types while it plays. */
436
+ interface ExpressionsDef {
437
+ /** Expression → skin per part type ("Angry": brows down, teeth bared). */
438
+ faces: Readonly<Record<string, Readonly<Record<PartTypeId, string>>>>;
439
+ /** Animation → expression; an animation not listed shows the chosen face. */
440
+ byAnimation: Readonly<Record<string, string>>;
441
+ /** An emotion of the actor contract → expression (`setEmotion`); under the animation's own face. */
442
+ emotions?: Readonly<Partial<Record<Emotion, string>>>;
443
+ /** Speech: the mouth part type and the skin each viseme puts on it (`setSpeech`); "rest" — the
444
+ * chosen mouth. Over every other face: a talking hero talks even while angry. */
445
+ speech?: {
446
+ type: PartTypeId;
447
+ visemes: Readonly<Partial<Record<Viseme, string>>>;
448
+ };
449
+ }
450
+ /** A group of part types in a character editor (the demo's FACE and BODY panels). */
451
+ interface EditorGroup {
452
+ id: string;
453
+ label: string;
454
+ /** Its tiles, in order. */
455
+ types: readonly PartTypeId[];
456
+ /** How its tiles picture a part. */
457
+ thumbnail?: {
458
+ /** Only these slots are drawn (the face, not the whole body the skin part also covers). */
459
+ slots?: readonly string[];
460
+ /** Drawn behind as a dark silhouette, and the picture framed on it (a head under the eyes). */
461
+ backdrop?: {
462
+ skin: string;
463
+ slots: readonly string[];
464
+ };
465
+ };
466
+ }
467
+ interface RigDef {
468
+ id: string;
469
+ data: SkeletonData;
470
+ partTypes: readonly PartTypeDef[];
471
+ colorTargets: readonly ColorTargetDef[];
472
+ rules: readonly PartsRule[];
473
+ /** The animation played when nothing else is asked. */
474
+ defaultAnimation: string;
475
+ /** Cross-fade between animations, seconds. */
476
+ mix: number;
477
+ /** Bones a game reads by role — where a shot leaves, what a bar sits over. */
478
+ anchors: Readonly<Record<string, BoneId>>;
479
+ /** The blink played over animations that do not move the eyes (none — no overlay). */
480
+ blink?: BlinkOptions;
481
+ /** The animation actually played for one asked while these parts are worn — a stance of its own
482
+ * for what is held (an idle with a rifle in both hands); omitted — always the one asked. */
483
+ stance?: (animation: string, worn: (type: PartTypeId) => string | null) => string;
484
+ /** Shading over the art (a dark side, a light rim); omitted — flat colours. */
485
+ shading?: ShadingDef;
486
+ /** Faces put on by animations; omitted — the chosen face always. */
487
+ expressions?: ExpressionsDef;
488
+ /** How an editor lays the parts out; omitted — one group of every type. */
489
+ editor?: {
490
+ groups: readonly EditorGroup[];
491
+ };
492
+ }
493
+ /** Checks a rig's references once, at load: a typo fails loudly here. */
494
+ declare function defineRig(rig: RigDef): RigDef;
495
+
496
+ type Rng = () => number;
497
+ declare function seededRandom(seed: number): Rng;
498
+ /** A seed from a string (an id). */
499
+ declare function seedOf(text: string): number;
500
+ declare function pick<T>(rng: Rng, list: readonly T[]): T | undefined;
501
+
502
+ type PartsChange = {
503
+ kind: "part";
504
+ type: PartTypeId;
505
+ skin: string | null;
506
+ } | {
507
+ kind: "hidden";
508
+ type: PartTypeId;
509
+ hidden: boolean;
510
+ } | {
511
+ kind: "color";
512
+ target: ColorTargetId;
513
+ color: Hex | null;
514
+ } | {
515
+ kind: "expression";
516
+ name: string | null;
517
+ } | {
518
+ kind: "speech";
519
+ viseme: Viseme;
520
+ };
521
+ type Listener = (change: PartsChange) => void;
522
+ declare class PartsManager {
523
+ readonly rig: RigDef;
524
+ private drawable;
525
+ private readonly chosen;
526
+ private readonly hidden;
527
+ private readonly colors;
528
+ private readonly lists;
529
+ private readonly listeners;
530
+ private readonly slotTarget;
531
+ private readonly slotArt;
532
+ private face;
533
+ private emotionFace;
534
+ private speech;
535
+ constructor(rig: RigDef, drawable?: (skin: string) => boolean);
536
+ /** The art changed (a pack added, another art source): recompute what each type offers. */
537
+ setDrawable(drawable: (skin: string) => boolean): void;
538
+ on(listener: Listener): () => void;
539
+ private emit;
540
+ typeDef(type: PartTypeId): PartTypeDef | undefined;
541
+ /** The skins a type offers, in the data's order. */
542
+ list(type: PartTypeId): readonly string[];
543
+ count(type: PartTypeId): number;
544
+ /** The chosen skin (null — none), whether hidden or not. */
545
+ part(type: PartTypeId): string | null;
546
+ /** Its index in `list`, -1 — none. */
547
+ index(type: PartTypeId): number;
548
+ isHidden(type: PartTypeId): boolean;
549
+ /** Chosen and not hidden — what a game asks ("is a weapon in hand?"). */
550
+ isWorn(type: PartTypeId): boolean;
551
+ color(target: ColorTargetId): Hex | null;
552
+ /** The skins to combine, in the rig's type order (a later one wins on the same attachment). */
553
+ wornSkins(): string[];
554
+ /** The colour a slot is painted with (replacing its animated colour), null — its own. */
555
+ slotColor(slotIndex: number): Hex | null;
556
+ /** The colour drawn into a slot's art (its `currentColor`), null — the art's own. */
557
+ artColor(slotIndex: number): Hex | null;
558
+ /** The expression on now (null — the chosen face). */
559
+ expression(): string | null;
560
+ /** Puts on a skin by index in `list` or by name; -1 / null — none. Unknown names are ignored. */
561
+ equip(type: PartTypeId, part: number | string | null): void;
562
+ unequip(type: PartTypeId): void;
563
+ /** Hides or shows a part, keeping which one. */
564
+ setHidden(type: PartTypeId, hidden: boolean): void;
565
+ /** The next part: a type that can be taken off goes … → last → none → first → … */
566
+ next(type: PartTypeId): void;
567
+ prev(type: PartTypeId): void;
568
+ /** Paints a target (null — back to the art's own colours). */
569
+ setColor(target: ColorTargetId, color: Hex | null): void;
570
+ /** Puts on an expression of the rig (null — back to the chosen face). Not part of a look. */
571
+ setExpression(name: string | null): void;
572
+ /** Puts on the face of an emotion (the rig's `expressions.emotions`); "neutral" — the chosen face.
573
+ * An animation's own face still wins while it plays. Not part of a look. */
574
+ setEmotion(emotion: Emotion): void;
575
+ /** The mouth's shape while speaking (lip-sync); "rest" — the chosen mouth. Not part of a look. */
576
+ setSpeech(viseme: Viseme): void;
577
+ /** Every type a random part, one that can be taken off — none 30% of the time. */
578
+ randomize(rng?: Rng): void;
579
+ /** The first skin of every type, nothing hidden, the art's own colours. */
580
+ reset(): void;
581
+ toLook(): CharacterLook;
582
+ /** Puts a look on. A skin the art cannot draw is skipped (what is worn stays). */
583
+ applyLook(look: CharacterLook): void;
584
+ private rebuildLists;
585
+ private first;
586
+ private init;
587
+ }
588
+
589
+ export { ArtCatalog as A, type BlinkOptions as B, type CharacterLook as C, defineRig as D, type EditorGroup as E, hexToNumber as F, imageSizes as G, type Hex as H, mergeLooks as I, numberToHex as J, parseLook as K, LOOK_VERSION as L, pick as M, readSkeletonData as N, seedOf as O, PartsManager as P, seededRandom as Q, type RigDef as R, Skeleton as S, skinImages as T, swapUnder as U, WHITE as W, Animator as a, type PlayOptions as b, type AnimationEvent as c, Slot as d, type Attachment as e, type ArtPack as f, type ArtDef as g, type ArtStyle as h, Animation as i, type ArtId as j, Bone as k, type BoneId as l, type Color as m, type ColorTargetDef as n, type ColorTargetId as o, type ExpressionsDef as p, type PartTypeDef as q, type PartTypeId as r, type PartsChange as s, type PartsRule as t, type Rng as u, type RuleView as v, type SkeletonData as w, type Skin as x, bothHands as y, colorOf as z };