rig-c 2.36.0 → 2.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rig-c",
3
- "version": "2.36.0",
3
+ "version": "2.37.0",
4
4
  "description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -824,22 +824,13 @@ export function posedSlots(
824
824
  /** Filled with each slot's placeholder once the sample's own timelines have moved it, before the sliders — what the attachments are posed from (`attachmentStates`). */
825
825
  placeholders?: Map<string, string | null>,
826
826
  ): { rows: CoreSlotRow[]; conflicts: string[] } {
827
- const resolve = plant.shown ?? shownAttachment;
828
827
  const colour = plant.colour ?? readColour;
829
- const blend = plant.blend ?? readBlend;
830
- const round = plant.round ?? gridRound;
831
828
  const byName = new Map(timelines.slots.map((s) => [s.name, s]));
832
- const rows: CoreSlotRow[] = [];
833
- const conflicts: string[] = [];
834
- const active = activeBones(doc);
835
- const gate = plant.slotTimelines ?? slotTimelinesApply;
836
- for (const slot of doc.slots) {
829
+ return slotRowsFrom(doc, plant, sliders, placeholders, (slot, live) => {
837
830
  let placeholder = slot.setup;
838
831
  const light = slot.color === undefined ? [1, 1, 1, 1] : colour(slot.color);
839
832
  const setupDark = slot.dark === undefined ? null : readColour(slot.dark);
840
833
  const dark = setupDark === null ? null : [setupDark[0], setupDark[1], setupDark[2]];
841
- // A slot on a bone the skin leaves inactive is not animated — not by the sample's timelines, not by a slider (`./skins.ts`).
842
- const live = gate(doc, slot, active);
843
834
  for (const tl of live ? (byName.get(slot.name)?.timelines ?? []) : []) {
844
835
  if (tl.kind === 'attachment') {
845
836
  const key = keyAt(tl.keys, t, plant);
@@ -855,6 +846,48 @@ export function posedSlots(
855
846
  else if (dark !== null && setupDark !== null) dark[at - 4] = v === null ? setupDark[at - 4] : clamp01(v[i]);
856
847
  });
857
848
  }
849
+ return { placeholder, light, dark };
850
+ });
851
+ }
852
+
853
+ /**
854
+ * What a slot holds once the timelines have posed it and before the sliders:
855
+ * the placeholder it shows, its light colour and its dark one (`null` where
856
+ * the slot states none). `posedSlots` reads it off one animation at `t`; the
857
+ * live tracks off their mixed entries (`./tracks.ts`, issue #1336).
858
+ */
859
+ export interface SlotBase {
860
+ placeholder: string | null;
861
+ light: number[];
862
+ dark: number[] | null;
863
+ }
864
+
865
+ /**
866
+ * The slot rows from each slot's `SlotBase` — `baseOf`, called once per slot
867
+ * in the document's order with whether the slot is animated at all — then the
868
+ * sliders, the resolution and the oracle's row: `posedSlots`' body past the
869
+ * timelines, which the live tracks share (`./tracks.ts`).
870
+ */
871
+ export function slotRowsFrom(
872
+ doc: CompiledDocument,
873
+ plant: TimelinePlant,
874
+ sliders: readonly SliderApplication[],
875
+ placeholders: Map<string, string | null> | undefined,
876
+ baseOf: (slot: ModelSlot, live: boolean) => SlotBase,
877
+ ): { rows: CoreSlotRow[]; conflicts: string[] } {
878
+ const resolve = plant.shown ?? shownAttachment;
879
+ const blend = plant.blend ?? readBlend;
880
+ const round = plant.round ?? gridRound;
881
+ const rows: CoreSlotRow[] = [];
882
+ const conflicts: string[] = [];
883
+ const active = activeBones(doc);
884
+ const gate = plant.slotTimelines ?? slotTimelinesApply;
885
+ for (const slot of doc.slots) {
886
+ // A slot on a bone the skin leaves inactive is not animated — not by the sample's timelines, not by a slider (`./skins.ts`).
887
+ const live = gate(doc, slot, active);
888
+ const base = baseOf(slot, live);
889
+ let placeholder = base.placeholder;
890
+ const { light, dark } = base;
858
891
  // The sliders, after the animation, in constraint order (`./constraints_slider.ts`).
859
892
  // What the slot shows after the sample's own timelines: the attachments' deform and sequence timelines are matched against it (`./deform.ts`, *A switch*).
860
893
  placeholders?.set(slot.name, placeholder);
@@ -953,16 +986,14 @@ function stepRows(posed: PosedStep): CoreBoneRow[] {
953
986
 
954
987
  /** `posedBoneWorld` up to its rows, refusing a bone the evaluator gave no transform (`PosedStep`). */
955
988
  function posedBoneStep(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step: PoseStep | undefined): PosedStep {
956
- const active = activeBones(doc);
957
989
  const bones = posedBones(doc, timelines, t, plant);
990
+ // One step of the stepped phase (issue #956, `./constraints_physics.ts`): the physics records posed by their timelines and stepped under the walk's context; the deformed curve a path walks as above.
991
+ if (constraints !== undefined && step !== undefined) return steppedLocals(doc, bones, plant, () => pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, sliders, step);
992
+ const active = activeBones(doc);
958
993
  // The caller's adjustment, after the timelines and before anything reads the locals (`LocalAdjust`).
959
994
  if (step?.adjust !== undefined) adjustLocals(doc, bones, step.adjust);
960
995
  let world = (plant.evaluate ?? worldTransforms)(bones, active);
961
- if (constraints !== undefined && step !== undefined) {
962
- // One step of the stepped phase (issue #956, `./constraints_physics.ts`): the physics records posed by their timelines and stepped under the walk's context; the deformed curve a path walks as above. No previous pass: under the step it is the previous step's, and `poseAnimations` leaves such bones out.
963
- const records = stepPhysicsRecords(pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, active, step.ctx, step.before);
964
- world = applyConstraints(bones, world, active, plant.constraints ? plant.constraints(records) : records, null, sliders, step.ctx, undefined, solverRules(plant.solver));
965
- } else if (constraints !== undefined) {
996
+ if (constraints !== undefined) {
966
997
  const setupRecords = constraintRecords(doc);
967
998
  // A path walks the curve the sample's deform timelines left on its slot (`./deform.ts`); a slider's deform of a walked path leaves the bones out (`pathAnimationsWhy`).
968
999
  const records = pathDeformed(doc, posedRecords(setupRecords, constraints, t), timelines, t, plant);
@@ -974,6 +1005,40 @@ function posedBoneStep(doc: CompiledDocument, timelines: CoreAnimationTimelines,
974
1005
  return { bones, world, active, round: plant.round ?? gridRound };
975
1006
  }
976
1007
 
1008
+ /**
1009
+ * One step of the stepped phase from locals already posed (issue #1336): the
1010
+ * caller's adjustment (`LocalAdjust`), the world transforms, the physics
1011
+ * records posed by `records` and stepped under the walk's context, then every
1012
+ * constraint. No previous pass: under the step it is the previous step's, and
1013
+ * `poseAnimations` leaves such bones out. A single animation's walk reaches it
1014
+ * through `posedBoneStep` with the locals `posedBones` posed; the live tracks
1015
+ * (`./tracks.ts`) with the locals their mixed entries posed — one pose path
1016
+ * past the timelines for both.
1017
+ */
1018
+ function steppedLocals(doc: CompiledDocument, bones: ModelBone[], plant: TimelinePlant, records: () => CoreConstraintRecord[], physicsKeyed: NonNullable<CoreConstraintTimelines['physicsKeyed']>, t: number, sliders: SliderApplication[] | undefined, step: PoseStep): PosedStep {
1019
+ const active = activeBones(doc);
1020
+ // The caller's adjustment, after the timelines and before anything reads the locals (`LocalAdjust`).
1021
+ if (step.adjust !== undefined) adjustLocals(doc, bones, step.adjust);
1022
+ let world = (plant.evaluate ?? worldTransforms)(bones, active);
1023
+ const stepped = stepPhysicsRecords(records(), physicsKeyed, t, active, step.ctx, step.before);
1024
+ world = applyConstraints(bones, world, active, plant.constraints ? plant.constraints(stepped) : stepped, null, sliders, step.ctx, undefined, solverRules(plant.solver));
1025
+ for (const b of bones) if (world.get(b.name) === undefined) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
1026
+ return { bones, world, active, round: plant.round ?? gridRound };
1027
+ }
1028
+
1029
+ /**
1030
+ * `steppedLocals`' world transforms, the oracle's rows formed beside them when
1031
+ * `rows` is set (as `posedBoneWorld` forms them and `posedBoneWorldAlone` does
1032
+ * not): the live tracks' entry to the stepped phase (`./tracks.ts`). The
1033
+ * document's constraint records as they stand — the live tracks mix no
1034
+ * constraint timeline — and no physics timeline keyed.
1035
+ */
1036
+ export function steppedLocalsWorld(doc: CompiledDocument, bones: ModelBone[], plant: TimelinePlant, sliders: SliderApplication[], step: PoseStep, rows: boolean): Map<string, CoreWorld> {
1037
+ const posed = steppedLocals(doc, bones, plant, () => constraintRecords(doc), [], 0, sliders, step);
1038
+ if (rows) stepRows(posed);
1039
+ return posed.world;
1040
+ }
1041
+
977
1042
  /** Each path record with the curve its slot shows deformed by the sample's own deform timelines at `t` (`./deform.ts`), or as it was. */
978
1043
  function pathDeformed(doc: CompiledDocument, records: CoreConstraintRecord[], timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant): CoreConstraintRecord[] {
979
1044
  if (timelines.attachments.every((a) => a.deform === null)) return records;
@@ -536,6 +536,14 @@ export function attachmentStates(
536
536
  sample: { timelines: CoreAnimationTimelines; t: number } | null,
537
537
  sliders: readonly AttachmentApplication[],
538
538
  plant: { deform?: DeformEvaluator; deformBlend?: DeformBlender; sequence?: SequenceEvaluator; slotTimelines?: SlotTimelineGate } = {},
539
+ /**
540
+ * The live tracks' deforms (`./tracks.ts`, issue #1336), in place of a
541
+ * sample's: each slot's deform array as the mixed entries left it (`null`
542
+ * none) and the records their deform timelines keyed on it — what the
543
+ * sample's own timelines would have set and seen. Absent on every other
544
+ * caller.
545
+ */
546
+ mixed?: ReadonlyMap<string, { deform: number[] | null; seen: readonly string[] }>,
539
547
  ): { shown: ShownGeometry[]; why: string[] } {
540
548
  const evalDeform = plant.deform ?? deformAt;
541
549
  const blendOf = plant.deformBlend ?? blendDeform;
@@ -548,9 +556,10 @@ export function attachmentStates(
548
556
  // A slot on a bone the skin leaves inactive is not animated: no deform, no frame, no slider switch (`./skins.ts`).
549
557
  const live = gate(doc, slot, active);
550
558
  let placeholder = placeholders.get(slot.name) ?? null;
551
- let deform: number[] | null = null;
559
+ const base = live ? mixed?.get(slot.name) : undefined;
560
+ let deform: number[] | null = base?.deform ?? null;
552
561
  let frame: number | null = null;
553
- const seen: string[] = [];
562
+ const seen: string[] = base === undefined ? [] : [...base.seen];
554
563
  // What the slot shows now, the record its timelines are keyed as, and what a deform or a frame is computed on.
555
564
  const state = (): { s: CoreShown; identity: string | null; vertices: ModelVertices | null; count: number | undefined } | null => {
556
565
  if (placeholder === null) return null;
package/src/core/entry.ts CHANGED
@@ -16,7 +16,8 @@
16
16
  * browser and holds the bundle to zero `node:` strings.
17
17
  *
18
18
  * ⚠️ A symbol is listed here only because a dependant was seen using it,
19
- * or asked for it in writing before it could be (`openTrack`, issue #1276).
19
+ * or asked for it in writing before it could be (`openTrack`, issue #1276;
20
+ * `openTracks`, issue #1336).
20
21
  * The list is `scripts/install_smoke.ts`'s `OBSERVED_SYMBOLS` rows for
21
22
  * `./core`, and a symbol added here and not there is not promised.
22
23
  */
@@ -27,6 +28,8 @@ export type { RawPose, RawBone, RawDrawn, RawClip, RawClipped, RawEvent, RawRese
27
28
  export { poseWalkSetup, poseLoopingWalk } from './walk.ts';
28
29
  export { openTrack } from './track.ts';
29
30
  export type { LiveTrack, TrackOptions, AdjustableLocals, BoneLocals, LocalAdjust } from './track.ts';
31
+ export { openTracks } from './tracks.ts';
32
+ export type { LiveTracks, TracksOptions, MixOptions, QueueOptions, TrackEntryView } from './tracks.ts';
30
33
  export type { WalkPose } from './walk.ts';
31
34
  export { documentPageLookup, drawnRegions, regionPageUvs, meshPageUvs, readUvSequences } from './uvs.ts';
32
35
  export type { DrawnRegion, UvPage, UvRegion, UvSource } from './uvs.ts';
package/src/core/raw.ts CHANGED
@@ -114,12 +114,12 @@
114
114
  * missing would draw a different picture in silence.
115
115
  */
116
116
  import { activeBones, CoreInputError, drawWalkOf, poseSetup, rawNumber, readColour, shownAttachment, sourceOfDoc, underNoSkin, type CompiledDocument, type CorePlant, type CoreSlotRow } from './index.ts';
117
- import { posedBoneWorld, posedBoneWorldAlone, posedSlots, type LocalAdjust, type TimelinePlant } from './animation.ts';
117
+ import { posedBoneWorld, posedBoneWorldAlone, posedSlots, type CoreAnimationTimelines, type LocalAdjust, type TimelinePlant } from './animation.ts';
118
118
  import { constraintsAbsentWhy, pathAnimationsWhy } from './constraints.ts';
119
119
  import { freshStepContext, steppedPreviousPassWhy } from './constraints_physics.ts';
120
120
  import { attachmentStates } from './deform.ts';
121
- import { drawOrderAt } from './draw_order.ts';
122
- import { eventsFired } from './events.ts';
121
+ import { drawOrderAt, type CoreDrawOrderKey } from './draw_order.ts';
122
+ import { eventsFired, type CoreEventRow } from './events.ts';
123
123
  import { REGION_TRIANGLES, REGION_UVS, type CoreClippedRow, type ShapeClipper } from './clipping.ts';
124
124
  import { poseGeometry, type CoreAttachmentRow, type CoreClipRow, type ShownGeometry } from './vertices.ts';
125
125
  import { RUNTIME_DEG, type CoreWorld } from './world.ts';
@@ -666,28 +666,105 @@ function stepWalk<P>(track: TrackState<P>, dt: number, first = false): P {
666
666
  const t = mode.loop ? (mode.time ?? loopedTime)(trackTime, duration, trackTime - dt) : trackTime < duration ? trackTime : duration;
667
667
  const before = ctx.time;
668
668
  ctx.time += mode.clock === undefined ? dt : mode.clock(dt, t - (first ? 0 : Math.max(last, 0)));
669
+ // A10's looping walk fires no event here — its one reader reads none — nor cuts its drawn attachments through a clip (`NO_CLIPPED_ROWS`), so its
670
+ // `clipped` rows are empty. The live track's looping mode (`live`) fires its events across the wrap by the rule `./track.ts` states as measured.
671
+ const fire = plant.events ?? eventsFired;
672
+ const keys = anim.timelines.events;
673
+ const pose = poseStepFrom(doc, plant, resolve, shape, mode, ctx, first, trackTime, t, {
674
+ world: (sliders) => (shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust })),
675
+ slots: (sliders, placeholders) => posedSlots(doc, anim.timelines, t, raw, sliders, placeholders),
676
+ drawOrder: anim.timelines.drawOrder,
677
+ at: t,
678
+ sample: { timelines: anim.timelines, t },
679
+ events: () => (mode.loop && mode.live !== true ? [] : mode.loop ? fire(keys, last, t, rawNumber, t < last) : fire(keys, last, t, rawNumber)),
680
+ });
681
+ track.last = t;
682
+ return pose;
683
+ }
684
+
685
+ /**
686
+ * What decides one step's pose before it is assembled (issue #1336): the bone
687
+ * world through the stepped phase, filling the sliders it applied; the slot
688
+ * rows, filling the placeholders the timelines left; the draw-order keys and
689
+ * the time they are read at; the deform and sequence timelines' sample, or the
690
+ * mixed deforms in its place; the events, computed when called. A walk's step
691
+ * reads every one off one animation at its time (`stepWalk`); the live tracks
692
+ * off their mixed entries (`./tracks.ts`). Everything past these is one body,
693
+ * `poseStepFrom`.
694
+ */
695
+ export interface StepPosers {
696
+ world: (sliders: SliderApplication[]) => Map<string, CoreWorld>;
697
+ slots: (sliders: readonly SliderApplication[], placeholders: Map<string, string | null>) => { rows: CoreSlotRow[]; conflicts: string[] };
698
+ drawOrder: readonly CoreDrawOrderKey[];
699
+ at: number;
700
+ sample: { timelines: CoreAnimationTimelines; t: number } | null;
701
+ mixed?: ReadonlyMap<string, { deform: number[] | null; seen: readonly string[] }>;
702
+ events: () => CoreEventRow[];
703
+ }
704
+
705
+ /**
706
+ * One step's pose from what decided it (`StepPosers`): the bone world, the
707
+ * physics phase moved past the reset on `first`, the slot rows, the draw
708
+ * order (the keys' order, else the setup's, then each slider's key), the
709
+ * attachments drawn in it, and the pose assembled in `shape` — refused by name
710
+ * where the core leaves a block out, in that order. The one body a walk's step
711
+ * and a live tracks' step share.
712
+ */
713
+ function poseStepFrom<P>(doc: CompiledDocument, plant: TimelinePlant, resolve: NonNullable<TimelinePlant['shown']>, shape: PoseShape<P>, mode: WalkMode, ctx: ReturnType<typeof freshStepContext>, first: boolean, trackTime: number, animationTime: number, posers: StepPosers): P {
669
714
  const sliders: SliderApplication[] = [];
670
- const world = shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust });
715
+ const world = posers.world(sliders);
671
716
  if (first) ctx.phase = 'update';
672
717
  const placeholders = new Map<string, string | null>();
673
- const slots = posedSlots(doc, anim.timelines, t, raw, sliders, placeholders);
718
+ const slots = posers.slots(sliders, placeholders);
674
719
  if (slots.conflicts.length > 0) throw new CoreInputError(`the raw walk leaves the slots out: ${slots.conflicts.join('; ')}`);
675
720
  const evalOrder = plant.drawOrder ?? drawOrderAt;
676
- let order = evalOrder(doc.slots.length, anim.timelines.drawOrder, t) ?? doc.slots.map((_s, k) => k);
721
+ let order = evalOrder(doc.slots.length, posers.drawOrder, posers.at) ?? doc.slots.map((_s, k) => k);
677
722
  for (const app of sliders) order = evalOrder(doc.slots.length, app.timelines.drawOrder, app.at) ?? order;
678
723
  const drawOrder = order.map((k) => doc.slots[k].name);
679
- const states = attachmentStates(doc, resolve, placeholders, { timelines: anim.timelines, t }, sliders, plant);
724
+ const states = attachmentStates(doc, resolve, placeholders, posers.sample, sliders, plant, posers.mixed);
680
725
  if (states.why.length > 0) throw new CoreInputError(`the raw walk leaves the attachments out: ${states.why.join('; ')}`);
681
726
  const rank = new Map(drawOrder.map((n, r) => [n, r]));
682
727
  const shown = [...states.shown].sort((a, b) => (rank.get(a.slot) ?? 0) - (rank.get(b.slot) ?? 0));
683
- // A10's looping walk fires no event here — its one reader reads none — nor cuts its drawn attachments through a clip (`NO_CLIPPED_ROWS`), so its
684
- // `clipped` rows are empty. The live track's looping mode (`live`) fires its events across the wrap by the rule `./track.ts` states as measured.
685
- const fire = plant.events ?? eventsFired;
686
- const keys = anim.timelines.events;
687
- const events = (): RawEvent[] => (mode.loop && mode.live !== true ? [] : (mode.loop ? fire(keys, last, t, rawNumber, t < last) : fire(keys, last, t, rawNumber)).map((e): RawEvent => [e[0], finite(e[1], 'event time'), e[2], finite(e[3], 'event float'), e[4]]));
688
- const pose = shape.step(doc, { trackTime, animationTime: t, world, slots: slots.rows, drawOrder, shown, events }, plant, mode);
689
- track.last = t;
690
- return pose;
728
+ const events = (): RawEvent[] => posers.events().map((e): RawEvent => [e[0], finite(e[1], 'event time'), e[2], finite(e[3], 'event float'), e[4]]);
729
+ return shape.step(doc, { trackTime, animationTime, world, slots: slots.rows, drawOrder, shown, events }, plant, mode);
730
+ }
731
+
732
+ /**
733
+ * The live tracks' side of the raw pose path (issue #1336, `./tracks.ts`):
734
+ * the refusals a walk makes before its first pose, a fresh physics context,
735
+ * the plant with the raw entry's rounding, and the two ways a pose is made —
736
+ * the setup pose reset (`reset: 'setup'`'s pose 0) and one step from what the
737
+ * mixed entries decided, assembled as the raw entry's pose (`RAW_SHAPE`,
738
+ * `RAW_MODE`: a non-finite value refused, every clipped row cut).
739
+ */
740
+ export interface RawTracksPath {
741
+ readonly ctx: ReturnType<typeof freshStepContext>;
742
+ /** `plant` with the raw entry's rounding. */
743
+ readonly raw: TimelinePlant;
744
+ setupPose(): RawPose;
745
+ step(first: boolean, trackTime: number, animationTime: number, posers: StepPosers): RawPose;
746
+ }
747
+
748
+ /** `RawTracksPath` opened on `doc`, refused by name where a walk on it would be (`openWalk`). */
749
+ export function openRawTracksPath(doc: CompiledDocument, plant: TimelinePlant): RawTracksPath {
750
+ const why = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
751
+ if (why !== null) throw new CoreInputError(`the raw walk leaves the bones out: ${why}`);
752
+ const ctx = freshStepContext(plant.physicsStep);
753
+ const raw: TimelinePlant = { ...plant, ...roundPlant(RAW_MODE) };
754
+ const resolve = plant.shown ?? shownAttachment;
755
+ return {
756
+ ctx,
757
+ raw,
758
+ setupPose: () => {
759
+ const setup = poseSetup(doc, raw, ctx);
760
+ const absent = setup.absent.filter(([block]) => block !== 'setup.clipped');
761
+ if (absent.length > 0) throw new CoreInputError(`the raw walk's reset pose leaves ${absent.map(([b, w]) => `${b} out (${w})`).join('; ')}`);
762
+ if (setup.world === null || setup.shown === null || setup.setup.slots === null || setup.setup.drawOrder === null) throw new CoreInputError('the raw walk\'s reset pose was not posed');
763
+ ctx.phase = 'update';
764
+ return RAW_SHAPE.setup(doc, setup.world, setup.setup.slots, setup.setup.drawOrder, setup.shown, plant, RAW_MODE);
765
+ },
766
+ step: (first, trackTime, animationTime, posers) => poseStepFrom(doc, plant, resolve, RAW_SHAPE, RAW_MODE, ctx, first, trackTime, animationTime, posers),
767
+ };
691
768
  }
692
769
 
693
770
  /** `walkIn`'s walk, each pose assembled in `shape` and handed to `visit` in order as it is posed (`poseRawAnimationEach`): the walk opened, then stepped over `steps`. */
package/src/core/track.ts CHANGED
@@ -17,6 +17,11 @@
17
17
  * scan on A10's schedule and at 60 fps over three cycles — 157,771 poses, every
18
18
  * one the same bits (the PR of issue #1276 carries the table).
19
19
  *
20
+ * Several animations on several tracks, set, queued and mixed as spine-core's
21
+ * `AnimationState` mixes them, are `openTracks` (`./tracks.ts`, issue #1336):
22
+ * a second state machine over the same pose path past the timelines, which
23
+ * this track reaches with the locals its one animation posed.
24
+ *
20
25
  * ## The walk
21
26
  *
22
27
  * - **Held** (`loop: false`): the raw entry's walk — the track time the