@energy8platform/golem 0.3.0 → 0.5.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.
@@ -50,17 +50,17 @@ export declare const GateConfig: z.ZodObject<{
50
50
  }, "strip", z.ZodTypeAny, {
51
51
  b: string;
52
52
  a: string;
53
- during?: [number, number] | undefined;
54
53
  min?: number | undefined;
55
54
  max?: number | undefined;
55
+ during?: [number, number] | undefined;
56
56
  minRatio?: number | undefined;
57
57
  maxRatio?: number | undefined;
58
58
  }, {
59
59
  b: string;
60
60
  a: string;
61
- during?: [number, number] | undefined;
62
61
  min?: number | undefined;
63
62
  max?: number | undefined;
63
+ during?: [number, number] | undefined;
64
64
  minRatio?: number | undefined;
65
65
  maxRatio?: number | undefined;
66
66
  }>, "many">>;
@@ -84,9 +84,9 @@ export declare const GateConfig: z.ZodObject<{
84
84
  bone: string;
85
85
  prop: "x" | "y" | "rotation";
86
86
  whenSlotVisible: string;
87
- space?: "local" | "world" | undefined;
88
87
  min?: number | undefined;
89
88
  max?: number | undefined;
89
+ space?: "local" | "world" | undefined;
90
90
  required?: boolean | undefined;
91
91
  }>, "many">>;
92
92
  transitions: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -130,9 +130,9 @@ export declare const GateConfig: z.ZodObject<{
130
130
  distances: {
131
131
  b: string;
132
132
  a: string;
133
- during?: [number, number] | undefined;
134
133
  min?: number | undefined;
135
134
  max?: number | undefined;
135
+ during?: [number, number] | undefined;
136
136
  minRatio?: number | undefined;
137
137
  maxRatio?: number | undefined;
138
138
  }[];
@@ -172,9 +172,9 @@ export declare const GateConfig: z.ZodObject<{
172
172
  distances?: {
173
173
  b: string;
174
174
  a: string;
175
- during?: [number, number] | undefined;
176
175
  min?: number | undefined;
177
176
  max?: number | undefined;
177
+ during?: [number, number] | undefined;
178
178
  minRatio?: number | undefined;
179
179
  maxRatio?: number | undefined;
180
180
  }[] | undefined;
@@ -182,9 +182,9 @@ export declare const GateConfig: z.ZodObject<{
182
182
  bone: string;
183
183
  prop: "x" | "y" | "rotation";
184
184
  whenSlotVisible: string;
185
- space?: "local" | "world" | undefined;
186
185
  min?: number | undefined;
187
186
  max?: number | undefined;
187
+ space?: "local" | "world" | undefined;
188
188
  required?: boolean | undefined;
189
189
  }[] | undefined;
190
190
  transitions?: {
@@ -8,8 +8,8 @@
8
8
  * - play/tick/stop are a thin wrapper over an AnimationState (see rig-state.ts) kept for simple consumers
9
9
  * (harness, viewer). Games should drive `state` directly for queues, priorities and overlays.
10
10
  */
11
- import { Container, Texture } from "pixi.js";
12
- import { type RigDocument, type Asset } from "./rig-format";
11
+ import { Container, Sprite, Mesh, MeshPlane, Texture } from "pixi.js";
12
+ import { type RigDocument, type Slot, type Attachment, type Asset } from "./rig-format";
13
13
  import { type Pose, type Affine } from "./rig-anim";
14
14
  import { AnimationState, type PlayResult } from "./rig-state";
15
15
  /** vertices per axis of a lattice-deformed region's render mesh (8 segments) */
@@ -23,6 +23,22 @@ export interface PlayOptions {
23
23
  onComplete?: () => void;
24
24
  onEvent?: (name: string, payload?: Record<string, unknown>) => void;
25
25
  }
26
+ export interface RigRenderItem {
27
+ att: Attachment;
28
+ slot: Slot;
29
+ node: Sprite | MeshPlane | Mesh;
30
+ /** lattice-deformed region */
31
+ lattice?: {
32
+ plane: MeshPlane;
33
+ offsets: Float64Array;
34
+ applied: Float64Array;
35
+ };
36
+ /** skinned mesh */
37
+ skinned?: {
38
+ mesh: Mesh;
39
+ positions: Float32Array;
40
+ };
41
+ }
26
42
  /** Build the asset-id → Texture map, loading each `src` once and cutting atlas frames (with rotation and trim). */
27
43
  export declare function createTextures(doc: RigDocument, loadSource: (src: string) => Promise<Texture>): Promise<Map<string, Texture>>;
28
44
  /**
@@ -32,9 +48,17 @@ export declare function createTextures(doc: RigDocument, loadSource: (src: strin
32
48
  export declare function atlasUV(asset: Asset, u: number, v: number, sourceW: number, sourceH: number): [number, number];
33
49
  export declare class RigPlayer {
34
50
  doc: RigDocument;
51
+ private inputPose;
52
+ private controlOverrides;
53
+ /** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
54
+ setControls(values: Record<string, number>): void;
55
+ resetControls(): void;
35
56
  readonly view: Container<import("pixi.js").ContainerChild>;
36
57
  readonly state: AnimationState;
37
58
  private items;
59
+ private beforePose;
60
+ private afterPose;
61
+ private clipPoints;
38
62
  private skel;
39
63
  private textures;
40
64
  private pages;
@@ -49,6 +73,19 @@ export declare class RigPlayer {
49
73
  missingAnimation?: "warn" | "throw";
50
74
  onWarning?: (message: string) => void;
51
75
  });
76
+ /** Read-only render handles for in-page tools. Nodes remain owned by the player. */
77
+ get renderItems(): readonly Readonly<RigRenderItem>[];
78
+ /** Current document-space clipping polygons, keyed by path attachment id. */
79
+ get clipPolygons(): ReadonlyMap<string, readonly number[]>;
80
+ /**
81
+ * Modify resolved control outputs before skeleton evaluation, constraints and skinning.
82
+ * Callbacks run in registration order, each with its own map. Change control inputs via setControls.
83
+ */
84
+ onBeforePose(callback: (pose: Pose) => Pose): () => void;
85
+ /** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
86
+ onAfterPose(callback: (player: RigPlayer) => void): () => void;
87
+ /** Rebuild the current pose without advancing playback; useful for reversible overlays. */
88
+ refreshPose(): void;
52
89
  /** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
53
90
  private pageTexture;
54
91
  /** Replace whatever is playing (no queueing). Resolves when the animation completes or is replaced. */
@@ -2,8 +2,10 @@
2
2
  * `@energy8platform/golem/runtime` — the browser half: play a rig document in PixiJS v8.
3
3
  * Depends on pixi.js (peer) and zod only; no node, no tools, no editor.
4
4
  */
5
- export { RigPlayer, createTextures, atlasUV, MESH_GRID, type PlayOptions } from "./rig-runtime";
5
+ export { RigPlayer, createTextures, atlasUV, MESH_GRID, type PlayOptions, type RigRenderItem } from "./rig-runtime";
6
6
  export { RigSymbol, type RigSymbolOptions, type SymbolSize, type SymbolViewLike, type TickerLike } from "./rig-symbol";
7
7
  export { AnimationState, type PlayResult, type PoseSink } from "./rig-state";
8
8
  export { parse, validate, migrateV1, RigDocument as RigDocumentSchema } from "./rig-format";
9
9
  export type { RigDocument, Bone, Slot, Asset, Attachment, Animation, Track, Key } from "./rig-format";
10
+ export type { RigControl, ControlBinding } from "./rig-format";
11
+ export { controlPose, resolveControls, sampleBinding } from "./rig-controls";
@@ -227,6 +227,7 @@ var Key = z.object({
227
227
  });
228
228
  var Interpolation = { interpolation: z.enum(["segment", "pchip"]).optional() };
229
229
  var Track = z.discriminatedUnion("target", [
230
+ z.object({ target: z.literal("control"), id: Id, prop: z.literal("value"), ...Interpolation, keys: z.array(Key).min(1) }),
230
231
  z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: z.array(Key).min(1) }),
231
232
  z.object({ target: z.literal("slot"), id: Id, prop: SlotProp, ...Interpolation, keys: z.array(Key).min(1) }),
232
233
  z.object({ target: z.literal("deform"), id: Id, prop: DeformProp, ...Interpolation, keys: z.array(Key).min(1) }),
@@ -249,6 +250,25 @@ var Animation = z.object({
249
250
  /** if generated from a preset — keep params so the agent can re-tune instead of re-keying */
250
251
  preset: z.object({ name: z.string(), params: z.record(z.unknown()) }).optional()
251
252
  });
253
+ var RigControl = z.object({
254
+ id: Id,
255
+ label: z.string().optional(),
256
+ min: z.number().finite(),
257
+ max: z.number().finite(),
258
+ default: z.number().finite()
259
+ }).strict();
260
+ var ControlValues = z.array(z.union([z.number().finite(), z.array(z.number().finite()).min(1)])).min(2);
261
+ var ControlChannel = z.discriminatedUnion("target", [
262
+ z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, values: ControlValues }).strict(),
263
+ z.object({ target: z.literal("slot"), id: Id, prop: z.literal("alpha"), values: ControlValues }).strict(),
264
+ z.object({ target: z.literal("deform"), id: Id, prop: z.literal("vertices"), values: ControlValues }).strict()
265
+ ]);
266
+ var ControlBinding = z.object({
267
+ id: Id,
268
+ axes: z.array(z.object({ control: Id, keys: z.array(z.number().finite()).min(2).max(32) }).strict()).min(1).max(2),
269
+ /** Flattened grid, first axis varies fastest. Linear / bilinear interpolation. */
270
+ channels: z.array(ControlChannel).min(1)
271
+ }).strict();
252
272
  var Reference = z.object({
253
273
  src: z.string(),
254
274
  opacity: z.number().min(0).max(1).default(0.5),
@@ -274,7 +294,9 @@ var RigSchema = z.object({
274
294
  ik: z.array(IkConstraint).default([]),
275
295
  transform: z.array(TransformConstraint).default([]),
276
296
  path: z.array(PathConstraint).default([]),
277
- animations: z.array(Animation)
297
+ animations: z.array(Animation),
298
+ controls: z.array(RigControl).optional(),
299
+ controlBindings: z.array(ControlBinding).optional()
278
300
  });
279
301
  var RigDocument = z.preprocess((input) => {
280
302
  const v = input?.version;
@@ -349,6 +371,8 @@ function validate(doc) {
349
371
  dup(doc.ik, "ik");
350
372
  dup(doc.transform, "transform");
351
373
  dup(doc.path, "path");
374
+ dup(doc.controls ?? [], "control");
375
+ dup(doc.controlBindings ?? [], "control binding");
352
376
  const bones = new Set(doc.bones.map((b) => b.id));
353
377
  const assets = new Map(doc.assets.map((a) => [a.id, a]));
354
378
  const slots = new Set(doc.slots.map((s) => s.id));
@@ -423,13 +447,47 @@ function validate(doc) {
423
447
  for (const b of c.bones) if (!bones.has(b)) errs.push(`path "${c.id}": unknown bone "${b}"`);
424
448
  if (!slots.has(c.target)) errs.push(`path "${c.id}": unknown target slot "${c.target}"`);
425
449
  }
450
+ const controls = new Map((doc.controls ?? []).map((c) => [c.id, c]));
451
+ const driven = /* @__PURE__ */ new Set();
452
+ for (const c of controls.values()) if (!(c.min < c.max && c.default >= c.min && c.default <= c.max))
453
+ errs.push(`control "${c.id}": require min < max and default within range`);
454
+ for (const b of doc.controlBindings ?? []) {
455
+ const prefix = `control binding "${b.id}"`;
456
+ if (new Set(b.axes.map((a) => a.control)).size !== b.axes.length) errs.push(`${prefix}: axes must use different controls`);
457
+ for (const axis of b.axes) {
458
+ const c = controls.get(axis.control);
459
+ if (!c) errs.push(`${prefix}: unknown control "${axis.control}"`);
460
+ else if (axis.keys[0] !== c.min || axis.keys.at(-1) !== c.max) errs.push(`${prefix}: keys must span the control's full range`);
461
+ if (axis.keys.some((v, i) => i > 0 && v <= axis.keys[i - 1])) errs.push(`${prefix}: keys must be strictly increasing`);
462
+ }
463
+ const count = b.axes.reduce((n, a) => n * a.keys.length, 1);
464
+ for (const ch of b.channels) {
465
+ const key = `${ch.target}.${ch.id}.${ch.prop}`;
466
+ if (driven.has(key)) errs.push(`${prefix}: channel ${key} already owned by a binding`);
467
+ driven.add(key);
468
+ if (ch.values.length !== count) errs.push(`${prefix}: ${key} needs ${count} grid values`);
469
+ if (ch.target === "deform") {
470
+ const att = attachments.get(ch.id);
471
+ if (!att || att.type === "region") errs.push(`${prefix}: vertices need a mesh or path attachment`);
472
+ else {
473
+ const n = att.weights ? att.weights.reduce((s, a) => s + a.length * 2, 0) : att.vertices.length;
474
+ if (ch.values.some((v) => !Array.isArray(v) || v.length !== n)) errs.push(`${prefix}: ${key} needs arrays of ${n} offsets`);
475
+ }
476
+ } else {
477
+ if (!(ch.target === "bone" ? bones : slots).has(ch.id)) errs.push(`${prefix}: unknown ${ch.target} "${ch.id}"`);
478
+ if (ch.values.some((v) => typeof v !== "number")) errs.push(`${prefix}: ${key} needs numeric values`);
479
+ if (ch.target === "slot" && ch.values.some((v) => typeof v === "number" && (v < 0 || v > 1))) errs.push(`${prefix}: alpha must be in 0..1`);
480
+ }
481
+ }
482
+ }
426
483
  for (const a of doc.animations) {
427
484
  const tracks = /* @__PURE__ */ new Set();
428
485
  for (const t of a.tracks) {
429
486
  const tid = trackId(t);
430
487
  if (tracks.has(tid)) errs.push(`animation "${a.id}": duplicate track ${tid}`);
431
488
  tracks.add(tid);
432
- const known = t.target === "bone" ? bones.has(t.id) : t.target === "slot" ? slots.has(t.id) : t.target === "deform" ? attachments.has(t.id) : t.target === "ik" ? iks.has(t.id) : t.target === "transform" ? transforms.has(t.id) : t.target === "path" ? paths.has(t.id) : true;
489
+ if (driven.has(tid)) errs.push(`animation "${a.id}": ${tid} is controlled by a binding; animate its control instead`);
490
+ const known = t.target === "bone" ? bones.has(t.id) : t.target === "slot" ? slots.has(t.id) : t.target === "deform" ? attachments.has(t.id) : t.target === "ik" ? iks.has(t.id) : t.target === "transform" ? transforms.has(t.id) : t.target === "path" ? paths.has(t.id) : t.target === "control" ? controls.has(t.id) : true;
433
491
  if (!known) errs.push(`animation "${a.id}": unknown ${t.target} "${t.id}"`);
434
492
  const bound = t.target === "deform" ? DEFORM_BOUNDS[t.prop] : void 0;
435
493
  const att = t.target === "deform" ? attachments.get(t.id) : void 0;
@@ -437,6 +495,10 @@ function validate(doc) {
437
495
  for (const k of t.keys) {
438
496
  if (k.t <= prev && prev >= 0) errs.push(`animation "${a.id}" ${tid}: keys not strictly increasing`);
439
497
  if (k.t > a.duration) errs.push(`animation "${a.id}" ${tid}: key at ${k.t}s exceeds duration`);
498
+ if (t.target === "control") {
499
+ const c = controls.get(t.id);
500
+ if (typeof k.v !== "number" || !Number.isFinite(k.v) || c && (k.v < c.min || k.v > c.max)) errs.push(`animation "${a.id}" ${tid}: control key outside its finite range`);
501
+ }
440
502
  if (t.target === "slot" && t.prop === "attachment") {
441
503
  if (k.v !== null && typeof k.v !== "string") errs.push(`animation "${a.id}" ${tid}: attachment key must be an attachment id or null`);
442
504
  else if (typeof k.v === "string") {
@@ -475,6 +537,52 @@ function parse(input) {
475
537
  return doc;
476
538
  }
477
539
 
540
+ // src/rig-controls.ts
541
+ function controlPose(doc, values) {
542
+ const out = /* @__PURE__ */ new Map();
543
+ for (const [id, value] of Object.entries(values)) {
544
+ const c = doc.controls?.find((c2) => c2.id === id);
545
+ if (!c) throw new Error(`unknown control "${id}"`);
546
+ if (!Number.isFinite(value)) throw new Error(`control "${id}" must be finite`);
547
+ out.set(`control:${id}:value`, Math.max(c.min, Math.min(c.max, value)));
548
+ }
549
+ return out;
550
+ }
551
+ function bracket(keys, value) {
552
+ let i = 0;
553
+ while (i < keys.length - 2 && value > keys[i + 1]) i++;
554
+ return [i, Math.max(0, Math.min(1, (value - keys[i]) / (keys[i + 1] - keys[i])))];
555
+ }
556
+ function sampleBinding(binding, values) {
557
+ const [x, u] = bracket(binding.axes[0].keys, values[0]);
558
+ const [y, v] = binding.axes[1] ? bracket(binding.axes[1].keys, values[1]) : [0, 0];
559
+ const width = binding.axes[0].keys.length, a = y * width + x;
560
+ const mix = (a2, b, u2) => a2 + (b - a2) * u2;
561
+ return binding.channels.map((ch) => {
562
+ const read = (i, j) => j === void 0 ? ch.values[i] : ch.values[i][j];
563
+ const at = (j) => {
564
+ const top = mix(read(a, j), read(a + 1, j), u);
565
+ return binding.axes[1] ? mix(top, mix(read(a + width, j), read(a + width + 1, j), u), v) : top;
566
+ };
567
+ return Array.isArray(ch.values[0]) ? ch.values[0].map((_, j) => at(j)) : at();
568
+ });
569
+ }
570
+ function resolveControls(doc, input) {
571
+ if (!doc.controlBindings?.length) return input;
572
+ const out = new Map(input), controls = new Map(doc.controls?.map((c) => [c.id, c]));
573
+ for (const b of doc.controlBindings) {
574
+ const values = b.axes.map((axis) => {
575
+ const c = controls.get(axis.control);
576
+ const value = input.get(`control:${c.id}:value`) ?? c.default;
577
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`control "${c.id}" must be finite`);
578
+ return Math.max(c.min, Math.min(c.max, value));
579
+ });
580
+ const sampled = sampleBinding(b, values);
581
+ b.channels.forEach((ch, i) => out.set(`${ch.target}:${ch.id}:${ch.prop}`, sampled[i]));
582
+ }
583
+ return out;
584
+ }
585
+
478
586
  // src/rig-anim.ts
479
587
  var DEG2 = Math.PI / 180;
480
588
  var named = {
@@ -578,6 +686,7 @@ function blendPoses(from, to, w, restOf) {
578
686
  }
579
687
  function restValue(doc, key) {
580
688
  const [target, id, prop] = key.split(":");
689
+ if (target === "control") return doc.controls?.find((c) => c.id === id)?.default ?? 0;
581
690
  if (target === "bone") return doc.bones.find((b) => b.id === id).transform[prop];
582
691
  if (target === "slot") {
583
692
  const s = doc.slots.find((s2) => s2.id === id);
@@ -680,7 +789,9 @@ function updateSubtree(skel, root) {
680
789
  for (const k of kids.get(id) ?? []) stack.push(k.id);
681
790
  }
682
791
  }
683
- function evaluate(doc, pose) {
792
+ function evaluate(doc, pose, modifiers) {
793
+ pose = resolveControls(doc, pose);
794
+ if (modifiers) for (const modify of modifiers) pose = modify(new Map(pose));
684
795
  const skel = { doc, pose, bones: /* @__PURE__ */ new Map(), attachment: /* @__PURE__ */ new Map() };
685
796
  for (const b of boneOrder(doc)) {
686
797
  const s = localOf(doc, pose, b);
@@ -1485,9 +1596,23 @@ var RigPlayer = class {
1485
1596
  this.state = new AnimationState(this, { ...opts, mix: opts.mix ?? 0.18 });
1486
1597
  this.setPose(/* @__PURE__ */ new Map());
1487
1598
  }
1599
+ inputPose = /* @__PURE__ */ new Map();
1600
+ controlOverrides = /* @__PURE__ */ new Map();
1601
+ /** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
1602
+ setControls(values) {
1603
+ this.controlOverrides = controlPose(this.doc, values);
1604
+ this.setPose(this.inputPose);
1605
+ }
1606
+ resetControls() {
1607
+ this.controlOverrides.clear();
1608
+ this.setPose(this.inputPose);
1609
+ }
1488
1610
  view = new Container();
1489
1611
  state;
1490
1612
  items = [];
1613
+ beforePose = /* @__PURE__ */ new Set();
1614
+ afterPose = /* @__PURE__ */ new Set();
1615
+ clipPoints = /* @__PURE__ */ new Map();
1491
1616
  skel;
1492
1617
  textures;
1493
1618
  pages = /* @__PURE__ */ new Map();
@@ -1496,6 +1621,35 @@ var RigPlayer = class {
1496
1621
  /** slots the editor has selected; setPose draws them at GHOST_ALPHA at least */
1497
1622
  ghosts = /* @__PURE__ */ new Set();
1498
1623
  clipGraphics = /* @__PURE__ */ new Map();
1624
+ /** Read-only render handles for in-page tools. Nodes remain owned by the player. */
1625
+ get renderItems() {
1626
+ return this.items;
1627
+ }
1628
+ /** Current document-space clipping polygons, keyed by path attachment id. */
1629
+ get clipPolygons() {
1630
+ return this.clipPoints;
1631
+ }
1632
+ /**
1633
+ * Modify resolved control outputs before skeleton evaluation, constraints and skinning.
1634
+ * Callbacks run in registration order, each with its own map. Change control inputs via setControls.
1635
+ */
1636
+ onBeforePose(callback) {
1637
+ this.beforePose.add(callback);
1638
+ return () => {
1639
+ this.beforePose.delete(callback);
1640
+ };
1641
+ }
1642
+ /** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
1643
+ onAfterPose(callback) {
1644
+ this.afterPose.add(callback);
1645
+ return () => {
1646
+ this.afterPose.delete(callback);
1647
+ };
1648
+ }
1649
+ /** Rebuild the current pose without advancing playback; useful for reversible overlays. */
1650
+ refreshPose() {
1651
+ this.setPose(this.inputPose);
1652
+ }
1499
1653
  /** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
1500
1654
  pageTexture(tex) {
1501
1655
  let page = this.pages.get(tex.source);
@@ -1550,7 +1704,11 @@ var RigPlayer = class {
1550
1704
  }
1551
1705
  /** Apply a pose (rest where silent) to the display objects. */
1552
1706
  setPose(pose) {
1553
- const skel = this.skel = evaluate(this.doc, pose);
1707
+ this.inputPose = new Map(pose);
1708
+ if (this.controlOverrides.size) pose = new Map([...pose, ...this.controlOverrides]);
1709
+ this.clipPoints.clear();
1710
+ const skel = this.skel = evaluate(this.doc, pose, this.beforePose);
1711
+ pose = skel.pose;
1554
1712
  const clips = new Set(this.doc.slots.flatMap((s) => s.clip ? [s.clip] : []));
1555
1713
  for (const [id, g] of this.clipGraphics) if (!clips.has(id)) {
1556
1714
  g.destroy();
@@ -1568,7 +1726,9 @@ var RigPlayer = class {
1568
1726
  const vertices = attachmentWorldVertices(skel, att.slot, att);
1569
1727
  const n = Math.max(32, vertices.length * 4);
1570
1728
  const points = samplePath(vertices, true, att.lengths, true, Array.from({ length: n }, (_, i) => i / n), true);
1571
- g.clear().poly(points.flatMap((p) => [p.x, p.y]), true).fill(16777215);
1729
+ const polygon = points.flatMap((p) => [p.x, p.y]);
1730
+ this.clipPoints.set(id, polygon);
1731
+ g.clear().poly(polygon, true).fill(16777215);
1572
1732
  }
1573
1733
  const m = new Matrix();
1574
1734
  const orderV = pose.get(pk("drawOrder", "order", "order"));
@@ -1589,6 +1749,7 @@ var RigPlayer = class {
1589
1749
  it.node.tint = rgbOf(color);
1590
1750
  it.node.zIndex = order.size ? order.get(s.id) ?? 0 : numOf(pose, pk("slot", s.id, "z"), s.z);
1591
1751
  if (it.skinned) {
1752
+ it.node.setFromMatrix(m.identity());
1592
1753
  const v = attachmentWorldVertices(skel, s.id, it.att);
1593
1754
  const buf = it.skinned.mesh.geometry.getBuffer("aPosition");
1594
1755
  buf.data.set(v);
@@ -1621,6 +1782,7 @@ var RigPlayer = class {
1621
1782
  it.node.scale.y *= h / (this.textures.get(att.asset)?.height ?? h);
1622
1783
  }
1623
1784
  }
1785
+ for (const callback of this.afterPose) callback(this);
1624
1786
  }
1625
1787
  /** World-space bone positions — used by editor gizmos and renderPreview overlays */
1626
1788
  bonePositions() {
@@ -1697,9 +1859,12 @@ export {
1697
1859
  RigPlayer,
1698
1860
  RigSymbol,
1699
1861
  atlasUV,
1862
+ controlPose,
1700
1863
  createTextures,
1701
1864
  migrateV1,
1702
1865
  parse,
1866
+ resolveControls,
1867
+ sampleBinding,
1703
1868
  validate
1704
1869
  };
1705
1870
  //# sourceMappingURL=runtime.js.map